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

Font.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.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);
        }
    }

}