FileDocCategorySizeDatePackage
Ellipse.javaAPI DocphoneME MR2 API (J2ME)23316Wed May 02 18:00:34 BST 2007com.sun.perseus.model

Ellipse.java

/*
 *
 *
 * Copyright  1990-2007 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.platform.MathSupport;

import com.sun.perseus.j2d.Box;
import com.sun.perseus.j2d.PathSupport;
import com.sun.perseus.j2d.Transform;
import com.sun.perseus.j2d.GraphicsProperties;
import com.sun.perseus.j2d.RenderGraphics;

import com.sun.perseus.util.SVGConstants;

import org.w3c.dom.DOMException;


/**
 * An <code>Ellipse</code> node models an SVG <code><ellipse></code>
 * or an <code><circle></code> element.
 * <br />
 * A negative radius along the x or y axis is illegal. A null radius
 * along the x or y axis disables rendering of the ellipes.
 * <br />
 * If the <code>Ellipse</code> is a circle, then setting the x radius
 * also sets the y radius (to the same value) and setting the y radius
 * also sets the x radius (to the same value).
 *
 * @version $Id: Ellipse.java,v 1.11 2006/06/29 10:47:31 ln156897 Exp $
 */
public class Ellipse extends AbstractShapeNode {
    /**
     * The rx and ry attributes are required for an <ellipse>
     */
    static final String[] ELLIPSE_REQUIRED_TRAITS
        = {SVGConstants.SVG_RX_ATTRIBUTE,
           SVGConstants.SVG_RY_ATTRIBUTE};

    /**
     * The r trait is required for a <circle>
     */
    static final String[] CIRCLE_REQUIRED_TRAITS
        = {SVGConstants.SVG_R_ATTRIBUTE};

    /**
     * If true, the x and y radii are constrained to always be the 
     * same value.
     */
    protected boolean isCircle = false;

    /**
     * This ellipe's origin along the x-axis
     */
    protected float x;

    /**
     * The ellipse's origin along the y-axis
     */
    protected float y;

    /**
     * The ellipse's width
     */
    protected float width;

    /**
     * The ellipse's height.
     */
    protected float height;

    /**
     * Constructor.
     *
     * @param ownerDocument this element's owner <code>DocumentNode</code>
     */
    public Ellipse(final DocumentNode ownerDocument) {
        this(ownerDocument, false);
}

    /**
     * Constructor.
     *
     * @param ownerDocument this element's owner <code>DocumentNode</code>
     * @param isCircle if true, the x and y radii will be constrained to
     *        always have the same value.
     */
    public Ellipse(final DocumentNode ownerDocument, final boolean isCircle) {
        super(ownerDocument);
        this.isCircle = isCircle;

        // Initially, the ellipse's width and height are zero, so we
        // set the corresponding bits accordingly.
        canRenderState |= CAN_RENDER_ZERO_WIDTH_BIT;
        canRenderState |= CAN_RENDER_ZERO_HEIGHT_BIT;
    }

    /**
     * @return the SVGConstants.SVG_CIRCLE_TAG if isCircle is true, 
     * SVGConstants.SVG_ELLIPSE_TAG otherwise.
     */

    public String getLocalName() {
        if (isCircle) {
            return SVGConstants.SVG_CIRCLE_TAG;
        } else {
            return SVGConstants.SVG_ELLIPSE_TAG;
        }
    }

    /**
     * Used by <code>DocumentNode</code> to create a new instance from
     * a prototype <code>Ellipse</code>.
     *
     * @param doc the <code>DocumentNode</code> for which a new node is
     *        should be created.
     * @return a new <code>Ellipse</code> for the requested document.
     */
    public ElementNode newInstance(final DocumentNode doc) {
        return new Ellipse(doc, isCircle);
    }

    /**
     * @return this ellipse's x-axis center
     */
    public float getCx() {
        return x + width / 2;
    }

    /**
     * @return this ellipse's y-axis center
     */
    public float getCy() {
        return y + height / 2;
    }

    /**
     * @return this ellipse's x-axis radius
     */
    public float getRx() {
        return width / 2;
    }

    /**
     * @return this ellipse's y-axis radius
     */
    public float getRy() {
        return height / 2;
    }

    /**
     * @param cx x-axis ellipse center coordinate
     */
    public void setCx(final float cx) {
        float newCx = cx - width / 2;
        if (newCx == x) {
            return;
        }
        modifyingNode();
        x = newCx;
        renderingDirty();
        modifiedNode();
    }

    /**
     * @param cy y-axis ellipse coordinate
     */
    public void setCy(final float cy) {
        float newCy = cy - height / 2;
        if (y == newCy) {
            return;
        }
        modifyingNode();
        y = newCy;
        renderingDirty();
        modifiedNode();
    }

