Archive for the 'Flex' Category

18
Feb
10

BitmapData.draw & Matrix – how to copy a section out of the middle of an image

Okay, if you google this, you’ll find TONS of people frustrated with the BitmapData class’s draw method. The livedocs are pretty vague here. They don’t really tell you how to use the matrix variable you can pass draw, and they kinda make it sound like the clipRect rectangle would let you arbitrarily cut out any size and shape from the drawable source. That’s almost true. What they don’t tell you is that the section you cut out will remain at the x/y coordinates from which you cut it.

So, let’s say you have a 100×100 image, and you want a copy of the section from 10/20 to 20/30. You might think it would be this easy:

var bmpData:BitmapData = new BitmapData(10,10);// a new 10x10 image
var rect:Rectangle = new Rectangle(10,20,10,10);
bmpData.draw(drawableItem,null,null,null,rect);

Truth is, it’s not much harder, but that bit of code will not give you what you want. The bit you grabbed from 10/20 to 20/30 will remain at those coordinates in your output, and since you only made a 10×10 image, it will be empty.

Here’s the code you want:

var bmpData:BitmapData = new BitmapData(10,10);// a new 10x10 image
var matrix:Matrix = new Matrix();
matrix.translate(-10,-20);
var rect:Rectangle = new Rectangle(10,20,10,10);
bmpData.draw(drawableItem,matrix,null,null,rect);

Your clipRect, there, will take the section you want, and the matrix transformation will translate it back to 0/0.

16
Apr
09

Flex/Flash Runtime Fonts with HTML

I’m guessing you arrived here because you did a search for “runtime fonts flex” or similar. I assume, therefore, that you know why you’re here. I got there just a couple months ago.

There’s a wealth of blog articles out there on how to dynamically load font libraries at runtime in Flex. here are just a few:
scottmorgan.com, undefined-type.com, nochump.com

What I seemed to find, though, was that these gave you one of two options: use a single font per Text component , TextArea or Label MXML component(in other words, no style control via HTML/CSS), or use the TextField AS class(which means you say “goodbye” to Design View in Flex Builder).

All I want is to be able to do this:


<VBox>
<TextField htmlText="&lt;span class='GothamBold'&gt;Gotham Bold&lt;/span&gt;&lt;span class='GothamBook'&gt;Gotham Book&lt;/span&gt;"/>
</VBox>

No dice.

So here’s how I did it.

I created a custom TextField AS class that can be called as an MXML component. It looks like this:

