/* * $RCSfile: Glyph.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.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.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; } /** * @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 + "]"; } }