    /**
     * @param rx the new x-axis radius
     */
    public void setRx(final float rx) {
        if (rx < 0) {
            throw new IllegalArgumentException();
        }

        if (width == rx * 2) {
            return;
        }

        modifyingNode();
        renderingDirty();
        
        float cx = getCx();
        width = rx * 2;
        x = cx - rx;

        computeCanRenderWidthBit(width);

        if (isCircle) {
            float cy = getCy();
            height = rx * 2;
            y = cy - rx;
            computeCanRenderHeightBit(width);
        }

        modifiedNode();
    }

    /**
     * @param ry the new y-axis radius
     */
    public void setRy(final float ry) {
        if (ry < 0) {
            throw new IllegalArgumentException();
        }

        if (height == ry * 2) {
            return;
        }

        modifyingNode();
        renderingDirty();

        float cy = getCy();
        height = ry * 2;
        y = cy - ry;

        if (isCircle) {
            float cx = getCx();
            width = ry * 2;
            x = cx - ry;
        }

        computeCanRenderHeightBit(height);
        modifiedNode();
    }

    /**
     * @param rg the RenderGraphics on which to fill the shape.
     */
    public void fillShape(final RenderGraphics rg) {
        rg.fillOval(x, y, width, height);
    }

    /**
     * @param rg the RenderGraphics on which to draw the shape.
     */
    public void drawShape(final RenderGraphics rg) {
        rg.drawOval(x, y, width, height);
    }

    /**
     * @param x the hit point coordinate along the x-axis, in user space.
     * @param y the hit point coordinate along the y-axis, in user space.
     * @param fillRule the fillRule to apply when testing for containment.
     * @return true if the hit point is contained within the shape.
     */
    public boolean contains(final float x, final float y, final int fillRule) {
        // Normalize the coordinates compared to the ellipse
        // having a center at 0,0 and a radius of 0.5.
        float normx = (x - this.x) / width - 0.5f;
        float normy = (y - this.y) / height - 0.5f;
        return (normx * normx + normy * normy) < 0.25f;
    }

    /**
     * Returns the stroked shape, using the given stroke properties.
     *
     * @param gp the <code>GraphicsProperties</code> defining the rendering
     *        context.
     * @return the shape's stroked path.
     */
    Object getStrokedPath(final GraphicsProperties gp) {
        return PathSupport.getStrokedEllipse(x, 
                                             y,
                                             width, 
                                             height,
                                             gp);
    }

    /**
     * Supported traits: cx, cy, rx, ry
     *
     * @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_CX_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_CY_ATTRIBUTE == traitName) {
            return true;
        }

        if (isCircle && SVGConstants.SVG_R_ATTRIBUTE == traitName) {
            return true;
        } else if (!isCircle 
                   && 
                   (SVGConstants.SVG_RX_ATTRIBUTE == traitName
                    ||
                    SVGConstants.SVG_RY_ATTRIBUTE == traitName)) {
            return true;
        } else {
            return super.supportsTrait(traitName);
        }
    }

    /**
     * @return an array of traits that are required by this element.
     */
    public String[] getRequiredTraits() {
        if (isCircle) {
            return CIRCLE_REQUIRED_TRAITS;
        } else {
            return ELLIPSE_REQUIRED_TRAITS;
        }
    }

    /**
     * Supported traits: cx, cy, r, rx, ry
     *
     * @param name the requested trait name.
     * @return the requested 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 requested
     * trait's computed value cannot be converted to a String (SVG Tiny only).
     */
    public String getTraitImpl(String name)
        throws DOMException {
        if (SVGConstants.SVG_CX_ATTRIBUTE == name) {
            return Float.toString(getCx());
        } else if (SVGConstants.SVG_CY_ATTRIBUTE == name) {
            return Float.toString(getCy());
        } else if ((!isCircle && SVGConstants.SVG_RX_ATTRIBUTE == name) 
                   || 
                   (isCircle && SVGConstants.SVG_R_ATTRIBUTE == name)) {
            return Float.toString(getRx());
        } else if (!isCircle && SVGConstants.SVG_RY_ATTRIBUTE == name) {
            return Float.toString(getRy());
        } else {
            return super.getTraitImpl(name);
        }
    }

