FileDocCategorySizeDatePackage
Glyph.javaAPI DocphoneME MR2 API (J2ME)17287Wed May 02 18:00:36 BST 2007com.sun.perseus.model

Glyph.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.j2d.RenderGraphics;
import com.sun.perseus.j2d.TextRenderingProperties;

import com.sun.perseus.util.SVGConstants;

import org.w3c.dom.DOMException;

import com.sun.perseus.j2d.Box;
import com.sun.perseus.j2d.Path;
import com.sun.perseus.j2d.PathSupport;
import com.sun.perseus.j2d.Tile;
import com.sun.perseus.j2d.Transform;

/**
 * A <tt>Glyph</tt> node corresponds to an SVG <tt><glyph></tt>
 * and <tt><missing-glyph></tt> elements. If the unicode value is null,
 * then a <tt>Glyph</tt> represents a <tt><missing-glyph></tt>
 * and its length is assumed to be 1.
 * <br />
 * 
 * @version $Id: Glyph.java,v 1.11 2006/06/29 10:47:31 ln156897 Exp $
 */
public class Glyph extends ElementNode {
    /**
     * By default, use the parent Font's advance
     */
    public static final float DEFAULT_HORIZONTAL_ADVANCE_X = -1;

    /**
     * The Path representing the outline of this glyph
     */
    protected Path d;

    /**
     * This glyph's horizontal advance.
     * A negative value means that the parent Font value should be used
     */
    protected float horizontalAdvanceX = DEFAULT_HORIZONTAL_ADVANCE_X;

    /**
     * The computed glyph's horizontal advance. If this glyphs
     * advance is set, then this is equal to the glyph's advance.
     * Otherwise, this is the grand-parent font's advance if there
     * is one.
     */
    protected float computedHorizontalAdvanceX = -1; 

    /**
     * The computed glyph origin.
     */
    protected float origin = 0;

    /**
     * The em square scale factor. 
     */
    protected float emSquareScale = 1f;

    /**
     * The unicode character, or sequence of unicode character, that 
     * this glyph represents. If null, then this Glyph can be used to
     * represent missing glyphs for arbitrary unicode values.
     */
    protected String unicode;

    /**
     * Set of glyph names. May be null.
     */
    protected String[] glyphName;

    /**
     * This glyph's local name.
     */
    protected String localName = SVGConstants.SVG_GLYPH_TAG;

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

    /**
     * Constructor.
     *
     * @param ownerDocument this element's owner <code>DocumentNode</code>
     * @param localName this element's local name. One of 
     *        'glyph' or 'missing-glyph'.
     */
    public Glyph(final DocumentNode ownerDocument,
                 final String localName) {
        super(ownerDocument);

        if (SVGConstants.SVG_GLYPH_TAG == localName
            ||
            SVGConstants.SVG_MISSING_GLYPH_TAG == localName) {
            this.localName = localName;
        } else {
            throw new IllegalArgumentException("Illegal Glyph name : " 
                                               + localName);
        }
    }

    /**
     * @return the SVGConstants.SVG_GLYPH_TAG value
     */

    public String getLocalName() {
        return localName;
    }

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

    /**
     * Sets the emSquareScale. See the Font's list method adding
     * children: when the FontFace is set, the Font sets the em square
     * transform on all its children. When a Glyph is
     * set, and if there is a FontFace already, the em square transform
     * is set as well.
     * 
     * @param newEmSquareScale the new value for this glyph's em square
     *        scale, i.e., the scale between the glyph's em square and 
     *        the text's coordinate system.
     */
    public void setEmSquareScale(final float newEmSquareScale) {
        if (newEmSquareScale == emSquareScale) {
            return;
        }
        emSquareScale = newEmSquareScale;
    }

    /**
     * Sets the horizontal advance along the x axis. This is
     * in the em square coordinate space.
     *
     * @param newHorizontalAdvanceX the new advance along the x axis
     */
    public void setHorizontalAdvanceX(final float newHorizontalAdvanceX) {
        if (newHorizontalAdvanceX == horizontalAdvanceX) {
            return;
        }

        this.horizontalAdvanceX = newHorizontalAdvanceX;
        if (newHorizontalAdvanceX == -1
            &&
            parent != null
            && parent instanceof Font) {
            computedHorizontalAdvanceX 
                = ((Font) parent).getHorizontalAdvanceX();
            origin = ((Font) parent).getHorizontalOriginX();
        } else {
            computedHorizontalAdvanceX = newHorizontalAdvanceX;
        }
    }

