Skip to main content
Participant
July 11, 2011
Question

Textoutline or shadow support in TLF?

  • July 11, 2011
  • 1 reply
  • 718 views

Hi,

I am new to the Text Layout Framework and one thing I would like to know is if there is support for getting text outlines or shadows. It would be best if it can support things that are specified in the TTML or DFXP specification. (specifically, tts:textoutline, where a color and thickness of the outline for the text is specified)

As I searched online it seemed to me that in the old days in order to support this in Flash we would have to use a glow filter and apply that over which is kind of hacky. Is there a better way to do this now using Text Layout Frameworkand where may I obtain the new functionality/documents to proceed?

Thanks for any pointers

Ryan

This topic has been closed for replies.

1 reply

Adobe Employee
July 12, 2011

TLF does not support that feature right now.

Pls have a look at http://download.macromedia.com/pub/labs/textlayout/source/ExpressiveEffects.zip. It is a demo that creating good looking TLF text.

Adobe Employee
July 12, 2011

The code needs a few changes to work well in Flash pro CS5 or 5.5, but I forgot what I changed. So I paste the corrct code of scene 1 here.

Just replace the original code of scene 1.

import fl.data.DataProvider;
import flash.display.Sprite;
import flash.events.*;
import flash.display.BlendMode;
import flash.filters.BitmapFilter;
import flash.filters.BitmapFilterQuality;
import flash.filters.BlurFilter;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.conversion.TextFilter;
import flashx.textLayout.container.DisplayObjectContainerController;
import flashx.textLayout.compose.StandardFlowComposer;
import flashx.textLayout.formats.CharacterFormat;
import flashx.textLayout.events.CompositionCompletionEvent;
import flash.text.engine.FontPosture;
import flash.text.engine.FontWeight;
import flash.text.engine.TextBaseline;

var target:Sprite;
var filterArray:Array = new Array();
var coupledArray:Array = new Array();

var filterStateArray:Array = new Array();
var blendModeStateArray:Array = new Array();
var colorStateArray:Array = new Array();

//sprites used for text flow elements
var artFlow:TextFlow;
var artSprite:Sprite = new Sprite();
var twoFlow:TextFlow;
var twoSprite:Sprite = new Sprite();
var ddcFlow:TextFlow;
var ddcSprite:Sprite = new Sprite();
var sfFlow:TextFlow;
var sfSprite:Sprite = new Sprite();
var dateFlow:TextFlow;
var dateSprite:Sprite = new Sprite();
var descFlow:TextFlow;
var descSprite:Sprite = new Sprite();

//used to style the TextFlow elements
var characterFormat:CharacterFormat;

//Style UI Components
var textformat:TextFormat = new TextFormat();
textformat.color = 0xdddddd;
cbBlendingMode.textField.setStyle("textFormat",textformat);
cbBlendingMode.dropdown.setRendererStyle("textFormat",textformat);
cbFilters.textField.setStyle("textFormat",textformat);
cbFilters.dropdown.setRendererStyle("textFormat",textformat);

//event listeners for UI elements
downloadSrc.addEventListener(MouseEvent.CLICK, downloadSource);
downloadSrc.addEventListener(MouseEvent.MOUSE_OVER, showSrcTip);
downloadSrc.addEventListener(MouseEvent.MOUSE_OUT, hideSrcTip);

/*********************************
** Loading Management Block
*********************************/
//Step 1) monitor the loading progress of the iternal swf assets
this.addEventListener(Event.ENTER_FRAME,demoProgress);

function demoProgress(event:Event):void {
    var loaded:Number = stage.loaderInfo.bytesLoaded
    var total:Number = stage.loaderInfo.bytesTotal
    var percent:Number = loaded/total;
    trace("loading demo " + Math.floor(percent*100)+"%");
    if(loaded >= total){
        removeEventListener(Event.ENTER_FRAME, demoProgress);
        getCDN();       
        loadLocaleXML();
    }
}

