/* * $RCSfile: Font.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.util.SVGConstants; import java.util.Vector; import org.w3c.dom.DOMException; /** * A <code>Font</code> node models an SVG <code><code></code> * element. * * @version $Id: Font.java,v 1.7 2006/06/29 10:47:31 ln156897 Exp $ */ public class Font extends ElementNode { /** * Only the horiz-adv-x trait is required on <font> */ static final String[] REQUIRED_TRAITS = {SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE}; /** * The X-coordinate in the font coordinate system of the origin * of a glyph to be used when drawing horizontally oriented text. * (Note that the origin applies to all glyphs in the font.) */ protected float horizontalOriginX; /** * The default horizontal advance after rendering a glyph in * horizontal orientation. Glyph widths are required to be non-negative, * even if the glyph is typically rendered right-to-left, as in * Hebrew and Arabic scripts. */ protected float horizontalAdvanceX; /** * The current fontFace describing this font, if any */ protected FontFace fontFace; /** * The Font's missing glyph, used to render unknown characters */ protected Glyph missingGlyph; /** * The Font's first horizontal kerning pair. May be null. */ protected HKern firstHKern; /** * The Font's last horizontal kerning pair. May be null. */ protected HKern lastHKern; /** * Constructor. * * @param ownerDocument this element's owner <code>DocumentNode</code> */ public Font(final DocumentNode ownerDocument) { super(ownerDocument); } /** * @return the SVGConstants.SVG_FONT_TAG value */ public String getLocalName() { return SVGConstants.SVG_FONT_TAG; } /** * Used by <code>DocumentNode</code> to create a new instance from * a prototype <code>Font</code>. * * @param doc the <code>DocumentNode</code> for which a new node is * should be created. * @return a new <code>Font</code> for the requested document. */ public ElementNode newInstance(final DocumentNode doc) { return new Font(doc); } /** * @param newHorizontalOriginX the new value for the <tt>Font</tt>'s * origin along the x-axis. */ public void setHorizontalOriginX(final float newHorizontalOriginX) { if (newHorizontalOriginX == horizontalOriginX) { return; } modifyingNode(); horizontalOriginX = newHorizontalOriginX; modifiedNode(); } /** * @return this <tt>Font</tt>'s origin along the x-axis */ public float getHorizontalOriginX() { return horizontalOriginX; } /** * @param newHorizontalAdvanceX the new horizontal advance along the * x-axis for this <tt>Font</tt> */ public void setHorizontalAdvanceX(final float newHorizontalAdvanceX) { if (newHorizontalAdvanceX == horizontalAdvanceX) { return; } modifyingNode(); horizontalAdvanceX = newHorizontalAdvanceX; modifiedNode(); } /** * @return this <tt>Font</tt>'s horizontal adavance along the x-axis */ public float getHorizontalAdvanceX() { return horizontalAdvanceX; } /** * Returns a Glyph if the FontFace can display the * character at the requested index. Returns null * if no matching glyph can be found. * * @param s the character sequence to display * @param index the index of the first character in <tt>s</tt> to * display. * @return the <tt>Glyph</tt> used to represent the input * character at <tt>index</tt> in <tt>s</tt> */ public Glyph canDisplay(final char[] s, final int index) { int max = s.length - index; int glLength = 0; int j = 0; Glyph gl = null; ModelNode c = firstChild; while (c != null) { if (c instanceof Glyph) { gl = (Glyph) c; glLength = gl.getLength(); if (gl.getUnicode() != null && glLength > 0 && index + glLength <= s.length) { for (j = 0; j < glLength; j++) { if (s[index + j] != gl.getCharAt(j)) { break; } } if (j == glLength) { return gl; } } } c = c.nextSibling; } // No matching glyph return null; } /** * @return the Glyph that represents missing glyphs. * This should *not* be null. */ public Glyph getMissingGlyph() { return missingGlyph; } // ==================================================================== // List Implementation overrides to track special children // ==================================================================== /** * @param node <tt>ElementNode</tt> to add to this <tt>ElementNode</tt> */ public void add(final ElementNode node) { super.add(node); addSpecial(node); } /** * Called in case special handling of children is required. * * @param node the <tt>ModelNode</tt> added to this list * * @see #addFontFace * @see #addGlyph */ protected void addSpecial(final ModelNode node) { if (!(node instanceof Glyph)) { if (!(node instanceof FontFace)) { if (!(node instanceof HKern)) { return; } else { addHKern((HKern) node); } } else { addFontFace((FontFace) node); } } else { Glyph gl = (Glyph) node; if (gl.getUnicode() == null) { addMissingGlyph(gl); } else { addGlyph(gl); } } } /** * @param newFontFace the <tt>FontFace</tt> child to add */ protected void addFontFace(final FontFace newFontFace) { if (fontFace != null) { // If there is already a FontFace child, we ignore the second // one. Only the first child is accounted for. return; } // A Font can only have a single font-face child. If there is alre fontFace = newFontFace; updateGlyphEmSquare(); } /** * Simply chain the kerning pairs so that they can be * looked up easily later. * * @param hkern the new <code>HKern</code> entry to add * to this node. */ protected void addHKern(final HKern hkern) { if (firstHKern != null) { lastHKern.nextHKern = hkern; lastHKern = hkern; } else { firstHKern = hkern; lastHKern = hkern; hkern.nextHKern = null; } } /** * Used by the Text class: looks up kerning pairs to check if * there is any horizontal kerning for the input pair. * * @param g1 this first glyph is assumed to be part of this * Font. * @param g2 this second glyph may come from a different font, in * which case this method returns 0. * @return the kerning adjustment for the given pair of <code>Glyph</code>s. */ float getHKern(final Glyph g1, final Glyph g2) { if (g2.parent != this) { return 0; } HKern k = firstHKern; while (k != null) { if (k.matchesFirst(g1)) { if (k.matchesSecond(g2)) { break; } } k = k.nextHKern; } if (k != null) { if (fontFace != null) { return k.k * fontFace.getEmSquareScale(); } else { return k.k; } } else { return 0; } } /** * Update the em square on all the <tt>Font</tt>'s glyphs */ protected void updateGlyphEmSquare() { float emSquareScale = 1; if (fontFace != null) { emSquareScale = fontFace.getEmSquareScale(); } ElementNode c = firstChild; while (c != null) { if (c instanceof Glyph) { ((Glyph) c).setEmSquareScale(emSquareScale); } c = (ElementNode) c.nextSibling; } } /** * @param gl set the input <tt>Glyph</tt>'s em square. */ protected void updateGlyphEmSquare(final Glyph gl) { if (fontFace != null) { gl.setEmSquareScale(fontFace.getEmSquareScale()); } else { gl.setEmSquareScale(1); } } /** * If there is already a missing glyph, any new call * to <tt>addMissingGlyph</tt> has no effect on the * missing glyph. * * @param gl <tt>Glyph</tt> to add as a missing glyph. */ protected void addMissingGlyph(final Glyph gl) { // Only use gl as a missing glyph if it is the // first missing glyph if (missingGlyph == null) { missingGlyph = gl; } updateGlyphEmSquare(gl); } /** * @param gl <tt>Glyph</tt> to add to this font */ protected void addGlyph(final Glyph gl) { updateGlyphEmSquare(gl); } /** * Font handlers the horiz-adv-x and horiz-origin-x traits. * * @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_HORIZ_ORIGIN_X_ATTRIBUTE == traitName || SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == traitName) { return true; } return super.supportsTrait(traitName); } /** * @return an array of traits that are required by this element. */ public String[] getRequiredTraits() { return REQUIRED_TRAITS; } /** * Font handles the horiz-adv-x and horiz-origin-x traits. * Other traits are handled by the super class. * * @param name the requested trait name (e.g., "horiz-adv-x") * @return the trait's 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(final String name) throws DOMException { if (SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == name) { return Float.toString(getHorizontalAdvanceX()); } else if (SVGConstants.SVG_HORIZ_ORIGIN_X_ATTRIBUTE == name) { return Float.toString(getHorizontalOriginX()); } else { return super.getTraitImpl(name); } } /** * Font handles the horiz-adv-x and horiz-origin-x traits. * Other traits are handled by the super class. * * @param name the requested trait name (e.g., "horiz-adv-x") * @return the trait's value, as a float. * * @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). */ float getFloatTraitImpl(final String name) throws DOMException { if (SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == name) { return getHorizontalAdvanceX(); } else if (SVGConstants.SVG_HORIZ_ORIGIN_X_ATTRIBUTE == name) { return getHorizontalOriginX(); } else { return super.getFloatTraitImpl(name); } } /** * Font handles the horiz-adv-x and horiz-origin-x traits. * Other traits are handled by the super class. * * @param name the trait's name (e.g., "horiz-adv-x") * @param value the new trait string value (e.g., "10") * * @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_HORIZ_ADV_X_ATTRIBUTE == name) { checkWriteLoading(name); setHorizontalAdvanceX(parseFloatTrait(name, value)); } else if (SVGConstants.SVG_HORIZ_ORIGIN_X_ATTRIBUTE == name) { checkWriteLoading(name); setHorizontalOriginX(parseFloatTrait(name, value)); } else { super.setTraitImpl(name, value); } } /** * Font handles the horiz-adv-x and horiz-origin-x traits. * Other traits are handled by the super class. * * @param name the trait's name (e.g., "horiz-adv-x") * @param value the new trait float value (e.g., 10f) * * @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 setFloatTraitImpl(final String name, final float value) throws DOMException { if (SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == name) { setHorizontalAdvanceX(value); } else if (SVGConstants.SVG_HORIZ_ORIGIN_X_ATTRIBUTE == name) { setHorizontalOriginX(value); } else { super.setFloatTraitImpl(name, value); } } }