/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.compiler.fxg.flex; import java.io.File; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.flex.compiler.common.Multiname; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.definitions.IClassDefinition; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.ITypeDefinition; import org.apache.flex.compiler.fxg.dom.IFXGNode; import org.apache.flex.compiler.fxg.swf.FXG2SWFTranscoder; import org.apache.flex.compiler.internal.fxg.dom.CDATANode; import org.apache.flex.compiler.internal.fxg.dom.GraphicContentNode; import org.apache.flex.compiler.internal.fxg.dom.GraphicNode; import org.apache.flex.compiler.internal.fxg.dom.GroupNode; import org.apache.flex.compiler.internal.fxg.dom.IMaskableNode; import org.apache.flex.compiler.internal.fxg.dom.IMaskingNode; import org.apache.flex.compiler.internal.fxg.dom.RichTextNode; import org.apache.flex.compiler.internal.fxg.dom.TextGraphicNode; import org.apache.flex.compiler.internal.fxg.dom.ITextNode; import org.apache.flex.compiler.internal.fxg.dom.richtext.BRNode; import org.apache.flex.compiler.internal.fxg.dom.richtext.DivNode; import org.apache.flex.compiler.internal.fxg.dom.richtext.ImgNode; import org.apache.flex.compiler.internal.fxg.dom.richtext.LinkNode; import org.apache.flex.compiler.internal.fxg.dom.richtext.ParagraphNode; import org.apache.flex.compiler.internal.fxg.dom.richtext.SpanNode; import org.apache.flex.compiler.internal.fxg.dom.richtext.TCYNode; import org.apache.flex.compiler.internal.fxg.dom.richtext.TabNode; import org.apache.flex.compiler.internal.fxg.dom.richtext.TextLayoutFormatNode; import org.apache.flex.compiler.internal.fxg.dom.types.BlendMode; import org.apache.flex.compiler.internal.fxg.dom.types.MaskType; import org.apache.flex.compiler.internal.projects.FlexProject; import org.apache.flex.compiler.internal.scopes.ASProjectScope; import org.apache.flex.compiler.problems.FXGDefinitionNotFoundProblem; import org.apache.flex.compiler.problems.FXGUndefinedPropertyProblem; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.swf.tags.DefineSpriteTag; import org.apache.flex.swf.tags.ICharacterTag; import org.apache.flex.swf.tags.ITag; import org.apache.flex.swf.tags.PlaceObject3Tag; import org.apache.flex.utils.StringUtils; /** * This implementation generates ActionScript classes for features not supported * in SWF tags. For example, ActionScript classes must be generated to draw * FXG 1.0 TextGraphic nodes programmatically (using instances of the * spark.components.RichText ActionScript class). To maintain a link between * the DefineSpriteTag display list and a generated RichText wrapper class, a * SWF SymbolClass is used and associated with the DefineSpriteTag. * * Other features not supported in SWF that require ActionScript classes to be * generated include alpha masks, luminosity masks, and pixel-bender based * blend modes, namely: colordodge, colorburn, exclusion, softlight, hue, * saturation, color, and luminosity. */ public class FlexFXG2SWFTranscoder extends FXG2SWFTranscoder { private String packageName; private final ICompilerProject project; private final ITypeDefinition stringType; private final ITypeDefinition objectType; private final ITypeDefinition anyType; private final HashMap<String, Integer> nameCounter; private Map<String, ITypeDefinition> dependencies; private static final String packageSpriteVisualElement = "spark.core.SpriteVisualElement"; private static final String packageShaderFilter = "spark.filters.ShaderFilter"; private static final String packageLuminosityMaskShader = "mx.graphics.shaderClasses.LuminosityMaskShader"; private static final String packageIFlexModuleFactory = "mx.core.IFlexModuleFactory"; private static final String packageFlashEvent = "flash.events.Event"; private static final String packageBreakElement = "flashx.textLayout.elements.BreakElement"; private static final String packageDivElement = "flashx.textLayout.elements.DivElement"; private static final String packageLinkElement = "flashx.textLayout.elements.LinkElement"; private static final String packageImgElement = "flashx.textLayout.elements.InlineGraphicElement"; private static final String packageRichText = "spark.components.RichText"; private static final String packageParagraphElement = "flashx.textLayout.elements.ParagraphElement"; private static final String packageSpanElement = "flashx.textLayout.elements.SpanElement"; private static final String packageTabElement = "flashx.textLayout.elements.TabElement"; private static final String packageTCYElement = "flashx.textLayout.elements.TCYElement"; private static final String packageTextLayoutFormat = "flashx.textLayout.formats.TextLayoutFormat"; /** * Construct a Flex specific FXG to SWF tag transcoder. */ public FlexFXG2SWFTranscoder(ICompilerProject project) { super(); this.project = project; stringType = (ITypeDefinition)project.getBuiltinType(IASLanguageConstants.BuiltinType.STRING); objectType = (ITypeDefinition)project.getBuiltinType(IASLanguageConstants.BuiltinType.OBJECT); anyType = (ITypeDefinition)project.getBuiltinType(IASLanguageConstants.BuiltinType.ANY_TYPE); nameCounter = new HashMap<String, Integer>(); dependencies = new HashMap<String, ITypeDefinition>(); } @Override public FXGSymbolClass transcode(IFXGNode node, String packageName, String className, Map<ITag, ITag> extraTags, Collection<ICompilerProblem> problems) { this.packageName = packageName; super.transcode(node, packageName, className, extraTags, problems); addDependency(packageSpriteVisualElement); //add dependency to the class we extend // Create a new sprite class to map to this Graphic's DefineSpriteTag StringBuilder buf = new StringBuilder(512); buf.append("package ").append(packageName).append("\n"); buf.append("{\n\n"); buf.append("import ").append(packageSpriteVisualElement).append(";\n\n"); buf.append("public class ").append(className).append(" extends SpriteVisualElement\n"); buf.append("{\n"); buf.append(" public function ").append(className).append("()\n"); buf.append(" {\n"); buf.append(" super();\n"); if (node instanceof GraphicNode) { GraphicNode graphicNode = (GraphicNode)node; if (!Double.isNaN(graphicNode.viewWidth)) buf.append(" viewWidth = ").append(graphicNode.viewWidth).append(";\n"); if (!Double.isNaN(graphicNode.viewHeight)) buf.append(" viewHeight = ").append(graphicNode.viewHeight).append(";\n"); if (graphicNode.getMaskType() == MaskType.ALPHA && graphicNode.mask != null) { int maskIndex = graphicNode.mask.getMaskIndex(); buf.append(" this.cacheAsBitmap = true;\n"); buf.append(" this.mask = this.getChildAt(").append(maskIndex).append(");\n"); } } buf.append(" }\n"); // End constructor buf.append("}\n"); // End class buf.append("}\n"); // End package graphicClass.setGeneratedSource(buf.toString()); return graphicClass; } /** * Finds the definition {@link ITypeDefinition} for the specified qualified * name and add this type to the list of classes/interfaces on which the FXG file * processed by this transcoder have dependency. * * @param qname qualified name which to add the dependency to */ private void addDependency(String qname) { getDefinition(qname); } /** * Returns the definition {@link ITypeDefinition} for the specified qualified * name and add this type to the list of classes/interfaces on which the FXG file * processed by this transcoder have dependency. * * @param qname qualified name for which to find the definition * @return the definition for the specified qualified name or * <code>null</code> if definition cannot be resolved. */ private ITypeDefinition getDefinition(String qname) { ITypeDefinition definition = dependencies.get(qname); if(definition == null) { ASProjectScope scope = (ASProjectScope)project.getScope(); definition = (ITypeDefinition)scope.findDefinitionByName(Multiname.crackDottedQName(project, qname , true), true); if(definition == null) { problems.add(new FXGDefinitionNotFoundProblem(qname)); return null; } dependencies.put(qname, definition); } return definition; } /** * This override simply ensures that a FlexFXG2SWFTranscoder is created * instead of an instance of the base class FXG2SWFTranscoder. * * @return a new transcoder with an isolated context to process a Library * Definition. */ @Override public FXG2SWFTranscoder newInstance() { FlexFXG2SWFTranscoder graphics = new FlexFXG2SWFTranscoder(project); graphics.packageName = packageName; graphics.graphicClass = graphicClass; graphics.definitions = definitions; graphics.extraTags = extraTags; graphics.imageMap = imageMap; graphics.depthMap = depthMap; return graphics; } //-------------------------------------------------------------------------- // // Advanced Graphics Code Generation // //-------------------------------------------------------------------------- /** * This override handles advanced mask features not supported by SWF. This * includes alpha and luminosity masks. * * @param node - the node being masked (which has a reference to the mask * node) * @param parentSprite - the SWF DefineSpriteTag that will be the parent of * this node when added to the display list */ @Override protected PlaceObject3Tag mask(IMaskableNode node, DefineSpriteTag parentSprite) { IMaskingNode mask = node.getMask(); PlaceObject3Tag po3 = super.mask(node, parentSprite); if (mask != null) { MaskType maskType = node.getMaskType(); if (maskType != MaskType.CLIP) { // Record the display list position that the mask was placed. // The ActionScript display list is 0 based, but SWF the depth // counter starts at 1, so we subtract 1. int maskIndex = getSpriteDepth(parentSprite) - 1; mask.setMaskIndex(maskIndex); } if (maskType == MaskType.LUMINOSITY) { // Create a new SymbolClass to map to this mask's // DefineSpriteTag (see below) String className = createUniqueName(graphicClass.getClassName() + "_Mask"); FXGSymbolClass symbolClass = new FXGSymbolClass(); symbolClass.setPackageName(packageName); symbolClass.setClassName(className); // Then record this SymbolClass with the top-level graphic // SymbolClass so that it will be associated with the FXG asset. graphicClass.addAdditionalSymbolClass(symbolClass); // Calculate luminosity mode int mode = 0; if (node.getLuminosityClip()) mode += 2; if (node.getLuminosityInvert()) mode += 1; //Add required dependencies addDependency(packageShaderFilter); addDependency(packageLuminosityMaskShader); StringBuilder buf = new StringBuilder(768); buf.append("package ").append(packageName).append("\n"); buf.append("{\n\n"); buf.append("import flash.display.Sprite;\n"); buf.append("import ").append(packageShaderFilter).append(";\n"); buf.append("import ").append(packageLuminosityMaskShader).append(";\n\n"); buf.append("public class ").append(className).append(" extends Sprite\n"); buf.append("{\n"); buf.append(" public function ").append(className).append("()\n"); buf.append(" {\n"); buf.append(" super();\n"); buf.append(" this.cacheAsBitmap = true;\n"); buf.append(" var shader:LuminosityMaskShader = new LuminosityMaskShader();\n"); buf.append(" shader.mode = ").append(mode).append(";\n"); buf.append(" var filter:ShaderFilter = new ShaderFilter(shader);\n"); buf.append(" this.filters = [filter.clone()];\n"); buf.append(" }\n"); buf.append("}\n"); // End class buf.append("}\n"); // End package symbolClass.setGeneratedSource(buf.toString()); symbolClass.setSymbol(po3.getCharacter()); } } return po3; } @Override /** * This override handles graphic content nodes that make use of advanced * graphics features not supported by SWF. A Group is translated into a * SWF DefineSpriteTag tag. Shapes are translated into SWF DefineShape tags. * * @param node - the graphic content node * @return the PlaceObjcet definition that would place the graphic on stage */ protected PlaceObject3Tag graphicContentNode(GraphicContentNode node) { // Keep track of whether a node had a mask and filters as this // scenario needs to be special cased to match the rendering order // of the ActionScript drawing API. boolean hasMaskAndFilters = node.mask != null && node.filters != null; PlaceObject3Tag po3 = super.graphicContentNode(node); // We skip text nodes because they already generate a symbol class // and will handle advanced graphics there if (po3 != null && !(node instanceof ITextNode) && hasAdvancedGraphics(node)) { SymbolClassType symbolClassType = SymbolClassType.SHAPE; if (hasMaskAndFilters || (node instanceof GroupNode)) symbolClassType = SymbolClassType.SPRITE; advancedGraphics(node, po3.getCharacter(), symbolClassType); } return po3; } @Override /** * This override handles Group nodes that make use of advanced * graphics features not supported by SWF. Groups are translated into * SWF DefineSpriteTags and can be linked to a SymbolClass that will use * ActionScript to draw the advanced graphic features. * * @param node - the Group node * @return the PlaceObject3Tag definition that would place the Group on stage */ protected PlaceObject3Tag group(GroupNode node) { PlaceObject3Tag po3 = super.group(node); if (po3 != null && hasAdvancedGraphics(node)) advancedGraphics(node, po3.getCharacter(), SymbolClassType.SPRITE); return po3; } /** * Determines whether a node uses advanced graphics features which are not * supported by the SWF format. If so, we will need to generate * ActionScript code to instruct the player to draw the advanced features. * * @param node - the graphics node that may make use of advanced graphics * features * @return true if advanced graphics features are in use */ private boolean hasAdvancedGraphics(IMaskableNode node) { if (node.getMask() != null && (node.getMaskType() == MaskType.ALPHA || node.getMaskType() == MaskType.LUMINOSITY)) { return true; } else if (node instanceof GraphicContentNode) { GraphicContentNode graphicNode = (GraphicContentNode)node; if (graphicNode.blendMode.needsPixelBenderSupport()) return true; } return false; } /** * Some advanced graphics features are not supported by SWF. This helper * method generates ActionScript instructions to handle advanced features * such as alpha masks, luminosity masks, or pixel-bender based blend modes * (e.g. colordodge, colorburn, exclusion, softlight, hue, saturation, * color, and luminosity). * * @param node - an FXG node that uses advanced graphics features * @param symbol - the SWF symbol for this node * @param symbolClassType - determines the base type for the SymbolClass */ private void advancedGraphics(GraphicContentNode node, ICharacterTag symbol, SymbolClassType symbolClassType) { IMaskingNode maskNode = node.getMask(); // Create a new SymbolClass to map to this mask's // DefineSpriteTag (see below) String className = graphicClass.getClassName(); if (maskNode != null) className += "_Maskee"; className = createUniqueName(className); FXGSymbolClass symbolClass = new FXGSymbolClass(); symbolClass.setPackageName(packageName); symbolClass.setClassName(className); // Then record this SymbolClass with the top-level graphic // SymbolClass so that it will be associated with the FXG asset. graphicClass.addAdditionalSymbolClass(symbolClass); StringBuilder buf = new StringBuilder(512); buf.append("package ").append(packageName).append("\n"); buf.append("{\n\n"); // Determine Base Class String baseClassName = null; if (symbolClassType == SymbolClassType.SPRITE) { buf.append("import flash.display.Sprite;\n"); baseClassName = "Sprite"; } else { buf.append("import flash.display.Shape;\n"); baseClassName = "Shape"; } // Advanced BlendModes String blendModeClass = null; BlendMode blendmode = node.blendMode; if(blendmode != null && blendmode.needsPixelBenderSupport()) { blendModeClass = blendmode.getClassName(); addDependency(blendModeClass); //add dependency to this class buf.append("import ").append(blendModeClass).append(";\n\n"); } // Class Definition and Constructor buf.append("public class ").append(className).append(" extends ").append(baseClassName).append("\n"); buf.append("{\n"); buf.append(" public function ").append(className).append("()\n"); buf.append(" {\n"); buf.append(" super();\n"); buf.append(" this.cacheAsBitmap = true;\n"); // Alpha and Luminosity Masks if (maskNode != null) { int maskIndex = maskNode.getMaskIndex(); if (symbolClassType == SymbolClassType.SPRITE) buf.append(" this.mask = this.getChildAt(").append(maskIndex).append(");\n"); else buf.append(" this.mask = this.parent.getChildAt(").append(maskIndex).append(");\n"); } // BlendMode Shader if (blendModeClass != null) buf.append(" this.blendShader = new ").append(Multiname.getBaseNameForQName(blendModeClass)).append("();\n"); buf.append(" }\n"); // End constructor buf.append("}\n"); // End class buf.append("}\n"); // End package symbolClass.setGeneratedSource(buf.toString()); symbolClass.setSymbol(symbol); } //-------------------------------------------------------------------------- // // TextGraphic and RichText Code Generation // //-------------------------------------------------------------------------- @Override /** * RichText is not supported by SWF. This override uses a Sprite-based * ActionScript SymbolClass to create a spark.components.RichText instance * to draw the text. * * @param node - an FXG 2.0 RichText node * @return the PlaceObject3Tag definition that would place the associated * DefineSpriteTag on the stage */ protected PlaceObject3Tag richtext(RichTextNode node) { return flexText(node); } @Override /** * TextGraphic is not supported by SWF. This override uses a Sprite-bsaed * ActionScript SymbolClass to create a spark.components.RichText instance * to draw the text. * * @param node - an FXG 1.0 TextGraphic node * @return the PlaceObject3Tag definition that would place the associated * DefineSpriteTag on the stage */ protected PlaceObject3Tag text(TextGraphicNode node) { return flexText(node); } /** * Generates Flex specific ActionScript for FXG 1.0 <TextGraphic> or * FXG 2.0 <RichText> nodes. * * @param node - either a TextGraphicNode or RichTextNode. * @return the PlaceObject3Tag definition that would place the associated * DefineSpriteTag on the stage */ private PlaceObject3Tag flexText(GraphicContentNode node) { if (node instanceof ITextNode) { ITextNode textNode = ((ITextNode)node); // Create a new SymbolClass to map to this TextGraphic's // DefineSpriteTag (see below) String className = createUniqueName(graphicClass.getClassName() + "_Text"); FXGSymbolClass spriteSymbolClass = new FXGSymbolClass(); spriteSymbolClass.setPackageName(packageName); spriteSymbolClass.setClassName(className); // Then record this SymbolClass with the top-level graphic // SymbolClass so that it will be associated with the FXG asset. graphicClass.addAdditionalSymbolClass(spriteSymbolClass); // Create a DefineSpriteTag to hold this TextGraphic DefineSpriteTag textSprite = createDefineSpriteTag(className); PlaceObject3Tag po3 = PlaceObject3Tag(textSprite, node.createGraphicContext()); spriteStack.push(textSprite); //Add dependencies addDependency(packageFlashEvent); addDependency(packageTextLayoutFormat); addDependency(packageIFlexModuleFactory); addDependency(packageRichText); addDependency(packageSpriteVisualElement); StringBuilder buf = new StringBuilder(4096); buf.append("package ").append(packageName).append("\n"); buf.append("{\n\n"); buf.append("import flashx.textLayout.elements.*;\n"); buf.append("import mx.core.mx_internal;\n"); buf.append("import ").append(packageFlashEvent).append(";\n"); buf.append("import ").append(packageTextLayoutFormat).append(";\n"); buf.append("import ").append(packageIFlexModuleFactory).append(";\n"); buf.append("import ").append(packageRichText).append(";\n"); buf.append("import ").append(packageSpriteVisualElement).append(";\n"); buf.append("use namespace mx_internal;\n\n"); // Advanced BlendModes String blendModeClass = null; BlendMode blendmode = node.blendMode; if(blendmode != null && blendmode.needsPixelBenderSupport()) { blendModeClass = blendmode.getClassName(); addDependency(blendModeClass); //add dependency to this class buf.append("import ").append(blendModeClass).append(";\n\n"); } buf.append("public class ").append(className).append(" extends SpriteVisualElement\n"); buf.append("{\n"); buf.append(" public function ").append(className).append("()\n"); buf.append(" {\n"); buf.append(" super();\n"); buf.append(" this.nestedSpriteVisualElement = true;\n"); if (hasAdvancedGraphics(node)) buf.append(" this.cacheAsBitmap = true;\n"); // Alpha Masks IMaskingNode maskNode = node.getMask(); if (maskNode != null && (node.getMaskType() == MaskType.ALPHA || node.getMaskType() == MaskType.LUMINOSITY)) { int maskIndex = maskNode.getMaskIndex(); buf.append(" this.mask = this.parent.getChildAt(").append(maskIndex).append(");\n"); } // BlendMode Shader if (blendModeClass != null) buf.append(" this.blendShader = new ").append(Multiname.getBaseNameForQName(blendModeClass)).append("();\n"); // Generate Text buf.append(" createText();\n"); buf.append(" }\n"); buf.append("\n"); buf.append(" private var _richTextComponent:RichText;\n\n"); buf.append(" private function createText():void\n"); buf.append(" {\n"); SourceContext textSource = generateRichText(textNode); if (textSource.functionBuffer != null) buf.append(textSource.functionBuffer.toString()); buf.append(" }\n"); if (textSource.classBuffer != null) buf.append(textSource.classBuffer.toString()); buf.append(generateModuleFactoryOverride("_richTextComponent")); buf.append("}\n"); // End class buf.append("}\n"); // End package spriteSymbolClass.setGeneratedSource(buf.toString()); spriteSymbolClass.setSymbol(textSprite); spriteStack.pop(); return po3; } return null; } /** * Generates a unique name by appending a random number to the given base * name. * * @param baseName the base of the generated name * @return the unique name */ private String createUniqueName(String baseName) { String suffix; if (nameCounter.containsKey(baseName)) { int counterValue = nameCounter.get(baseName) + 1; nameCounter.put(baseName, counterValue); suffix = String.valueOf(counterValue); } else { suffix = "0"; nameCounter.put(baseName, 0); } return baseName + '_' + suffix; } //-------------------------------------------------------------------------- // // Methods for ActionScript Generation // //-------------------------------------------------------------------------- /** * Generates ActionScript code to initialize a new instance of * RichText for a given FXG TextGraphic node, its attributes, and any * child nodes. * * @param node The TextGraphic node to process. * * @return Returns the code generation buffers (for the function and * potentially class scopes). */ private SourceContext generateRichText(ITextNode textNode) { // Generate ActionScript equivalent of tag markup. We use 1024 // characters of generated code is sufficient for the initial size of // the function scope buffer. We use 0 characters for the class scope // buffer until we encounter the special case of: // <img source="@Embed('...')"> // which involves generating class member variables to embed images. SourceContext srcContext = new SourceContext(1024, 0); StringBuilder buf = srcContext.functionBuffer; Variables varContext = new Variables(); IClassDefinition definition = (IClassDefinition)getDefinition(packageRichText); if(definition != null) { varContext.setVar(definition, NodeType.RICHTEXT); String elementVar = varContext.elementVar; generateTextVariable(textNode, srcContext, varContext); buf.append(" _richTextComponent = ").append(elementVar).append(";\r\n"); buf.append(" addChild(").append(elementVar).append(");\r\n"); buf.append(" var addHandler:Function = function(event:Event):void\r\n"); buf.append(" {\r\n"); buf.append(" removeEventListener(Event.ADDED_TO_STAGE, addHandler);\r\n\r\n"); buf.append(" // If we don't have a module factory by now then use the root\r\n"); buf.append(" if (moduleFactory == null && root is IFlexModuleFactory)\r\n"); buf.append(" moduleFactory = IFlexModuleFactory(root);\r\n"); buf.append(" };\r\n"); buf.append(" addEventListener(Event.ADDED_TO_STAGE, addHandler);\r\n"); } return srcContext; } /** * Create a module factory override so we do not try to use a RichText's styles until we * have a module factory. The module factory will tell us which style manager to use. * * @param elementVar * @return */ private String generateModuleFactoryOverride(String elementVar) { StringBuilder buf = new StringBuilder(1024); buf.append("\r\n /**\r\n"); buf.append(" * @private\r\n"); buf.append(" * Create a module factory override so we do not try to use a RichText's\r\n"); buf.append(" * styles until we have a module factory. The module factory will tell us\r\n"); buf.append(" * which style manager to use.\r\n"); buf.append(" */\r\n"); buf.append(" override public function set moduleFactory(factory:IFlexModuleFactory):void\r\n"); buf.append(" {\r\n"); //TODO: This line causes a stack overflow, even though the super class has the same setter. //Bug filed against falcon for this: <will file a bug for this as soon as I come up with a basic repro case> //Uncomment this when the bug is fixed. //buf.append(" super.moduleFactory = factory;\r\n"); buf.append(" ").append(elementVar).append(".regenerateStyleCache(true);\r\n"); buf.append(" ").append(elementVar).append(".styleChanged(null);\r\n"); buf.append(" ").append(elementVar).append(".stylesInitialized();\r\n"); buf.append(" ").append(elementVar).append(".validateProperties();\r\n"); buf.append(" ").append(elementVar).append(".validateSize();\r\n"); buf.append(" ").append(elementVar).append(".setLayoutBoundsSize(NaN, NaN);\r\n"); buf.append(" ").append(elementVar).append(".validateDisplayList();\r\n"); buf.append(" invalidateSize();\r\n"); buf.append(" }\r\n"); return buf.toString(); } /** * Generates ActionScript to initialize a variable for a given text node, * populates any specified attributes as properties or styles, and * recursively processes any text child nodes. * @param textNode - the current text node * @param srcContext - the current code generation buffers * @param varContext - the current generated ActionScript variable context */ private void generateTextVariable(ITextNode textNode, SourceContext srcContext, Variables varContext) { StringBuilder buf = srcContext.functionBuffer; Map<String, String> attributes = textNode.getTextAttributes(); List<ITextNode> children = textNode.getTextChildren(); String currentVar = varContext.elementVar; String contentVar = varContext.contentVar; String parentClass = varContext.elementClass; String parentChildrenVar = varContext.elementChildrenVar; IClassDefinition type = varContext.type; if (!varContext.varDeclared) { // var someElement:SomeElement = new SomeElement(); buf.append(" var ").append(currentVar).append(":").append(parentClass).append(" = new ").append(parentClass).append("();\r\n"); // var someContent:Array = []; if (contentVar != null) buf.append(" var ").append(contentVar).append(":Array = [];\r\n"); } else { // someElement = new SomeElement(); buf.append(" ").append(currentVar).append(" = new ").append(parentClass).append("();\r\n"); // someContent = []; if (contentVar != null) buf.append(" ").append(contentVar).append(" = [];\r\n"); } // Attributes generateAttributes(textNode, type, attributes, srcContext, currentVar); // Properties // Note: We process RichTextNode properties after content has been assigned. if (!(textNode instanceof RichTextNode)) generateProperties(srcContext, textNode, currentVar, varContext); // Child Nodes if (children != null && children.size() > 0) { Iterator<ITextNode> iter = children.iterator(); while (iter.hasNext()) { String elementVar = null; ITextNode child = iter.next(); IClassDefinition definition = null; // FXG 2.0 if (child instanceof RichTextNode) { definition = (IClassDefinition)getDefinition(packageRichText); if(definition != null) { varContext.setVar(definition, NodeType.RICHTEXT); elementVar = varContext.elementVar; generateTextVariable(child, srcContext, varContext); buf.append(" ").append(contentVar).append(".push(").append(elementVar).append(");\r\n"); } } else if (child instanceof ParagraphNode) { definition = (IClassDefinition)getDefinition(packageParagraphElement); if(definition != null) { varContext.setVar(definition, NodeType.PARAGRAPH); elementVar = varContext.elementVar; generateTextVariable(child, srcContext, varContext); buf.append(" ").append(contentVar).append(".push(").append(elementVar).append(");\r\n"); } } else if (child instanceof SpanNode) { definition = (IClassDefinition)getDefinition(packageSpanElement); if(definition != null) { varContext.setVar(definition, NodeType.SPAN); elementVar = varContext.elementVar; generateTextVariable(child, srcContext, varContext); buf.append(" ").append(contentVar).append(".push(").append(elementVar).append(");\r\n"); } } else if (child instanceof DivNode) { definition = (IClassDefinition)getDefinition(packageDivElement); if(definition != null) { varContext.setVar(definition, NodeType.DIV); elementVar = varContext.elementVar; generateTextVariable(child, srcContext, varContext); buf.append(" ").append(contentVar).append(".push(").append(elementVar).append(");\r\n"); } } else if (child instanceof CDATANode) { // someContent.push("some text"); String text = formatString(((CDATANode)child).content); buf.append(" ").append(contentVar).append(".push(").append(text).append(");\r\n"); } else if (child instanceof BRNode) { addDependency(packageBreakElement); buf.append(" ").append(contentVar).append(".push(new BreakElement());\r\n"); } else if (child instanceof ImgNode) { definition = (IClassDefinition)getDefinition(packageImgElement); if(definition != null) { varContext.setVar(definition,NodeType.IMG); elementVar = varContext.elementVar; generateTextVariable(child, srcContext, varContext); buf.append(" ").append(contentVar).append(".push(").append(elementVar).append(");\r\n"); } } else if (child instanceof LinkNode) { definition = (IClassDefinition)getDefinition(packageLinkElement); if(definition != null) { varContext.setVar(definition,NodeType.LINK); elementVar = varContext.elementVar; generateTextVariable(child, srcContext, varContext); buf.append(" ").append(contentVar).append(".push(").append(elementVar).append(");\r\n"); } } else if (child instanceof TabNode) { definition = (IClassDefinition)getDefinition(packageTabElement); if(definition != null) { varContext.setVar(definition,NodeType.TAB); elementVar = varContext.elementVar; generateTextVariable(child, srcContext, varContext); buf.append(" ").append(contentVar).append(".push(").append(elementVar).append(");\r\n"); } } else if (child instanceof TCYNode) { definition = (IClassDefinition)getDefinition(packageTCYElement); if(definition != null) { varContext.setVar(definition,NodeType.TCY); elementVar = varContext.elementVar; generateTextVariable(child, srcContext, varContext); buf.append(" ").append(contentVar).append(".push(").append(elementVar).append(");\r\n"); } } // FXG 1.0 else if (child instanceof TextGraphicNode) { definition = (IClassDefinition)getDefinition(packageRichText); if(definition != null) { varContext.setVar(definition,NodeType.RICHTEXT); elementVar = varContext.elementVar; generateTextVariable(child, srcContext, varContext); buf.append(" ").append(contentVar).append(".push(").append(elementVar).append(");\r\n"); } } else { //Should not happen. Ignore this. } } } // e.g. someElement.mxmlChildren = someContent; if (parentChildrenVar != null && contentVar != null) buf.append(" ").append(currentVar).append(".").append(parentChildrenVar).append(" = ").append(contentVar).append(";\r\n"); // RichText is a special case whose properties must be set after content // is assigned and the textFlow has been populated. if (textNode instanceof RichTextNode) generateProperties(srcContext, textNode, currentVar, varContext); } /** * Generates ActionScript code for child property nodes that represent * complex property values. It also generates the property assignment * statement. * * @param srcContext - the ActionScript source code generation buffers. * @param parentNode - the parent ITextNode to process for properties * @param parentVar - the parent variable that declares the properties * @param varContext - the current context for generating variables in * ActionScript code */ private void generateProperties(SourceContext srcContext, ITextNode parentNode, String parentVar, Variables varContext) { Map<String, ITextNode> properties = parentNode.getTextProperties(); if (properties != null) { StringBuilder buf = srcContext.functionBuffer; for (Map.Entry<String, ITextNode> entry : properties.entrySet()) { String propertyName = entry.getKey(); ITextNode node = entry.getValue(); if (node instanceof TextLayoutFormatNode) { IClassDefinition definition = (IClassDefinition)getDefinition(packageTextLayoutFormat); if(definition != null) { varContext.setVar(definition, NodeType.TEXT_LAYOUT_FORMAT); generateTextVariable(node, srcContext, varContext); // RichText does not support setting text layout formatting // at the top level so we must update its textFlow instead. if (parentNode instanceof RichTextNode) { buf.append(" ").append(parentVar).append(".textFlow.").append( propertyName).append(" = ").append(varContext.elementVar).append(";\r\n"); } else { buf.append(" ").append(parentVar).append(".").append( propertyName).append(" = ").append(varContext.elementVar).append(";\r\n"); } } } } } } /** * Converts the attributes specified on an FXG node into ActionScript * property initializers. * * @param type The ActionScript type for this node. * @param attributes The Map of attributes specified on this node. * @param srcContext The source code generation buffers. * @param variableName The ActionScript variable name representing the * instance of this node. */ private void generateAttributes(ITextNode node, IClassDefinition type, Map<String, String> attributes, SourceContext srcContext, String variableName) { if (attributes != null) { StringBuilder buf = srcContext.functionBuffer; // Handle <img source="@Embed('xyz.jpg')" /> as a special case if (node instanceof ImgNode) { String imgSource = attributes.get("source"); imgSource = parseSource(imgSource); if (imgSource != null) { // Resolve relative file path File f = new File(imgSource); if (!f.isAbsolute() && resourceResolver != null) { String resolvedPath = resourceResolver.resolve(imgSource); if (resolvedPath != null) imgSource = resolvedPath; } imgSource = imgSource.replace('\\', '/'); // Generate [Embed] for the image. if (srcContext.classBuffer == null) srcContext.classBuffer = new StringBuilder(128); StringBuilder classBuf = srcContext.classBuffer; String imgVar = createUniqueName("img"); classBuf.append("\n"); classBuf.append(" [Embed(source=\"").append(imgSource).append("\")]\n"); classBuf.append(" private static var ").append(imgVar).append(":Class;\n"); // Generate source attribute assignment buf.append(" ").append(variableName).append(".source = ").append(imgVar).append(";\n"); attributes.remove("source"); } } for (Map.Entry<String, String> entry : attributes.entrySet()) { String attribName = entry.getKey(); String attribValue = entry.getValue(); String thisAttrib = null; IDefinition propertyDefinition = ((FlexProject)project).resolveProperty(type, attribName); if (propertyDefinition != null) //is it a property? { ITypeDefinition propertyType = propertyDefinition.resolveType(project); if (propertyType.isInstanceOf(stringType, project) || propertyType == objectType || propertyType == anyType) { thisAttrib = attribName + " = \"" + attribValue + "\""; } else { thisAttrib = attribName + " = " + attribValue; } } else if (((FlexProject)project).resolveStyle(type, attribName) != null) //is it a style? { thisAttrib = "setStyle(\"" + attribName + "\", \"" + attribValue + "\")"; } else { problems.add(new FXGUndefinedPropertyProblem(attribName)); continue; } if (thisAttrib != null) buf.append(" " + variableName + '.' + thisAttrib + ";\r\n"); } } } /** * Quotes a String and escapes any special characters so that it can be * used as a literal ActionScript value. * * @param content the raw String * @return a Quoted String suitable for use as an ActionScript value. */ private String formatString(String content) { if (content != null) return StringUtils.formatString(content); return content; } @Override public ITypeDefinition[] getDependencies() { return dependencies.values().toArray(new ITypeDefinition[0]); } //-------------------------------------------------------------------------- // // Helper Classes for ActionScript Source Code Generation // //-------------------------------------------------------------------------- /** * An enumeration that specifies the base type of a SymbolClass. */ private static enum SymbolClassType { SPRITE, SHAPE } /** * Text node type enumeration. */ private static enum NodeType { DIV, FORMAT, IMG, LINK, PARAGRAPH, RICHTEXT, SPAN, TAB, TCY, TEXT_LAYOUT_FORMAT } /** * Provides a context to the current ActionScript generation buffers. * Multiple buffers allow code to be generated in different parts of an * ActionScript file, such as generating a class scope member while * generating a function scope. */ private static class SourceContext { /** * Constructor. * @param functionSize - Initial function buffer size. * @param classSize - Initial class buffer size. */ private SourceContext(int functionSize, int classSize) { if (functionSize > 0) functionBuffer = new StringBuilder(functionSize); if (classSize > 0) classBuffer = new StringBuilder(classSize); } private StringBuilder functionBuffer; private StringBuilder classBuffer; } /** * Provides a context of variables in use for ActionScript source * generation of a text node and its children. */ private static class Variables { private Variable divVar; private Variable formatVar; private Variable imgVar; private Variable linkVar; private Variable paragraphVar; private Variable richTextVar; private Variable spanVar; private Variable tabVar; private Variable tcyVar; private Variable textLayoutFormatVar; private Variables() { } private void setVar(IClassDefinition type, NodeType nodeType) { this.type = type; Variable var = getVar(nodeType); if (var != null) { var.count++; if (!var.reusableVar) { varDeclared = false; elementVar = var.elementVar + var.count; contentVar = var.contentVar + var.count; } else { varDeclared = var.count > 1; elementVar = var.elementVar; contentVar = var.contentVar; } elementClass = var.elementClass; elementChildrenVar = var.elementChildrenVar; } } private Variable getVar(NodeType nodeType) { switch (nodeType) { case DIV: { if (divVar == null) divVar = new Variable("DivElement", "divElement", "divContent", "mxmlChildren", false); return divVar; } case FORMAT: { if (formatVar == null) formatVar = new Variable("TextLayoutFormat", "formatElement", null, null, false); return formatVar; } case IMG: { if (imgVar == null) imgVar = new Variable("InlineGraphicElement", "imgElement", null, null, true); return imgVar; } case LINK: { if (linkVar == null) linkVar = new Variable("LinkElement", "linkElement", "linkContent", "mxmlChildren", true); return linkVar; } case PARAGRAPH: { if (paragraphVar == null) paragraphVar = new Variable("ParagraphElement", "paragraphElement", "paragraphContent", "mxmlChildren", true); return paragraphVar; } case RICHTEXT: { if (richTextVar == null) richTextVar = new Variable("RichText", "textElement", "textContent", "content", true); return richTextVar; } case SPAN: { if (spanVar == null) spanVar = new Variable("SpanElement", "spanElement", "spanContent", "mxmlChildren", true); return spanVar; } case TAB: { if (tabVar == null) tabVar = new Variable("TabElement", "tabElement", null, null, true); return tabVar; } case TCY: { if (tcyVar == null) tcyVar = new Variable("TCYElement", "tcyElement", "tcyContent", "mxmlChildren", true); return tcyVar; } case TEXT_LAYOUT_FORMAT: { if (textLayoutFormatVar == null) textLayoutFormatVar = new Variable("TextLayoutFormat", "tlfElement", null, null, true); return textLayoutFormatVar; } } return null; } private IClassDefinition type; private boolean varDeclared; private String elementClass; private String elementVar; private String contentVar; private String elementChildrenVar; } /** * The context for an individual variable. */ private static class Variable { private Variable(String elementClass, String elementVar, String contentVar, String elementChildrenVar, boolean reusableVar) { this.elementClass = elementClass; this.elementVar = elementVar; this.contentVar = contentVar; this.elementChildrenVar = elementChildrenVar; this.reusableVar = reusableVar; } private int count; private boolean reusableVar; private String elementClass; private String elementVar; private String contentVar; private String elementChildrenVar; } }