//Step 2) load locale.xml file which has localized UI strings and paths to external fonts
var localeXML:XML;
var fontPath:String;
var loader:URLLoader;
function loadLocaleXML():void
{
    writeShellDebug("Load XML " + cdnURL+"XML/ExpressiveEffects.xml");
    var request:URLRequest = new URLRequest(cdnURL+"XML/ExpressiveEffects.xml");
    loader = new URLLoader(request);
    loader.addEventListener(Event.COMPLETE, xmlLoaded);

}
function xmlLoaded(event:Event)
{
    localeXML = XML(event.target.data);

    //get path to fonts
    fontPath = localeXML.externalAssets.fontPath.@url;
   
    //set UI Controls to localized text
    for each (var cntrl:XML in localeXML.UIControlStrings.control) {

        if(cntrl.@name == "cbBlendingMode") {
            var dp:DataProvider = new DataProvider(cntrl);
            cbBlendingMode.dataProvider = dp;
        } else if(cntrl.@name == "cbFilters") {
            dp = new DataProvider(cntrl);
            cbFilters.dataProvider = dp;
        } else if(cntrl.@name == "blendingModeLabel") {
            blendingModeLabel.text = cntrl.@value;
        } else if(cntrl.@name == "filterLabel") {
            filterLabel.text = cntrl.@value;
        } else if(cntrl.@name == "colorPickerLabel") {
            colorPickerLabel.text = cntrl.@value;
        } else if(cntrl.@name == "DownloadSrc") {
            srcTip.tip.tip_txt.text = cntrl.@value;
        }
    }
    cbBlendingMode.textField.setStyle("textFormat",textformat);
    cbBlendingMode.dropdown.setRendererStyle("textFormat",textformat);
    cbFilters.textField.setStyle("textFormat",textformat);
    cbFilters.dropdown.setRendererStyle("textFormat",textformat);
    loadFonts(fontPath);
}
//Step 3) load external font assets
function loadFonts(fontURL:String):void
{
    trace("loading fonts");
    var request:URLRequest = new URLRequest(cdnURL+fontURL);
    var loader:Loader = new Loader();
    loader.contentLoaderInfo.addEventListener(Event.COMPLETE, fontLoaded);
    loader.load(request);
}
function fontLoaded(event:Event)
{
    var FontLibrary:Class = event.target.applicationDomain.getDefinition("EffectsFonts") as Class;
    Font.registerFont(FontLibrary._GaramondItalic);
    Font.registerFont(FontLibrary._GaramondRegular);
    Font.registerFont(FontLibrary._Myriad);
    Font.registerFont(FontLibrary._MyriadBold);   
    setupContainers();            //update the character format of the TextFlow component
    doneLoading();        //tell the loader shell we are done loading
}