    /**
     * Supported traits: cx, cy, rx, ry
     *
     * @param name the requested trait name.
     * @param 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 float
     * @throws SecurityException if the application does not have the necessary
     * privilege rights to access this (SVG) content.
     */
    float getFloatTraitImpl(final String name)
        throws DOMException {
        if (SVGConstants.SVG_CX_ATTRIBUTE == name) {
            return getCx();
        } else if (SVGConstants.SVG_CY_ATTRIBUTE == name) {
            return getCy();
        } else if ((!isCircle && SVGConstants.SVG_RX_ATTRIBUTE == name) 
                   || 
                   (isCircle && SVGConstants.SVG_R_ATTRIBUTE == name)) {
            return getRx();
        } else if (SVGConstants.SVG_RY_ATTRIBUTE == name) {
            return getRy();
        } else {
            return super.getFloatTraitImpl(name);
        }
    }

    /**
     * @param traitName the trait name.
     */
    TraitAnim createTraitAnimImpl(final String traitName) {
        if (SVGConstants.SVG_CX_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_CY_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_RX_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_RY_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_R_ATTRIBUTE == traitName) {
            return new FloatTraitAnim(this, traitName, TRAIT_TYPE_FLOAT);
        } else {
            return super.createTraitAnimImpl(traitName);
        }
    }

    /**
     * Set the trait value as float.
     *
     * @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 {
        if (SVGConstants.SVG_CX_ATTRIBUTE == name) {
            setCx(value[0][0]);
        } else if (SVGConstants.SVG_CY_ATTRIBUTE == name) {
            setCy(value[0][0]);
        } else if (SVGConstants.SVG_RX_ATTRIBUTE == name) {
            checkPositive(name, value[0][0]);
            setRx(value[0][0]);
        } else if (SVGConstants.SVG_RY_ATTRIBUTE == name) {
            checkPositive(name, value[0][0]);
            setRy(value[0][0]);
        } else if (SVGConstants.SVG_R_ATTRIBUTE == name) {
            checkPositive(name, value[0][0]);
            setRx(value[0][0]);
        } else {
            super.setFloatArrayTrait(name, value);
        }
    }

    /**
     * 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_CX_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_CY_ATTRIBUTE == traitName) {
            return toAnimatedFloatArray(parseFloatTrait(traitName, value));
        } else if (SVGConstants.SVG_RX_ATTRIBUTE == traitName
                   || 
                   SVGConstants.SVG_R_ATTRIBUTE == traitName
                   ||
                   SVGConstants.SVG_RY_ATTRIBUTE == traitName) {
            return toAnimatedFloatArray(parsePositiveFloatTrait(traitName, 
                                                                value));
        } else {
            return super.validateFloatArrayTrait(traitName,
                                                 value,
                                                 reqNamespaceURI,
                                                 reqLocalName,
                                                 reqTraitNamespace,
                                                 reqTraitName);
        }
    }

    /**
     * Supported traits: cx, cy, rx, ry
     *
     * @param name the trait name.
     * @param value the trait's string 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 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_CX_ATTRIBUTE == name) {
            setCx(parseFloatTrait(name, value));
        } else if (SVGConstants.SVG_CY_ATTRIBUTE == name) {
            setCy(parseFloatTrait(name, value));
        } else if ((!isCircle && SVGConstants.SVG_RX_ATTRIBUTE == name) 
                   || 
                   (isCircle && SVGConstants.SVG_R_ATTRIBUTE == name)) {
            setRx(parsePositiveFloatTrait(name, value));
        } else if (SVGConstants.SVG_RY_ATTRIBUTE == name) {
            setRy(parsePositiveFloatTrait(name, value));
        } else {
            super.setTraitImpl(name, value);
        }
    }

    /**
     * Supported traits: cx, cy, rx, ry
     *
     * @param name the trait name.
     * @param value the trait float 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.
     * @throws SecurityException if the application does not have the necessary
     * privilege rights to access this (SVG) content.
     */
    public void setFloatTraitImpl(final String name, final float value)
        throws DOMException {
        if (SVGConstants.SVG_CX_ATTRIBUTE == name) {
            setCx(value);
        } else if (SVGConstants.SVG_CY_ATTRIBUTE == name) {
            setCy(value);
        } else if ((!isCircle 
                    && SVGConstants.SVG_RX_ATTRIBUTE == name) 
                   || 
                   (isCircle 
                    && 
                    SVGConstants.SVG_R_ATTRIBUTE == name)) {
            checkPositive(name, value);
            setRx(value);
        } else if (SVGConstants.SVG_RY_ATTRIBUTE == name) {
            checkPositive(name, value);
            setRy(value);
        } else {
            super.setFloatTraitImpl(name, value);
        }
    }