    /**
     * Override set parent to capture the default advance
     * 
     * @param parent this node's new parent
     */
    public void setParent(final ModelNode parent) {
        super.setParent(parent);

        if (parent != null && parent instanceof Font) {
            if (horizontalAdvanceX == -1) {
                computedHorizontalAdvanceX 
                    = ((Font) parent).getHorizontalAdvanceX();
            }
            origin = ((Font) parent).getHorizontalOriginX();
        } else {
            origin = 0;
        }
    }
            
    /**
     * @param tx the <code>Transform</code> to add node transform to.
     */
    protected void applyTransform(final Transform tx) {
        tx.mScale(emSquareScale, -emSquareScale);
        tx.mTranslate(-origin, 0);
    }

    /**
     * @param tx the inverse <code>Transform</code> between this node and its
     * parent space.
     */
    protected void applyInverseTransform(final Transform tx) {
        tx.mTranslate(origin, 0);
        tx.mScale(1 / emSquareScale, -1 / emSquareScale);
    }

    /**
     * @return the horizontal advance along the x axis for this 
     * glyph. This advance is in the em square coordinate space.
     * To get the advance in text coordinate space, use the 
     * getTextHorizontalAdvanceX method.
     * Note that if the horizontalAdvanceX of this glyph is -1,
     * this method returns the horizontal advance of the parent
     * FontFace element if one is set.
     *
     * @see #getTextHorizontalAdvanceX
     */
    public float getHorizontalAdvanceX() {
        return computedHorizontalAdvanceX;
    }
    
    /**
     * @return the advance along the x axis in the text
     * coordinate space, i.e., after applying the 
     * emSquare transform.
     */
    public float getTextHorizontalAdvanceX() {
        return computedHorizontalAdvanceX * emSquareScale;
    }

    /**
     * @return this glyph's geometry, as a <tt>Path</tt>.
     *         The returned path is in the Font's coordinate system.
     */
    public Path getD() {
        return d;
    }

    /**
     * @param newD the new <tt>Path</tt> for this <tt>Glyph</tt>
     */
    public void setD(final Path newD) {
        if (equal(newD, d)) {
            return;
        }
        this.d = newD;
    }

    /**
     * Returns this glyph's name(s)
     *
     * @return thsi glyph's name set. Null if none.
     */
    public String[] getGlyphName() {
        return glyphName;
    }

    /**
     * Sets this glyph's name(s). May be null.
     *
     * @param glyphName the new name for the glyph
     */
    public void setGlyphName(final String[] glyphName) {
        if (equal(glyphName, this.glyphName)) {
            return;
        }
        this.glyphName = glyphName;
    }

    /**
     * @param tile the Tile instance whose bounds should be appended.
     * @param trp the TextRenderingProperties describing the nodes rendering 
     *        characteristics.
     * @param t the Transform to the requested tile space, from this node's user
     *        space.
     * @return the expanded tile.
     */
    protected Tile addRenderingTile(Tile tile, 
                                    final TextRenderingProperties trp, 
                                    final Transform t) {
        if (d != null) {
            if (trp.getStroke() == null) {
                // No stroking on the shape, we can use the geometrical bounding
                // box.
                Box renderingBox = addNodeBBox(null, t);
                if (renderingBox != null) {
                    if (tile == null) {
                        tile = new Tile();
                        tile.snapBox(renderingBox);
                    } else {
                        tile.addSnapBox(renderingBox);
                    }
                }
            } else {
                // Need to account for stroking, with a more costly operation 
                // to compute the stroked bounds.
                Object strokedPath = PathSupport.getStrokedPath(d, trp);
                if (tile == null) {
                    tile = new Tile();
                    PathSupport.computeStrokedPathTile(tile, strokedPath, t);
                } else {
                    Tile st = new Tile();
                    PathSupport.computeStrokedPathTile(st, strokedPath, t);
                    tile.addTile(st);
                }
            }
        }
        return tile;
    }
    