//create text flows and add them to the stage
function setupContainers():void {
   
    var artText:String = "<?xml version='1.0' encoding='utf-8'?><flow:TextFlow xmlns:flow='http://ns.adobe.com/textLayout/2008'><flow:p><flow:span>art</flow:span></flow:p></flow:TextFlow>";
    var twoText:String = "<?xml version='1.0' encoding='utf-8'?><flow:TextFlow xmlns:flow='http://ns.adobe.com/textLayout/2008'><flow:p><flow:span>2</flow:span></flow:p></flow:TextFlow>";
    var ddcText:String = "<?xml version='1.0' encoding='utf-8'?><flow:TextFlow xmlns:flow='http://ns.adobe.com/textLayout/2008'><flow:p><flow:span>Digital Design Conference</flow:span></flow:p></flow:TextFlow>";
    var sfText:String = "<?xml version='1.0' encoding='utf-8'?><flow:TextFlow xmlns:flow='http://ns.adobe.com/textLayout/2008'><flow:p><flow:span>SAN FRANCISCO, CALIFORNIA</flow:span></flow:p></flow:TextFlow>";
    var dateText:String = "<?xml version='1.0' encoding='utf-8'?><flow:TextFlow xmlns:flow='http://ns.adobe.com/textLayout/2008'><flow:p><flow:span>MARCH 2009</flow:span></flow:p></flow:TextFlow>";
    var descText:String = "<?xml version='1.0' encoding='utf-8'?><flow:TextFlow xmlns:flow='http://ns.adobe.com/textLayout/2008'><flow:p><flow:span>A conference for the 21st century, covering digital innovation, science and culture and bringing together thought leaders from Europe, the Middle-East, America and Asia.</flow:span></flow:p></flow:TextFlow>";
   
    // Art Flow //
    artSprite.name = "artSprite";
    artSprite.x = 40;
    artSprite.y = 70;
    artSprite.buttonMode = true;
    addChild(artSprite);
   
    artSprite.addEventListener(MouseEvent.CLICK, onSpriteClick);
   
    artFlow = TextFilter.importToFlow(artText, TextFilter.TEXT_LAYOUT_FORMAT);
    artFlow.flowComposer = new StandardFlowComposer();
    artFlow.flowComposer.addController(new DisplayObjectContainerController(artSprite, 280, 130));
    artFlow.addEventListener(CompositionCompletionEvent.COMPOSITION_COMPLETE, onArtFlowComplete);
    coupledArray["artSprite"] = artFlow;
    filterStateArray["artSprite"] = "none";
    blendModeStateArray["artSprite"] = "none";
   
    characterFormat = new CharacterFormat();
    characterFormat.fontLookup = FontLookup.EMBEDDED_CFF;
    characterFormat.fontFamily = "_GaramondItalic";
    characterFormat.fontStyle = FontPosture.ITALIC;
    characterFormat.color = 0x7d8dc5;
    characterFormat.fontSize = 215;
    characterFormat.baselineShift = 20;
    characterFormat.alignmentBaseline = TextBaseline.IDEOGRAPHIC_CENTER;
    artFlow.hostCharacterFormat = characterFormat;
    colorStateArray["artSprite"] = characterFormat.color;
   
    // Two Flow //
    twoSprite.name = "twoSprite";
    twoSprite.x = 270;
    twoSprite.y = 70;
    twoSprite.buttonMode = true;
    addChild(twoSprite);
   
    twoSprite.addEventListener(MouseEvent.CLICK, onSpriteClick);
   
    twoFlow = TextFilter.importToFlow(twoText, TextFilter.TEXT_LAYOUT_FORMAT);
    twoFlow.flowComposer = new StandardFlowComposer();
    twoFlow.flowComposer.addController(new DisplayObjectContainerController(twoSprite, 100, 125));
    coupledArray["twoSprite"] = twoFlow;
    filterStateArray["twoSprite"] = "none";
    blendModeStateArray["twoSprite"] = "none";
   
    characterFormat = new CharacterFormat();
    characterFormat.fontLookup = FontLookup.EMBEDDED_CFF;
    characterFormat.fontFamily = "_GaramondRegular";
    characterFormat.fontStyle = FontPosture.NORMAL;
    characterFormat.color = 0xffa533;
    characterFormat.fontSize = 150;
    twoFlow.hostCharacterFormat = characterFormat;
    colorStateArray["twoSprite"] = characterFormat.color;
   
    // Digital Design Conference //
    ddcSprite.name = "ddcSprite";
    ddcSprite.x = 100;
    ddcSprite.y = 185;
    ddcSprite.buttonMode = true;   
    addChild(ddcSprite);
   
    ddcSprite.addEventListener(MouseEvent.CLICK, onSpriteClick);
   
    ddcFlow = TextFilter.importToFlow(ddcText, TextFilter.TEXT_LAYOUT_FORMAT);
    ddcFlow.flowComposer = new StandardFlowComposer();
    ddcFlow.flowComposer.addController(new DisplayObjectContainerController(ddcSprite, 360, 45));
    coupledArray["ddcSprite"] = ddcFlow;
    filterStateArray["ddcSprite"] = "none";
    blendModeStateArray["ddcSprite"] = "none";
   
    characterFormat = new CharacterFormat();
    characterFormat.fontLookup = FontLookup.EMBEDDED_CFF;
    characterFormat.fontFamily = "_Myriad";
    characterFormat.fontStyle = FontPosture.NORMAL;
    characterFormat.color = 0x000033;
    characterFormat.fontSize = 30;
    characterFormat.dominantBaseline = TextBaseline.IDEOGRAPHIC_CENTER;
    characterFormat.baselineShift = 5;
    ddcFlow.hostCharacterFormat = characterFormat;
    colorStateArray["ddcSprite"] = characterFormat.color;
   
    // San Francisco //
    sfSprite.name = "sfSprite";
    sfSprite.x = 50;
    sfSprite.y = 220;
    sfSprite.buttonMode = true;   
    addChild(sfSprite);
   
    sfSprite.addEventListener(MouseEvent.CLICK, onSpriteClick);
   
    sfFlow = TextFilter.importToFlow(sfText, TextFilter.TEXT_LAYOUT_FORMAT);
    sfFlow.flowComposer = new StandardFlowComposer();
    sfFlow.flowComposer.addController(new DisplayObjectContainerController(sfSprite, 300, 30));
    coupledArray["sfSprite"] = sfFlow;
    filterStateArray["sfSprite"] = "none";
    blendModeStateArray["sfSprite"] = "none";
   
    characterFormat = new CharacterFormat();
    characterFormat.fontLookup = FontLookup.EMBEDDED_CFF;
    characterFormat.fontFamily = "_MyriadBold";
    characterFormat.fontWeight = FontWeight.BOLD;
    characterFormat.color = 0xca0f16;
    characterFormat.fontSize = 12;
    characterFormat.trackingLeft = 3;
    characterFormat.dominantBaseline = TextBaseline.IDEOGRAPHIC_CENTER;
    characterFormat.baselineShift = 10;
    sfFlow.hostCharacterFormat = characterFormat;
    colorStateArray["sfSprite"] = characterFormat.color;
   
    // Date //
    dateSprite.name = "dateSprite";
    dateSprite.x = 285;
    dateSprite.y = 240;
    dateSprite.rotation = 90;
    dateSprite.buttonMode = true;   
    addChild(dateSprite);
   
    dateSprite.addEventListener(MouseEvent.CLICK, onSpriteClick);
   
    dateFlow = TextFilter.importToFlow(dateText, TextFilter.TEXT_LAYOUT_FORMAT);
    dateFlow.flowComposer = new StandardFlowComposer();
    dateFlow.flowComposer.addController(new DisplayObjectContainerController(dateSprite, 100, 30));
    coupledArray["dateSprite"] = dateFlow;
    filterStateArray["dateSprite"] = "none";
    blendModeStateArray["dateSprite"] = "none";
   
    characterFormat = new CharacterFormat();
    characterFormat.fontLookup = FontLookup.EMBEDDED_CFF;
    characterFormat.fontFamily = "_MyriadBold";
    characterFormat.fontWeight = FontWeight.BOLD;
    characterFormat.color = 0x464f86;
    characterFormat.fontSize = 10;
    characterFormat.trackingLeft = 2;
    dateFlow.hostCharacterFormat = characterFormat;
    colorStateArray["dateSprite"] = characterFormat.color;
   
    // Desc //
    descSprite.name = "descSprite";
    descSprite.x = 295;
    descSprite.y = 240;
    descSprite.buttonMode = true;   
    addChild(descSprite);
   
    descSprite.addEventListener(MouseEvent.CLICK, onSpriteClick);
   
    descFlow = TextFilter.importToFlow(descText, TextFilter.TEXT_LAYOUT_FORMAT);
    descFlow.flowComposer = new StandardFlowComposer();
    descFlow.flowComposer.addController(new DisplayObjectContainerController(descSprite, 150, 100));
    coupledArray["descSprite"] = descFlow;
    filterStateArray["descSprite"] = "none";
    blendModeStateArray["descSprite"] = "none";
   
    characterFormat = new CharacterFormat();
    characterFormat.fontLookup = FontLookup.EMBEDDED_CFF;
    characterFormat.fontFamily = "_GaramondItalic";
    characterFormat.fontStyle = FontPosture.ITALIC;
    characterFormat.color = 0x000000;
    characterFormat.fontSize = 12;
    descFlow.hostCharacterFormat = characterFormat;
    colorStateArray["descSprite"] = characterFormat.color;
   
    updateContainers();
   
    cbBlendingMode.addEventListener(Event.CHANGE, onBlendingModeChange);
    cbFilters.addEventListener(Event.CHANGE, onFilterChange);
    colorPicker.addEventListener(Event.CHANGE, onColorChange);
    target = artSprite;
}
        