    /**
     * @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_CX_ATTRIBUTE == name
            ||
            SVGConstants.SVG_CY_ATTRIBUTE == name) {
            return Float.toString(value[0][0]);
        } else if ((!isCircle 
                    && SVGConstants.SVG_RX_ATTRIBUTE == name) 
                   || 
                   (isCircle 
                    && 
                    SVGConstants.SVG_R_ATTRIBUTE == name)) {
            return Float.toString(value[0][0]);
        } else if (SVGConstants.SVG_RY_ATTRIBUTE == name) {
            return Float.toString(value[0][0]);
        } else {
            return super.toStringTrait(name, value);
        }
    }

    /**
     * @param bbox the bounding box to which this node's bounding box should be
     *        appended. That bounding box is in the target coordinate space. It 
     *        may be null, in which case this node should create a new one.
     * @param t the transform from the node coordinate system to the coordinate
     *        system into which the bounds should be computed.
     * @return the bounding box of this node, in the target coordinate space, 
     */
    Box addNodeBBox(final Box bbox, 
                    final Transform t) {
        
        float rx = getRx();
        float ry = getRy();

        if (t == null 
            || (t.getComponent(1) == 0 && t.getComponent(2) == 0)
            || width == 0 
            || height == 0) {
            // If we are dealing with no transform or if the 
            // transform is a simple zoom/pan
            return addTransformedBBox(bbox, x, y, width, height, t);
        }

        //
        // The ellipse's equations are:
        //
        // x = cx + rx * cos (t)
        // y = cy + ry * sin (t)
        //
        // When transformed through t, the equation becomes:
        //
        // [x']   [m0 m2 m4]   [x]
        // [y'] = [m1 m3 m5] * [y]
        // [1 ]   [ 0 0   1]   [1]
        //
        // x' = m0 * x + m2 * y + m4
        // y' = m1 * x + m3 * y + m5
        //
        // x' = m0 * cx + m0 * rx * cos(t) + m2 * cy + m2 * ry * sin(t) + m4
        // y' = m1 * cx + m1 * rx * cos(t) + m3 * cy + m3 * ry * sin(t) + m5
        //
        // x' = m0 * cx + m2 * cy + m4 + m0 * rx * cos(t) + m2 * ry * sin(t)
        // y' = m1 * cx + m3 * cy + m5 + m1 * rx * cos(t) + m3 * ry * sin(t)
        //
        // fx = m0 * cx + m2 * cy + m4 
        // fy = m1 * cx + m3 * cy + m5
        //
        // vx(t) = m0 * rx * cos(t) + m2 * ry * sin(t)
        // vy(t) = m1 * rx * cos(t) + m3 * ry * sin(t)
        //
        // The maximum and minimum are computed by the derivative functions:
        //
        // vx(t)' = -m0 * rx * sin(t) + m2 * ry * cos(t)
        // vy(t)' = -m1 * rx * sin(t) + m3 * ry * cos(t)
        //
        // vx(t)' = 0 for tan(t) = sin(t) / cos(t) 
        //                       = m2 * ry / m0 * rx
        // vy(t)' = 0 for tan(t) = sin(t) / cos(t) 
        //                       = m3 * ry / m1 * rx
        //
        float m0 = t.getComponent(0);
        float m1 = t.getComponent(1);
        float m2 = t.getComponent(2);
        float m3 = t.getComponent(3);
        float m4 = t.getComponent(4);
        float m5 = t.getComponent(5);
        float cx = getCx();
        float cy = getCy();

        float m0rx = m0 * rx;
        float m2ry = m2 * ry;
        float m1rx = m1 * rx;
        float m3ry = m3 * ry;

        float theta = MathSupport.atan2(m2ry, m0rx);
        float theta2 = theta + MathSupport.PI;
        float cost = MathSupport.cos(theta);
        float sint = MathSupport.sin(theta);
        float cost2 = MathSupport.cos(theta2);
        float sint2 = MathSupport.sin(theta2);

        float maxX = m0rx * cost + m2ry * sint;
        float minX = m0rx * cost2 + m2ry * sint2;
        float width = maxX - minX;

        if (minX > maxX) {
            minX = maxX;
            width = -width;
        }

        theta = MathSupport.atan2(m3ry, m1rx);
        theta2 = theta + MathSupport.PI;
        cost = MathSupport.cos(theta);
        sint = MathSupport.sin(theta);
        cost2 = MathSupport.cos(theta2);
        sint2 = MathSupport.sin(theta2);

        float maxY = m1rx * cost + m3ry * sint;
        float minY = m1rx * cost2 + m3ry * sint2;
        float height = maxY - minY;

        if (minY > maxY) {
            minY = maxY;
            height = -height;
        }

        float fx = m0 * cx + m2 * cy + m4;
        float fy = m1 * cx + m3 * cy + m5;
        
        // (fx, fy) is the upper left corner of the bounding box
        return addBBox(bbox, fx + minX, fy + minY, width, height);
    }


}