package com.[EMPLOYER].[CLIENT].decaf.components
{
import com.[CLIENT].services.StyleManager;

import flash.events.Event;
import flash.events.MouseEvent;
import flash.net.URLRequest;
import flash.net.navigateToURL;
import flash.text.AntiAliasType;
import flash.text.Font;
import flash.text.StyleSheet;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;

import mx.controls.Text;
import mx.controls.textClasses.TextRange;
import mx.events.FlexEvent;
import mx.utils.StringUtil;
/**
* This TextField differs from the native Text component in three important ways:
* 1. This TextField component intelligently sets the embedFonts attribute based on registered
* fonts and their ability to display all the glyps set to the htmlText attribute.
* 2. This TextField component exposes a styleSheet attribute, and by default will capture
* Decaf's dispatching of the event fired when the CSS document has loaded, and assign that
* document as its own stylesheet.
* 3. This TextField Component handles the oddly complex relationship between setting selectable
* to false, and yet maintaining link functionality with the hand cursor and navigateToURL intact.
* @author Thomas Brady
* @summary An extension of Text that includes embedded fonts, setting the stylesheet, while preserving link functionality.
* */
public class TextField extends Text
{
private var _styleSheet:StyleSheet;
private var styleMan:StyleManager;
private var _style:Boolean = false;
private var _embed:Boolean = false;

/** We subscribe to three event broadcasts in the constructor:
* 1. FlexEvent.UPDATE_COMPLETE - Our override of the native Text event. A key method.
* 2. MouseEvent.CLICK - Essential for preserving the linked HTML anchors while setting
* 3. StyleManager.dispatcher.ON_CSS_LOAD - When the StyleManager has loaded a CSS doc
* this TextField will then immediately set its stylesheet to the StyleManager's
* reference.
* selectable to false.*/
public function TextField()
{
super();
//super.textField.embedFonts = true;
StyleManager.dispatcher.addEventListener("ON_CSS_LOAD",updateCompleteHandler);
addEventListener(FlexEvent.UPDATE_COMPLETE, updateCompleteHandler);
addEventListener(MouseEvent.CLICK,onClick);
}

/** This is a native function of the Text component. By overriding it, we ensure
* that our improvements are not reset by the native method.
*/
private function updateCompleteHandler(e:Event):void {
super.textField.selectable = false;
super.textField.wordWrap = true;
super.textField.multiline = true;
super.textField.mouseWheelEnabled = false;
super.textField.autoSize = TextFieldAutoSize.LEFT;
if (_style) {
super.textField.styleSheet = StyleManager.styleSheet;
} else {
super.textField.styleSheet = null;
}
super.textField.embedFonts = _embed;
}

/*
* get the "class" from an HTML string, if one exists
*/
private function getFontClassName( str:String ):String
{
var _term:String = "class=";
var _classIdx:Number = str.indexOf( _term );
if( _classIdx > - 1 )
{
var _searchTermLength:Number =_term.length;
var _quote:String = str.slice( _classIdx + _searchTermLength, _classIdx + _searchTermLength + 1 );
var _lastQuoteIdx:uint = str.slice( _classIdx + _searchTermLength + 1, str.length ).indexOf( _quote );
var _className:String = str.slice( _classIdx + _searchTermLength + 1, _classIdx + _searchTermLength + 1 + _lastQuoteIdx );
return _className;
}
else
{
return null;
}
}

override protected function childrenCreated():void
{
super.childrenCreated();
}

/** The heart of the TextField class
* The first important action taken in this method is to strip out unneeded newline, carriage
* return, and tab characters that fowl up rendering. The method goes on to determine whether
* there is a stylesheet, whether the font called for in the HTML has been registered and
* whether the font contains all the glyphs necessary to render the HTML before setting the
* values of _style and _embed Booleans, which are used in updateCompleteHandler.
* NOTE: There is a useful debug loop in here (commented out) that can help you locate glyphs
* that are not included in the font. */
override public function set htmlText(value:String):void {
var regEx:RegExp = /[\n\t\r]/g;
value = value.replace(regEx,"");

if (StyleManager.styleSheet) {
_styleSheet = StyleManager.styleSheet;
//trace("assigned StyleSheet");
// get the classname from HTML string
var _className:String = getFontClassName( value );
//trace( "drawText > _className: " + _className );

// get the font-family from the _className
var _fontName:String = StyleManager.styleSheet.getStyle( "."+_className ).fontFamily;
//trace( "drawText > _fontName: " + _fontName );

if( _className != null && _fontName != null )
{
// create a Font reference
var _font:Font;

var _fonts:Array = Font.enumerateFonts();
for( var c:Number=0; c < _fonts.length; c++ )
{
if( _fonts[c].fontName == _fontName )
{
_font = _fonts[c];
break;
}
}

//trace("FONT:"+_font);

//DEBUG LOOP - checks for what glyph is not included

/*
for (var myIndex:uint=0;myIndex<value.length;myIndex++) {
if (!_font.hasGlyphs(value.substr(myIndex,1))) {
trace("FONT DOES NOT HAVE CHARACTER -"+value.substr(myIndex,1)+"- AT INDEX "+myIndex+" WHICH IS CHAR CODE "+value.charCodeAt(myIndex));
}
}
*/

if( _font.hasGlyphs( value ) )
{
trace("EMBEDDING FONT");
super.textField.styleSheet = StyleManager.styleSheet;
super.textField.antiAliasType = AntiAliasType.ADVANCED;
super.textField.embedFonts = true;
_embed = true;
_style = true;
}
else
{
//trace("NOT EMBEDDING FONT FOR ");
trace( "Embedded font does not contain all the necessary chars. using non-embedded version font for following block");
trace(value);
super.textField.styleSheet = _styleSheet;
_embed = false;
_style = true;
}
}
else
{
// there's no class name, so just use some default
trace("NO CLASS NAME");
super.textField.styleSheet = null;
var _tf2:TextFormat = new TextFormat( "_sans", 12 );
super.textField.defaultTextFormat = _tf2;
_embed = false;
_style = false;
}
} else {
trace("NO STYLESHEET");
}
super.htmlText = value;
}

protected function onClick(pEvent:MouseEvent):void
{
// Find the letter under our click
var index:int = textField.getCharIndexAtPoint(pEvent.localX, pEvent.localY);
if (index != -1)
{
// convert the letter to a text range so we can extract the url
var range:TextRange = new TextRange(this, false, index, index + 1);
// make sure it contains a url
if (range.url.length > 0)
{
// The normal click event strips out the 'event;' portion of the url.
// So to be consistent, let's strip it out, too.
var url:String = range.url;
var tlLink:String = String(range.htmlText);
var targetAndRemaining:String = mx.utils.StringUtil.trim(tlLink.slice(tlLink.lastIndexOf(" TARGET=")+9,tlLink.length - 1));
var target:String = targetAndRemaining.slice(0,targetAndRemaining.indexOf('"'));
/*
if (url.substr(0, 6) == 'event:')
{
url = url.substring(6);
}
*/
// Manually dispatch the link event with the url neatly included
//dispatchEvent(new TextEvent(TextEvent.LINK, false, false, url));
navigateToURL(new URLRequest(url),target);
}
}
}

[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="")]
public function set styleSheet(sh:StyleSheet):void {
_styleSheet = sh;
super.textField.styleSheet = sh;
}

}
}

Extending Text gets us all the real nuts and bolts we need for functionality and use in MXML. Text, however, does not set embedFonts to true, nor does it even expose that attribute (from its super class Label).

So right there in updateCompleteHandler, which is called any time a property of the TextField changes, we go ahead and set the embedFonts to true. In reality, you’ll probably want to do some tests before you go ahead and set that true: you’ll want to check to see that you have loaded the fonts the TextField wants to use and that the fonts you loaded have all the glyphs in your string, for instance. We also set the stylesheet.

The selectable property is also an interesting challenge. Thanks to this Flex Cookbook tip we can turn selectable off and still get anchor tags to properly link.

The stylesheet dependency introduces some interesting timing issues. I’m instantiating this class via MXML, so I can’t count on the stylesheet (the CSS doc) having loaded. Flex is going to scoop up this MXML and start rendering right away, and the first time updateCompleteHandler gets called, we’re not going to have a stylesheet. So I have a static stylesheet class that dispatches an “ON_CSS_LOAD” event, to which the TextField class listens, and fires updateCompleteHandler when it hears it. At that point, and forward, there will be an actual stylesheet.

There’s also the set styleSheet for overrides, which is inspectable, which lets you modify this variable via AS or MXML.

So what you end up with is an MXML component that you can see in design mode. You can, therefore, easily lay things out. You won’t, yet, see any embedded fonts. You’re not at runtime. You also can’t double-click the component in design view to edit the text, the way you can a Text component. That’s something I’d like to look into.