/* * $RCSfile: GradientElement.java,v $ * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.perseus.model; import com.sun.perseus.util.SVGConstants; import com.sun.perseus.j2d.LinearGradientPaintDef; import org.w3c.dom.DOMException; import org.w3c.dom.svg.SVGRGBColor; import org.w3c.dom.svg.SVGMatrix; import com.sun.perseus.j2d.RGB; import com.sun.perseus.j2d.PaintDef; import com.sun.perseus.j2d.PaintTarget; import com.sun.perseus.j2d.PaintServer; import com.sun.perseus.j2d.Transform; /** * <code>GradientElement</code> abstract class is a helper base * class for <code>LinearGradient</code> and <code>RadialGradient</code>. * <br /> * * @version $Id: GradientElement.java,v 1.10 2006/06/29 10:47:31 ln156897 Exp $ */ public abstract class GradientElement extends PaintElement { /** * Computed PaintDef, in userSpaceOnUse. */ PaintDef computedPaint; /** * The last computed GradientColorMap fractions */ float[] lastColorMapFractions; /** * The last computed GradientColorMap colors */ int[] lastColorMapRGBA; /** * The spread method. Possible values are <code>pad</code>, <code>reflect</code>, <code>repeat</code>. */ String spreadMethod = SVGConstants.SVG_PAD_VALUE; /** * Addintional paint transform, like the gradientTransform on gradients. */ Transform transform = new Transform(null); /** * Constructor. * * @param ownerDocument this element's owner <code>DocumentNode</code> */ public GradientElement(final DocumentNode ownerDocument) { super(ownerDocument); isObjectBBox = true; } /** * @param newTransform this node's new transform. Note that the * input value is used by reference. */ public void setTransform(final Transform newTransform) { if (equal(newTransform, transform)) { return; } modifyingNode(); this.transform = newTransform; onPaintChange(); modifiedNode(); } /** * @return this node's transform */ public Transform getTransform() { return transform; } /** * Sets the isObjectBBox state. * * @param newIsObjectBBox the new value for the isObjectBBox * property. */ public void setIsObjectBBox(final boolean newIsObjectBBox) { if (newIsObjectBBox == isObjectBBox) { return; } isObjectBBox = newIsObjectBBox; onPaintChange(); } /** * Sets the spreadMethod state. * * @param value the new value for the spreadMethod * property. */ public void setSpreadMethod(final String value) { if (spreadMethod == value) { return; } spreadMethod = value; onPaintChange(); } /** * Supported traits: transform. * * @param traitName the name of the trait which the element may support. * @return true if this element supports the given trait in one of the * trait accessor methods. */ boolean supportsTrait(final String traitName) { if (SVGConstants.SVG_GRADIENT_TRANSFORM_ATTRIBUTE == traitName || SVGConstants.SVG_GRADIENT_UNITS_ATTRIBUTE == traitName || SVGConstants.SVG_SPREADMETHOD_ATTRIBUTE == traitName) { return true; } else { return super.supportsTrait(traitName); } } /** * @param name the requested trait name. * @return the requested trait value. * * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested * trait is not supported on this element or null. * @throws DOMException with error code TYPE_MISMATCH_ERR if requested * trait's computed value cannot be converted to a String (SVG Tiny only). */ public String getTraitImpl(final String name) throws DOMException { if (SVGConstants.SVG_GRADIENT_TRANSFORM_ATTRIBUTE == name) { return toStringTrait(transform); } if (SVGConstants.SVG_GRADIENT_UNITS_ATTRIBUTE == name) { if (isObjectBBox) { return SVGConstants.SVG_OBJECT_BOUND_BOX_VALUE; } else { return SVGConstants.SVG_USER_SPACE_ON_USE_VALUE; } } if (SVGConstants.SVG_SPREADMETHOD_ATTRIBUTE == name) { return spreadMethod; } else { return super.getTraitImpl(name); } } /** * @param name the trait's name. * @param value the new trait value, as a string. * * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested * trait is not supported on this element or null. * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested * trait's value cannot be specified as a String * @throws DOMException with error code INVALID_ACCESS_ERR if the input * value is an invalid value for the given trait or null. * @throws DOMException with error code NO_MODIFICATION_ALLOWED_ERR: if * attempt is made to change readonly trait. */ public void setTraitImpl(final String name, final String value) throws DOMException { if (SVGConstants.SVG_GRADIENT_TRANSFORM_ATTRIBUTE == name) { setTransform(parseTransformTrait(name, value)); } else if (SVGConstants.SVG_GRADIENT_UNITS_ATTRIBUTE .equals(name)) { if (SVGConstants.SVG_OBJECT_BOUND_BOX_VALUE.equals(value)) { setIsObjectBBox(true); } else if (SVGConstants.SVG_USER_SPACE_ON_USE_VALUE.equals(value)) { setIsObjectBBox(false); } else { throw illegalTraitValue(name, value); } } else if (SVGConstants.SVG_SPREADMETHOD_ATTRIBUTE == name) { setSpreadMethod(value); } else { super.setTraitImpl(name, value); } } /** * @param name matrix trait name. * @return the trait value corresponding to name as SVGMatrix. * * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested * trait is not supported on this element or null. * @throws DOMException with error code TYPE_MISMATCH_ERR if requested * trait's computed value cannot be converted to {@link * org.w3c.dom.svg.SVGMatrix SVGMatrix} */ SVGMatrix getMatrixTraitImpl(final String name)throws DOMException { if (SVGConstants.SVG_GRADIENT_TRANSFORM_ATTRIBUTE.equals(name)) { return toSVGMatrixTrait(transform); } else { return super.getMatrixTraitImpl(name); } } /** * @param name name of trait to set * @param matrix Transform value of trait * * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested * trait is not supported on this element or null. * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested * trait's value cannot be specified as an {@link org.w3c.dom.svg.SVGMatrix * SVGMatrix} * @throws DOMException with error code INVALID_ACCESS_ERR if the input * value is an invalid value for the given trait or null. * @throws DOMException with error code NO_MODIFICATION_ALLOWED_ERR: if * attempt is made to change readonly trait. */ void setMatrixTraitImpl(final String name, final Transform matrix) throws DOMException { if (SVGConstants.SVG_GRADIENT_TRANSFORM_ATTRIBUTE.equals(name)) { setTransform(matrix); } else { super.setMatrixTraitImpl(name, matrix); } } /** * @param name the name of the trait to convert. * @param value the float trait value to convert. */ String toStringTrait(final String name, final float[][] value) { if (SVGConstants.SVG_GRADIENT_TRANSFORM_ATTRIBUTE == name) { Transform transform = new Transform(value[0][0], value[1][0], value[2][0], value[3][0], value[4][0], value[5][0]); return toStringTrait(transform); } else { return super.toStringTrait(name, value); } } /** * GradientElement handles the gradientTransform as a TransformTraitAnim and * the gradientUnits as a StringTraitAnim. * * @param traitName the trait name. */ TraitAnim createTraitAnimImpl(final String traitName) { if (SVGConstants.SVG_GRADIENT_TRANSFORM_ATTRIBUTE == traitName) { return new TransformTraitAnim(this, traitName); } else if (SVGConstants.SVG_GRADIENT_UNITS_ATTRIBUTE == traitName) { return new StringTraitAnim(this, NULL_NS, traitName); } else { return super.createTraitAnimImpl(traitName); } } /** * Set the trait value as float array. * * @param name the trait's name. * @param value the trait's value. * * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested * trait is not supported on this element. * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested * trait's value cannot be specified as a float * @throws DOMException with error code INVALID_ACCESS_ERR if the input * value is an invalid value for the given trait. */ void setFloatArrayTrait(final String name, final float[][] value) throws DOMException { // We use .equals for the transform attribute as the string may not // have been interned. We use == for the motion pseudo attribute because // it is only used internally and from the SVGConstants strings. if (SVGConstants.SVG_GRADIENT_TRANSFORM_ATTRIBUTE.equals(name)) { if (transform == null) { modifyingNode(); transform = new Transform(value[0][0], value[1][0], value[2][0], value[3][0], value[4][0], value[5][0]); } else { if (!transform.equals(value)) { modifyingNode(); transform.setTransform(value[0][0], value[1][0], value[2][0], value[3][0], value[4][0], value[5][0]); } else { return; } } modifiedNode(); } else { super.setFloatArrayTrait(name, value); } } /** * Validates the input trait value. * * @param namespaceURI the trait's namespace URI. * @param traitName the name of the trait to be validated. * @param value the value to be validated * @param reqNamespaceURI the namespace of the element requesting * validation. * @param reqLocalName the local name of the element requesting validation. * @param reqTraitNamespace the namespace of the trait which has the values * value on the requesting element. * @param reqTraitName the name of the trait which has the values value on * the requesting element. * @throws DOMException with error code INVALID_ACCESS_ERR if the input * value is incompatible with the given trait. */ String validateTraitNS(final String namespaceURI, final String traitName, final String value, final String reqNamespaceURI, final String reqLocalName, final String reqTraitNamespace, final String reqTraitName) throws DOMException { if (namespaceURI != null) { return super.validateTraitNS(namespaceURI, traitName, value, reqNamespaceURI, reqLocalName, reqTraitNamespace, reqTraitName); } if (SVGConstants.SVG_GRADIENT_UNITS_ATTRIBUTE .equals(traitName)) { if (!SVGConstants.SVG_USER_SPACE_ON_USE_VALUE.equals(value) && !SVGConstants.SVG_OBJECT_BOUND_BOX_VALUE.equals(value)) { throw illegalTraitValue(traitName, value); } return value; } if (SVGConstants.SVG_SPREADMETHOD_ATTRIBUTE.equals(traitName)) { if (!SVGConstants.SVG_PAD_VALUE.equals(value) && !SVGConstants.SVG_REFLECT_VALUE.equals(value) && !SVGConstants.SVG_REPEAT_VALUE.equals(value)) { throw illegalTraitValue(traitName, value); } return value; } return super.validateTraitNS(namespaceURI, traitName, value, reqNamespaceURI, reqLocalName, reqTraitNamespace, reqTraitName); } /** * Validates the input trait value. * * @param traitName the name of the trait to be validated. * @param value the value to be validated * @param reqNamespaceURI the namespace of the element requesting * validation. * @param reqLocalName the local name of the element requesting validation. * @param reqTraitNamespace the namespace of the trait which has the values * value on the requesting element. * @param reqTraitName the name of the trait which has the values value on * the requesting element. * @throws DOMException with error code INVALID_ACCESS_ERR if the input * value is incompatible with the given trait. */ public float[][] validateFloatArrayTrait( final String traitName, final String value, final String reqNamespaceURI, final String reqLocalName, final String reqTraitNamespace, final String reqTraitName) throws DOMException { if (SVGConstants.SVG_GRADIENT_TRANSFORM_ATTRIBUTE == traitName) { Transform txf = parseTransformTrait(traitName, value); return new float[][] {{txf.getComponent(0)}, {txf.getComponent(1)}, {txf.getComponent(2)}, {txf.getComponent(3)}, {txf.getComponent(4)}, {txf.getComponent(5)}}; } else { return super.validateFloatArrayTrait(traitName, value, reqNamespaceURI, reqLocalName, reqTraitNamespace, reqTraitName); } } /** * Builds a GradientColorMap from the children <stop> * elements. */ final void buildGradientColorMap() { // First, compute the number of stop children. ElementNode c = (ElementNode) getFirstElementChild(); int n = 0; Stop[] stop = new Stop[5]; while (c != null) { if (c.getLocalName() == SVGConstants.SVG_STOP_TAG && c.getNamespaceURI() == SVGConstants.SVG_NAMESPACE_URI) { stop[n] = (Stop) c; n++; if (n > stop.length - 1) { Stop[] tmpStop = new Stop[stop.length + 5]; System.arraycopy(stop, 0, tmpStop, 0, stop.length); stop = tmpStop; } } c = (ElementNode) c.getNextElementSibling(); } if (n == 0) { // To obtain the same result as a 'none' fill, we just use two // stops with fully transparent fill. lastColorMapFractions = new float[] {0, 1}; lastColorMapRGBA = new int[] {0x00000000, 0x00000000}; return; } if (n == 1) { // We duplicate the single gradient to provide the effect of // a solid color. RGB color = stop[0].getStopColor(); int a = (int) (stop[0].getStopOpacity() * 255); lastColorMapFractions = new float[] {0, 1}; lastColorMapRGBA = new int[] {a << 24 | color.getRed() << 16 | color.getGreen() << 8 | color.getBlue(), a << 24 | color.getRed() << 16 | color.getGreen() << 8 | color.getBlue() }; return; } float[] fractions = new float[n]; int[] rgba = new int[n]; RGB col = null; for (int i = 0; i < n; i++) { fractions[i] = stop[i].getOffset(); if (i > 0 && fractions[i] <= fractions[i - 1]) { fractions[i] = fractions[i - 1]; } col = stop[i].getStopColor(); rgba[i] = ((int) (stop[i].getStopOpacity() * 255) << 24) | col.getRed() << 16 | col.getGreen() << 8 | col.getBlue(); } // Check that the first stop is zero. If not, we need to dupplicate the // first stop and give it fraction zero. if (fractions[0] != 0) { float[] tmpFractions = new float[fractions.length + 1]; int[] tmpRgba = new int[rgba.length + 1]; tmpFractions[0] = 0; tmpRgba[0] = rgba[0]; System.arraycopy(fractions, 0, tmpFractions, 1, fractions.length); System.arraycopy(rgba, 0, tmpRgba, 1, rgba.length); fractions = tmpFractions; rgba = tmpRgba; } // Check that the last stop is 1. If not we duplicate the last stop. if (fractions[fractions.length - 1] != 1) { float[] tmpFractions = new float[fractions.length + 1]; int[] tmpRgba = new int[rgba.length + 1]; tmpFractions[tmpFractions.length - 1] = 1; tmpRgba[tmpRgba.length - 1] = rgba[rgba.length - 1]; System.arraycopy(fractions, 0, tmpFractions, 0, fractions.length); System.arraycopy(rgba, 0, tmpRgba, 0, rgba.length); fractions = tmpFractions; rgba = tmpRgba; } lastColorMapFractions = fractions; lastColorMapRGBA = rgba; } /** * Should be called when the paint should recompute itself and * notify its references. */ protected void onPaintChange() { computedPaint = null; lastColorMapFractions = null; lastColorMapRGBA = null; if (loaded) { notifyPaintChange(); } } }