    /**
     * @return this <tt>Glyph</tt>'s unicode value
     */
    public String getUnicode() {
        return unicode;
    }

    /**
     * @param newUnicode this <tt>Glyph</tt>'s new unicode value
     */
    public void setUnicode(final String newUnicode) {
        if (equal(newUnicode, unicode)) {
            return;
        }
        this.unicode = newUnicode;
    }

    /**
     * @return the number of characters represented by this glyph
     */
    public int getLength() {
        if (unicode == null) {
            return 1;
        }
        
        return unicode.length();
    }

    /**
     * It is the responsibility of the caller to check that the 
     * requested index is between the unicode's value index range
     * (i.e., between 0 and unicode.length())
     *
     * @param i the index of the unicode value for the given index.
     * @return the character at the requested index 
     */
    public int getCharAt(final int i) {
        return unicode.charAt(i);
    }

    /**
     * A <tt>Glyph</tt> is hit if the <tt>Path</tt> it paints is
     * hit by the given point.
     *  
     * @param pt the x/y coordinate. Should never be null and be
     *        of size two. If not, the behavior is unspecified. The 
     *        point is in the node's user space.
     * @param trp the <tt>TextRenderingProperties</tt> containing the properties
     *        applicable to the hit detection operation. This is used
     *        because the same node may be referenced multiple times
     *        by different proxies. 
     * @return true if this node is hit at the given point. false otherwise.
     */
    public boolean isHit(final float[] pt, final TextRenderingProperties trp) {
        if (d == null) {
            return false;
        }

        // If the node is filled, see if the shape is hit
        if (trp.getFill() != null) {
            if (PathSupport.isHit(d, trp.getFillRule(), pt[0], pt[1])) {
                return true;
            }
        }

        // Test detection on the edge if the stroke color
        // is set.
        if (trp.getStroke() != null) {
            Object strokedPath = PathSupport.getStrokedPath(d, trp);
            if (PathSupport.isStrokedPathHit(strokedPath, trp.getFillRule(), 
                                             pt[0], pt[1])) {
                return true;
            }
        }

        return false;
    }

    /**
     * @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) {
        return addShapeBBox(bbox, d, t);
    }

    /**
     * Supported traits: d, horiz-adv-x
     *
     * @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_D_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_GLYPH_NAME_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_UNICODE_ATTRIBUTE == traitName) {
            return true;
        }
        return super.supportsTrait(traitName);
    }

    /**
     * Supported traits: d, horiz-adv-x
     *
     * @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_D_ATTRIBUTE == name) {
            if (d == null) {
                return "";
            } else {
                return d.toString();
            }
        } else if (SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == name) {
            return Float.toString(getHorizontalAdvanceX());
        } else if (SVGConstants.SVG_UNICODE_ATTRIBUTE == name) {
            if (unicode == null) {
                return "";
            }
            return unicode;
        } else if (SVGConstants.SVG_GLYPH_NAME_ATTRIBUTE == name) {
            return toStringTrait(getGlyphName());
        }

        return super.getTraitImpl(name);
    }

    /**
     * Supported traits: d, horiz-adv-x
     *
     * @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_D_ATTRIBUTE == name) {
            checkWriteLoading(name);
            setD(parsePathTrait(name, value));
        } else if (SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == name) {
            checkWriteLoading(name);
            setHorizontalAdvanceX(parsePositiveFloatTrait(name, value));
        } else if (SVGConstants.SVG_UNICODE_ATTRIBUTE == name) {
            checkWriteLoading(name);
            if (value == null) {
                throw illegalTraitValue(name, value);
            }
            setUnicode(value);
        } else if (SVGConstants.SVG_GLYPH_NAME_ATTRIBUTE == name) {
            checkWriteLoading(name);
            setGlyphName(parseStringArrayTrait(name, value, 
                                               SVGConstants.COMMA_STR));
        } else {
            super.setTraitImpl(name, value);
        }
    }


    /**
     * @return a text description of the glyph including it's unicode value
     */
    public String toString() {
        return "com.sun.perseus.model.Glyph[" + unicode + "]";
    }
}