function updateContainers():void {
    artFlow.flowComposer.updateAllContainers();
    twoFlow.flowComposer.updateAllContainers();
    ddcFlow.flowComposer.updateAllContainers();
    sfFlow.flowComposer.updateAllContainers();
    dateFlow.flowComposer.updateAllContainers();
    descFlow.flowComposer.updateAllContainers();
}
       
function onSpriteClick(e:Event):void {   
    var targetSprite:Sprite;
        if (e.target is TextLine) {
            targetSprite = e.target.parent;
        }else{
            targetSprite = Sprite(e.target);   
        }
    target = targetSprite;
    root['hoverBox'].alpha = 0;
    root['activeBox'].alpha = 1;   
    root['activeBox'].width = targetSprite.width;
    root['activeBox'].height = targetSprite.height;   
    root['activeBox'].y = targetSprite.y;   
   
        if (target.name == "dateSprite") {
            root['activeBox'].x = targetSprite.x-15;
        }else{
            root['activeBox'].x = targetSprite.x;
        }
       
    selectItemInCombo(cbBlendingMode,blendModeStateArray[target.name]);
    selectItemInCombo(cbFilters,filterStateArray[target.name]);
    colorPicker.selectedColor = colorStateArray[target.name];
}

function onColorChange(e:Event):void {
    var cp:ColorPicker = e.target as ColorPicker
    characterFormat = new CharacterFormat(coupledArray[target.name].hostCharacterFormat);
    characterFormat.color = cp.selectedColor;
    coupledArray[target.name].hostCharacterFormat = characterFormat;
    colorStateArray[target.name] = cp.selectedColor;
    applyFilterChange();
    updateContainers();
}

