/*
*
* 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 com.adobe.internal.fxg.dom;
import java.util.ArrayList;
import java.util.List;
import static com.adobe.fxg.FXGConstants.*;
import com.adobe.fxg.FXGException;
import com.adobe.fxg.FXGVersion;
import com.adobe.fxg.dom.FXGNode;
import com.adobe.fxg.util.FXGLog;
import com.adobe.fxg.util.FXGLogger;
import com.adobe.internal.fxg.dom.transforms.ColorTransformNode;
import com.adobe.internal.fxg.dom.transforms.MatrixNode;
import com.adobe.internal.fxg.dom.types.BlendMode;
import com.adobe.internal.fxg.dom.types.MaskType;
import com.adobe.internal.fxg.types.FXGMatrix;
/**
* Base class for all nodes that present graphic content or represent groups
* of graphic content. Children inherit parent context information for
* transforms, blend modes and masks.
*
* @author Peter Farland
*/
public abstract class GraphicContentNode extends AbstractFXGNode
implements MaskableNode
{
//--------------------------------------------------------------------------
//
// Attributes
//
//--------------------------------------------------------------------------
//------------
// id
//------------
protected String id = "undefined";
/**
* An id attribute provides a well defined name to a text node.
*
* @return the id
*/
public String getId()
{
return id;
}
/**
* Sets the node id.
*
* @param value - the node id as a String.
*/
public void setId(String value)
{
id = value;
}
/** The visible. */
public boolean visible = true;
/** The x. */
public double x = 0.0;
/** The y. */
public double y = 0.0;
/** The scale x. */
public double scaleX = 1.0;
/** The scale y. */
public double scaleY = 1.0;
/** The rotation. */
public double rotation = 0.0;
/** The alpha. */
public double alpha = 1.0;
/** The blend mode. */
public BlendMode blendMode = BlendMode.AUTO;
/** The mask type. */
public MaskType maskType = MaskType.CLIP;
/** The luminosity clip. */
public boolean luminosityClip = false;
/** The luminosity invert. */
public boolean luminosityInvert = false;
protected boolean translateSet;
protected boolean scaleSet;
protected boolean rotationSet;
protected boolean alphaSet;
protected boolean maskTypeSet;
//is part of clip mask
/** The is partof clip mask. */
public boolean isPartofClipMask = false;
//--------------------------------------------------------------------------
//
// Children
//
//--------------------------------------------------------------------------
/** The filters. */
public List<FilterNode> filters;
/** The mask. */
public MaskingNode mask;
/** The matrix. */
public MatrixNode matrix;
/** The color transform. */
public ColorTransformNode colorTransform;
//--------------------------------------------------------------------------
//
// FXGNode Implementation
//
//--------------------------------------------------------------------------
/**
* Adds an FXG child node to this node.
* <p>
* Graphic content nodes support child property nodes <filter>,
* <mask>, <matrix>, or <colorTransform>.
* </p>
*
* @param child - a child FXG node to be added to this node.
*
* @throws FXGException if the child is not supported by this node.
*/
@Override
public void addChild(FXGNode child)
{
if (child instanceof FilterNode)
{
if (filters == null)
filters = new ArrayList<FilterNode>();
filters.add((FilterNode)child);
}
else if (child instanceof MaskPropertyNode)
{
mask = ((MaskPropertyNode)child).mask;
if (mask instanceof GraphicContentNode)
{
((GraphicContentNode)mask).setParentGraphicContext(createGraphicContext());
}
}
else if (child instanceof MatrixNode)
{
if (translateSet || scaleSet || rotationSet)
//Exception:Cannot supply a matrix child if transformation attributes were provided
throw new FXGException(child.getStartLine(), child.getStartColumn(), "InvalidChildMatrixNode");
matrix = (MatrixNode)child;
}
else if (child instanceof ColorTransformNode)
{
if (alphaSet)
//Exception:Cannot supply a colorTransform child if alpha attribute was provided.
throw new FXGException(child.getStartLine(), child.getStartColumn(), "InvalidChildColorTransformNode");
colorTransform = (ColorTransformNode)child;
}
else
{
super.addChild(child);
}
}
/**
* Sets an FXG attribute on this FXG node. Delegates to the parent
* class to process attributes that are not in the list below.
* <p>
* Graphic content nodes support the following attributes:
* <ul>
* <li><b>rotation</b> (ASDegrees): Defaults to 0.</li>
* <li><b>scaleX</b> (Number): Defaults to 1.</li>
* <li><b>scaleY</b> (Number): Defaults to 1.</li>
* <li><b>x</b> (Number): The horizontal placement of the left edge of the
* text box, relative to the parent grouping element. Defaults to 0.</li>
* <li><b>y</b> (Number): The vertical placement of the top edge of the
* text box, relative to the parent grouping element. Defaults to 0.</li>
* <li><b>blendMode</b> (String): [normal, add, alpha, darken, difference,
* erase, hardlight, invert, layer, lighten, multiply, normal, subtract,
* screen, overlay, auto, colordodge, colorburn, exclusion, softlight,
* hue, saturation, color, luminosity] Defaults to auto.</li>
* <li><b>alpha</b> (ASAlpha): Defaults to 1.</li>
* <li><b>maskType</b> (String):[clip, alpha]: Defaults to clip.</li>
* <li><b>visible</b> (Boolean): Whether or not the text box is visible.
* Defaults to true.</li>
* </ul>
* </p>
* <p>
* Graphic content nodes also support an id attribute.
* </p>
*
* @param name - the unqualified attribute name
* @param value - the attribute value
*
* @throws FXGException if a value is out of the valid range.
* @see com.adobe.internal.fxg.dom.AbstractFXGNode#setAttribute(java.lang.String, java.lang.String)
*/
@Override
public void setAttribute(String name, String value)
{
if (FXG_X_ATTRIBUTE.equals(name))
{
x = DOMParserHelper.parseDouble(this, value, name);
translateSet = true;
}
else if (FXG_Y_ATTRIBUTE.equals(name))
{
y = DOMParserHelper.parseDouble(this, value, name);
translateSet = true;
}
else if (FXG_ROTATION_ATTRIBUTE.equals(name))
{
rotation = DOMParserHelper.parseDouble(this, value, name);
rotationSet = true;
}
else if (FXG_SCALEX_ATTRIBUTE.equals(name))
{
scaleX = DOMParserHelper.parseDouble(this, value, name);
scaleSet = true;
}
else if (FXG_SCALEY_ATTRIBUTE.equals(name))
{
scaleY = DOMParserHelper.parseDouble(this, value, name);
scaleSet = true;
}
else if (FXG_ALPHA_ATTRIBUTE.equals(name))
{
alpha = DOMParserHelper.parseDouble(this, value, name, ALPHA_MIN_INCLUSIVE, ALPHA_MAX_INCLUSIVE, alpha);
alphaSet = true;
}
else if (FXG_BLENDMODE_ATTRIBUTE.equals(name))
{
blendMode = parseBlendMode(this, value, blendMode);
}
else if (FXG_VISIBLE_ATTRIBUTE.equals(name))
{
visible = DOMParserHelper.parseBoolean(this, value, name);
}
else if (FXG_ID_ATTRIBUTE.equals(name))
{
id = value;
}
else if (FXG_MASKTYPE_ATTRIBUTE.equals(name))
{
maskType = DOMParserHelper.parseMaskType(this, value, name, maskType);
// Luminosity mask is not supported by Flex on Mobile in
// FXG 2.0.
if (isForMobile() && (maskType == MaskType.LUMINOSITY))
{
FXGLog.getLogger().log(FXGLogger.WARN, "MobileUnsupportedLuminosityMask", null,
((AbstractFXGNode)this).getDocumentName(), startLine, startColumn);
maskTypeSet = false;
}
else
{
maskTypeSet = true;
}
}
else if (getFileVersion().equalTo(FXGVersion.v1_0))
{
// Rest of the attributes are not supported by FXG 1.0
// Exception:Attribute {0} not supported by node {1}.
throw new FXGException(getStartLine(), getStartColumn(), "InvalidNodeAttribute", name, getNodeName());
}
else if (FXG_LUMINOSITYCLIP_ATTRIBUTE.equals(name))
{
luminosityClip = DOMParserHelper.parseBoolean(this, value, name);
}
else if (FXG_LUMINOSITYINVERT_ATTRIBUTE.equals(name))
{
luminosityInvert = DOMParserHelper.parseBoolean(this, value, name);
}
else
{
super.setAttribute(name, value);
}
}
//--------------------------------------------------------------------------
//
// MaskableNode Implementation
//
//--------------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public MaskingNode getMask()
{
return mask;
}
/**
* {@inheritDoc}
*/
public MaskType getMaskType()
{
return maskType;
}
/**
* {@inheritDoc}
*/
public boolean getLuminosityClip()
{
return luminosityClip;
}
/**
* {@inheritDoc}
*/
public boolean getLuminosityInvert()
{
return luminosityInvert;
}
//--------------------------------------------------------------------------
//
// Helper Methods
//
//--------------------------------------------------------------------------
private GraphicContext parentGraphicContext;
/**
* Creates the graphic context.
*
* @return the graphic context
*/
public GraphicContext createGraphicContext()
{
GraphicContext graphicContext = new GraphicContext();
if (parentGraphicContext != null)
graphicContext.scalingGrid = parentGraphicContext.scalingGrid;
FXGMatrix transform = graphicContext.getTransform();
if (matrix != null)
{
FXGMatrix t = new FXGMatrix(matrix);
transform.concat(t);
}
else
{
if (scaleSet)
transform.scale(scaleX, scaleY);
if (rotationSet)
transform.rotate(rotation);
if (translateSet)
transform.translate(x, y);
}
if (colorTransform != null)
{
graphicContext.colorTransform = colorTransform;
}
else if (alphaSet)
{
if (graphicContext.colorTransform == null)
graphicContext.colorTransform = new ColorTransformNode();
graphicContext.colorTransform.alphaMultiplier = alpha;
}
graphicContext.blendMode = blendMode;
if (filters != null)
{
graphicContext.addFilters(filters);
}
if (maskTypeSet)
graphicContext.maskType = maskType;
else if (parentGraphicContext != null)
graphicContext.maskType = parentGraphicContext.maskType;
return graphicContext;
}
/**
* Sets the parent graphic context.
*
* @param context the new parent graphic context
*/
public void setParentGraphicContext(GraphicContext context)
{
parentGraphicContext = context;
}
/**
* Convert an FXG String value to a BlendMode enumeration.
*
* @param node the FXG node
* @param value the FXG attribute String value
* @return the matching BlendMode
* @throws FXGException if the String did not match a known
* BlendMode.
*/
protected BlendMode parseBlendMode(FXGNode node, String value, BlendMode defMode)
{
if (FXG_BLENDMODE_ADD_VALUE.equals(value))
{
return BlendMode.ADD;
}
else if (FXG_BLENDMODE_ALPHA_VALUE.equals(value))
{
return BlendMode.ALPHA;
}
else if (FXG_BLENDMODE_DARKEN_VALUE.equals(value))
{
return BlendMode.DARKEN;
}
else if (FXG_BLENDMODE_DIFFERENCE_VALUE.equals(value))
{
return BlendMode.DIFFERENCE;
}
else if (FXG_BLENDMODE_ERASE_VALUE.equals(value))
{
return BlendMode.ERASE;
}
else if (FXG_BLENDMODE_HARDLIGHT_VALUE.equals(value))
{
return BlendMode.HARDLIGHT;
}
else if (FXG_BLENDMODE_INVERT_VALUE.equals(value))
{
return BlendMode.INVERT;
}
else if (FXG_BLENDMODE_LAYER_VALUE.equals(value))
{
return BlendMode.LAYER;
}
else if (FXG_BLENDMODE_LIGHTEN_VALUE.equals(value))
{
return BlendMode.LIGHTEN;
}
else if (FXG_BLENDMODE_MULTIPLY_VALUE.equals(value))
{
return BlendMode.MULTIPLY;
}
else if (FXG_BLENDMODE_NORMAL_VALUE.equals(value))
{
return BlendMode.NORMAL;
}
else if (FXG_BLENDMODE_OVERLAY_VALUE.equals(value))
{
return BlendMode.OVERLAY;
}
else if (FXG_BLENDMODE_SCREEN_VALUE.equals(value))
{
return BlendMode.SCREEN;
}
else if (FXG_BLENDMODE_SUBTRACT_VALUE.equals(value))
{
return BlendMode.SUBTRACT;
}
else if (getFileVersion().equalTo(FXGVersion.v1_0))
{
// Rest of the blend modes are unknown for FXG 1.0
//Exception:Unknown blend mode: {0}.
throw new FXGException(getStartLine(), getStartColumn(), "UnknownBlendMode", value);
}
else if (isForMobile() && getCompilerVersion().equalTo(FXGVersion.v2_0)
&& (FXG_BLENDMODE_COLORDOGE_VALUE.equals(value) ||
FXG_BLENDMODE_COLORBURN_VALUE.equals(value) ||
FXG_BLENDMODE_EXCLUSION_VALUE.equals(value) ||
FXG_BLENDMODE_SOFTLIGHT_VALUE.equals(value) ||
FXG_BLENDMODE_HUE_VALUE.equals(value) ||
FXG_BLENDMODE_SATURATION_VALUE.equals(value) ||
FXG_BLENDMODE_COLOR_VALUE.equals(value) ||
FXG_BLENDMODE_LUMINOSITY_VALUE.equals(value)))
{
// The following pixel-bender based blend modes are not supported by FXG 2.0 on Mobile.
// Log a warning and ignore the blend modes.
FXGLog.getLogger().log(FXGLogger.WARN, "MobileUnsupportedPBBlendMode", null,
((AbstractFXGNode)node).getDocumentName(), node.getStartLine(), node.getStartColumn(), value);
return BlendMode.NORMAL;
}
else if (FXG_BLENDMODE_COLORDOGE_VALUE.equals(value))
{
return BlendMode.COLORDODGE;
}
else if (FXG_BLENDMODE_COLORBURN_VALUE.equals(value))
{
return BlendMode.COLORBURN;
}
else if (FXG_BLENDMODE_EXCLUSION_VALUE.equals(value))
{
return BlendMode.EXCLUSION;
}
else if (FXG_BLENDMODE_SOFTLIGHT_VALUE.equals(value))
{
return BlendMode.SOFTLIGHT;
}
else if (FXG_BLENDMODE_HUE_VALUE.equals(value))
{
return BlendMode.HUE;
}
else if (FXG_BLENDMODE_SATURATION_VALUE.equals(value))
{
return BlendMode.SATURATION;
}
else if (FXG_BLENDMODE_COLOR_VALUE.equals(value))
{
return BlendMode.COLOR;
}
else if (FXG_BLENDMODE_LUMINOSITY_VALUE.equals(value))
{
return BlendMode.LUMINOSITY;
}
else if (FXG_BLENDMODE_AUTO_VALUE.equals(value))
{
return BlendMode.AUTO;
}
else
{
if (isVersionGreaterThanCompiler())
{
// Warning: Minor version of this FXG file is greater than minor
// version supported by this compiler. Log a warning for an unknown
// blend mode.
FXGLog.getLogger().log(FXGLogger.WARN, "UnknownBlendMode", null, getDocumentName(), startLine, startColumn, value);
}
else
{
//Exception:Unknown blend mode: {0} for FXGVersion 2.0.
throw new FXGException(getStartLine(), getStartColumn(), "UnknownBlendMode", value);
}
}
return defMode;
}
/**
* Convert discreet transform attributes to child matrix. This allows
* concatenation of another matrix.
*/
public void convertTransformAttrToMatrix()
{
try
{
MatrixNode matrixNode = MatrixNode.class.newInstance();
// Convert discreet transform attributes to FXGMatrix.
FXGMatrix matrix = FXGMatrix.convertToMatrix(scaleX, scaleY, rotation, x, y);
// Set matrix attributes to FXGMatrix values.
matrix.setMatrixNodeValue(matrixNode);
// Reset all discreet transform attributes since matrix
// and discreet transform attributes cannot coexist.
resetTransformAttr();
// Add child matrix to the node.
this.addChild(matrixNode);
}
catch (Throwable t)
{
throw new FXGException(mask.getStartLine(), mask.getStartColumn(), "InvalidChildMatrixNode", t);
}
}
/**
* Reset discreet transform attributes to their default value. This allows
* child matrix can be set instead.
*/
private void resetTransformAttr()
{
x = 0.0;
y = 0.0;
scaleX = 1.0;
scaleY = 1.0;
rotation = 0.0;
translateSet = false;
scaleSet = false;
rotationSet = false;
}
}