function onBlendingModeChange(e:Event):void {
    switch (ComboBox(e.target).selectedItem.data) {
        case "difference":
                target.blendMode = BlendMode.DIFFERENCE;
            break;
        case "lighten":
                target.blendMode = BlendMode.LIGHTEN;
            break;
        case "multiply":
                target.blendMode = BlendMode.MULTIPLY;
            break;
        case "add":
                target.blendMode = BlendMode.ADD;
            break;           
        case "darken":
                target.blendMode = BlendMode.DARKEN;
            break;           
        case "hardlight":
                target.blendMode = BlendMode.HARDLIGHT;
            break;           
        case "overlay":
                target.blendMode = BlendMode.OVERLAY;
            break;           
        case "screen":
                target.blendMode = BlendMode.SCREEN;
            break;   
        case "erase":
                target.blendMode = BlendMode.ERASE;
            break;   
        case "invert":
                target.blendMode = BlendMode.INVERT;
            break;               
        case "layer":
                target.blendMode = BlendMode.LAYER;
            break;               
        case "shader":
                target.blendMode = BlendMode.SHADER;
            break;               
        case "subtract":
                target.blendMode = BlendMode.SUBTRACT;
            break;               
        default:
                target.blendMode = BlendMode.NORMAL;
            break;
    }
    blendModeStateArray[target.name] = ComboBox(e.target).selectedItem.data;
    target.dispatchEvent(new MouseEvent(MouseEvent.CLICK, true, false));
}

function applyFilterChange(filter:String=null):void {
   
    filter = (filter) ? filter : filterStateArray[target.name];
   
    filterArray = new Array();
        switch (filter) {
            case "dropshadow":
                    filterArray.push(new DropShadowFilter(3, 45, 0x000000, 0.75, 3, 3, 0.75, 3));
                break;
            case "blur":
                    filterArray.push(new BlurFilter(5, 5, BitmapFilterQuality.HIGH));
                break;
            case "glow":
                    filterArray.push(new GlowFilter(0x990000,0.8,6,6,1.10,BitmapFilterQuality.HIGH,false,false));
                break;           
            case "bevel":
                    filterArray.push(new BevelFilter(2,45,0xFFFFFF,0.8,0x000000,0.8,2,2,.75,BitmapFilterQuality.HIGH,BitmapFilterType.INNER,false));
                break;
            case "gradientglow":
                    filterArray.push(new GradientGlowFilter(3,45,[0xFFFFFF, 0xFF0000, 0xFFFF00, 0x00CCFF],[0, 1, 1, 1],[0, 63, 126, 255],6,6,2.5,BitmapFilterQuality.HIGH,BitmapFilterType.OUTER,false));
                break;
            case "gradientbevel":
                    characterFormat = new CharacterFormat(coupledArray[target.name].hostCharacterFormat);
                    filterArray.push(new GradientBevelFilter(2,45,[0xFFFFFF, characterFormat.color, 0x000000],[1,1,1],[0, 128, 255],2,2,1,BitmapFilterQuality.LOW,BitmapFilterType.INNER,true));
                break;               
            default:
                    // No filters
                break;
        }       
    target.filters = filterArray;
    filterStateArray[target.name] = filter;
    target.dispatchEvent(new MouseEvent(MouseEvent.CLICK, true, false));
}

function onFilterChange(e:Event):void {
    applyFilterChange(ComboBox(e.target).selectedItem.data);
}

function selectItemInCombo(cb:ComboBox,item:String):void {
    for (var i:int=0;i<cb.length;i++) {
        if (cb.getItemAt(i).data == item) {
            cb.selectedIndex = i;
        }
    }
}

function onArtFlowComplete(e:Event):void {
    artSprite.dispatchEvent(new MouseEvent(MouseEvent.CLICK, true, false));
}

function downloadSource(e:Event):void
{
    navigateToURL(new URLRequest(cdnURL+"DownloadableSrc/ExpressiveEffects.zip"));
}
function showSrcTip(e:Event):void
{
    srcTip.gotoAndPlay(2);
}

function hideSrcTip(e:Event):void
{
    srcTip.gotoAndStop(1);
}

/****************************************************
** Adobe Shell Code for World Class Text Tour
** only useful when demo is loaded in the demo shell
*******************************************************/
//tells shell it is ok to display demo
function doneLoading():void
{
    try {
        var shellLoader:MovieClip = MovieClip(this.parent.parent);
        shellLoader.payloadContentLoaded();
    } catch (error:Error){
        //nothing
    }
}
//gets server paths, if any, from shell
var cdnURL:String = "";
function getCDN():void
{
    try {
        var shellLoader:MovieClip = MovieClip(this.parent.parent);
        cdnURL = shellLoader.cdnURL;
    } catch (error:Error){
        cdnURL = "";        //thrown when run outside of shell context
    }
}
//for debug use only
function writeShellDebug(s:String):void
{
    try {
        var shellLoader:MovieClip = MovieClip(this.parent.parent);
        shellLoader.writeDebug(s);
    } catch (error:Error){
        //nothing
    }
}