/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.bridge;

import java.awt.Font;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
import java.text.AttributedCharacterIterator;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.StringTokenizer;
import java.util.Vector;

import org.apache.batik.css.engine.SVGCSSEngine;
import org.apache.batik.css.engine.value.Value;
import org.apache.batik.dom.util.XMLSupport;
import org.apache.batik.gvt.font.GVTFont;
import org.apache.batik.gvt.font.GVTGlyphVector;
import org.apache.batik.gvt.font.GVTLineMetrics;
import org.apache.batik.gvt.font.Glyph;
import org.apache.batik.gvt.font.Kern;
import org.apache.batik.gvt.font.KerningTable;
import org.apache.batik.gvt.font.SVGGVTGlyphVector;
import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;

/**
 * Represents an SVG font.
 *
 * @author <a href="mailto:bella.robinson@cmis.csiro.au">Bella Robinson</a>
 * @version $Id: SVGGVTFont.java,v 1.11 2003/04/11 13:54:49 vhardy Exp $
 */
public final class SVGGVTFont implements GVTFont, SVGConstants {

    private float fontSize;
    private SVGFontFace fontFace;
    private String[] glyphUnicodes;
    private String[] glyphNames;
    private String[] glyphLangs;
    private String[] glyphOrientations;
    private String[] glyphForms;
    private Element[] glyphElements;
    private Element[] hkernElements;
    private Element[] vkernElements;
    private BridgeContext ctx;
    private Element textElement;
    private Element missingGlyphElement;
    private KerningTable hKerningTable;
    private KerningTable vKerningTable;
    private String language;
    private String orientation;

    private GVTLineMetrics lineMetrics=null;

    /**
     * Constructs a new SVGGVTFont of the specified size.
     *
     * @param fontSize The size of the font to create.
     * @param fontFace The font face that describes the font.
     * @param glyphUnicodes An array containing the unicode values for
     * all the glyphs this font can display.
     * @param glyphNames An array containing the names of all the
     * glyphs this font can display.
     * @param ctx The bridge context.
     * @param glyphElements An array containing the children glyph
     * elements of the SVG font.
     * @param missingGlyphElement The missing glyph element for this
     * font.
     * @param hkernElements An array containing all hkern elements for
     * this font.
     * @param vkernElements An array containing all vkern elements for
     * this font.
     * @param textElement The text element that contains the text to
     * be rendered using this font.
     */
    public SVGGVTFont(float fontSize,
                      SVGFontFace fontFace,
                      String[] glyphUnicodes,
                      String[] glyphNames,
                      String[] glyphLangs,
                      String[] glyphOrientations,
                      String[] glyphForms,
                      BridgeContext ctx,
                      Element[] glyphElements,
                      Element missingGlyphElement,
                      Element[] hkernElements,
                      Element[] vkernElements,
                      Element textElement) {
        this.fontFace = fontFace;
        this.fontSize = fontSize;
        this.glyphUnicodes = glyphUnicodes;
        this.glyphNames = glyphNames;
        this.glyphLangs = glyphLangs;
        this.glyphOrientations = glyphOrientations;
        this.glyphForms = glyphForms;
        this.ctx = ctx;
        this.glyphElements = glyphElements;
        this.missingGlyphElement = missingGlyphElement;
        this.hkernElements = hkernElements;
        this.vkernElements = vkernElements;
        this.textElement = textElement;

        this.language = XMLSupport.getXMLLang(textElement);

        Value v = CSSUtilities.getComputedStyle
            (textElement, SVGCSSEngine.WRITING_MODE_INDEX);
        if (v.getStringValue().startsWith(CSS_TB_VALUE)) {
            // top to bottom, so set orientation to "v"
            this.orientation = SVG_V_VALUE;
        } else {
            this.orientation = SVG_H_VALUE;
        }

        createKerningTables();
    }


    /**
     * Creates the kerning tables for this font. Two tables are created,
     * horizontal and vertical. If there are not children vkern or hkern
     * elements these tables will be empty.
     */
    private void createKerningTables() {

        Kern[] hEntries = new Kern[hkernElements.length];
        for (int i = 0; i < hkernElements.length; i++) {
            Element hkernElement = hkernElements[i];
            SVGHKernElementBridge hkernBridge =
                (SVGHKernElementBridge)ctx.getBridge(hkernElement);
            Kern hkern = hkernBridge.createKern(ctx, hkernElement, this);
            hEntries[i] = hkern;
        }
        hKerningTable = new KerningTable(hEntries);

        Kern[] vEntries = new Kern[vkernElements.length];
        for (int i = 0; i < vkernElements.length; i++) {
            Element vkernElement = vkernElements[i];
            SVGVKernElementBridge vkernBridge =
                (SVGVKernElementBridge)ctx.getBridge(vkernElement);
            Kern vkern = vkernBridge.createKern(ctx, vkernElement, this);
            vEntries[i] = vkern;
        }
        vKerningTable = new KerningTable(vEntries);

    }

    /**
     * Returns the horizontal kerning value for the specified glyph pair.
     * This will be zero if there is no explicit horizontal kerning value
     * for this particular glyph pair.
     *
     * @param glyphCode1 The id of the first glyph.
     * @param glyphCode2 The id of the second glyph.
     *
     * @return The horizontal kerning value.
     */
    public float getHKern(int glyphCode1, int glyphCode2) {
        if (glyphCode1 < 0 || glyphCode1 >= glyphUnicodes.length
            || glyphCode2 < 0 || glyphCode2 >= glyphUnicodes.length) {
            return 0f;
        }
        return hKerningTable.getKerningValue(glyphCode1, glyphCode2,
                glyphUnicodes[glyphCode1], glyphUnicodes[glyphCode2]);
    }

    /**
     * Returns the vertical kerning value for the specified glyph pair.
     * This will be zero if there is no explicit vertical kerning value for
     * for this particular glyph pair.
     *
     * @param glyphCode1 The id of the first glyph.
     * @param glyphCode2 The id of the second glyph.
     *
     * @return The vertical kerning value.
     */
    public float getVKern(int glyphCode1, int glyphCode2) {
        if (glyphCode1 < 0 || glyphCode1 >= glyphUnicodes.length
            || glyphCode2 < 0 || glyphCode2 >= glyphUnicodes.length) {
            return 0f;
        }
        return vKerningTable.getKerningValue(glyphCode1, glyphCode2,
                glyphUnicodes[glyphCode1], glyphUnicodes[glyphCode2]);
    }

    /**
     * Returns an array of glyph codes (unique ids) of the glyphs with the
     * specified name (there may be more than one).
     *
     * @param name The name of the glyph.
     *
     * @return An array of matching glyph codes. This may be empty.
     */
    public int[] getGlyphCodesForName(String name) {
        Vector glyphCodes = new Vector();
        for (int i = 0; i < glyphNames.length; i++) {
            if (glyphNames[i] != null && glyphNames[i].equals(name)) {
                glyphCodes.add(new Integer(i));
            }
        }
        int[] glyphCodeArray = new int[glyphCodes.size()];
        for (int i = 0; i < glyphCodes.size(); i++) {
            glyphCodeArray[i] = ((Integer)glyphCodes.elementAt(i)).intValue();
        }
        return glyphCodeArray;
    }

    /**
     * Returns an array of glyph codes (unique ids) of the glyphs with the
     * specified unicode value (there may be more than one).
     *
     * @param unicode The unicode value of the glyph.
     *
     * @return An array of matching glyph codes. This may be empty.
     */
    public int[] getGlyphCodesForUnicode(String unicode) {
        Vector glyphCodes = new Vector();
        for (int i = 0; i < glyphUnicodes.length; i++) {
            if (glyphUnicodes[i] != null && glyphUnicodes[i].equals(unicode)) {
                glyphCodes.add(new Integer(i));
            }
        }
        int[] glyphCodeArray = new int[glyphCodes.size()];
        for (int i = 0; i < glyphCodes.size(); i++) {
            glyphCodeArray[i] = ((Integer)glyphCodes.elementAt(i)).intValue();
        }
        return glyphCodeArray;
    }

    /**
     * Returns true if the glyph language matches the language of the
     * text node to be rendered by this font.  This will be the case
     * if one of the languages in glyphLang matches exactly with the
     * xml:lang attibute of the text node, or if the xml:lang
     * attribute exactly equals a prefix of one glyph languages.
     *
     * @param glyphLang A comma separated list of languages that are associated
     * with a glyph.
     *
     * @return Whether or not the glyph language matches the language of the
     * text node.
     */
    private boolean languageMatches(String glyphLang) {
        if (glyphLang == null || glyphLang.length() == 0) {
            return true;  // will match all languages
        }
        StringTokenizer st = new StringTokenizer(glyphLang, ",");
        while (st.hasMoreTokens()) {
            String s = st.nextToken();
            if (s.equals(language)
               || (s.startsWith(language) && s.length() > language.length()
                   && s.charAt(language.length()) == '-')) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if the glyph orientation matches the orientation of the
     * text node to be rendered by this font.
     *
     * @param glyphOrientation The glyph orientation attribute value. Will be
     * "h", "v" or empty.
     *
     * @return Whether or not the glyph orientation matches the text to be
     * rendered by this font object.
     */
    private boolean orientationMatches(String glyphOrientation) {
        if (glyphOrientation == null || glyphOrientation.length() == 0) {
            return true;
        }
        return glyphOrientation.equals(orientation);
    }


    /**
     * Returns true if the glyph form matches that of the current character in
     * the aci.
     *
     * @param gyphUnicode The unicode value of the glyph.
     * @param glyphForm The arabic-form glyph attribute.
     * @param aci The aci containing the character to check.
     * @param currentIndex The index of the character to check.
     */
    private boolean formMatches(String glyphUnicode,
                                String glyphForm,
                                AttributedCharacterIterator aci,
                                int currentIndex) {
        if (aci == null || glyphForm == null || glyphForm.length() == 0) {
            // there aren't any attributes attached to the text
            // or the glyph doesn't have an arabic form
            return true;
        }

        char c = aci.setIndex(currentIndex);
        Integer form = (Integer)aci.getAttribute
            (GVTAttributedCharacterIterator.TextAttribute.ARABIC_FORM);

        if (form == null || form.equals
            (GVTAttributedCharacterIterator.TextAttribute.ARABIC_NONE)) {
            // the glyph has an arabic form and the current character
            // form is "none" so don't match
            return false;
        }

        // see if c is the start of an arabic ligature
        if (glyphUnicode.length() > 1) {

            boolean matched = true;
            for (int j = 1; j < glyphUnicode.length(); j++) {
                c = aci.next();
                if (glyphUnicode.charAt(j) != c) {
                    matched = false;
                    break;
                }
            }

            // reset the aci
            aci.setIndex(currentIndex);

            if (matched) {

                // ligature matches, now check that the arabic forms are ok
                char lastChar =
                    aci.setIndex(currentIndex + glyphUnicode.length() - 1);
                Integer lastForm = (Integer)aci.getAttribute(
                    GVTAttributedCharacterIterator.TextAttribute.ARABIC_FORM);

                // reset the aci again
                aci.setIndex(currentIndex);

                if (form != null && lastForm != null) {
                    if (form.equals(GVTAttributedCharacterIterator.
                                    TextAttribute.ARABIC_TERMINAL) &&
                        lastForm.equals(GVTAttributedCharacterIterator.
                                        TextAttribute.ARABIC_INITIAL)) {
                        // return true if the glyph form is isolated
                        return glyphForm.equals
                            (SVGConstants.SVG_ISOLATED_VALUE);

                    } else if (form.equals(GVTAttributedCharacterIterator.
                                           TextAttribute.ARABIC_TERMINAL)) {
                        // return true if the glyph form is terminal
                        return glyphForm.equals
                            (SVGConstants.SVG_TERMINAL_VALUE);

                    } else if (form.equals(GVTAttributedCharacterIterator.
                                           TextAttribute.ARABIC_MEDIAL) &&
                               lastForm.equals(GVTAttributedCharacterIterator.
                                               TextAttribute.ARABIC_MEDIAL)) {
                        // return true if the glyph form is medial
                        return glyphForm.equals(SVGConstants.SVG_MEDIAL_VALUE);
                    }
                    // should test for other combos as well here
                }
            }
        }

        if (form.equals(GVTAttributedCharacterIterator.
                        TextAttribute.ARABIC_ISOLATED)) {
            return glyphForm.equals(SVGConstants.SVG_ISOLATED_VALUE);
        }
        if (form.equals(GVTAttributedCharacterIterator.
                        TextAttribute.ARABIC_TERMINAL)) {
            return glyphForm.equals(SVGConstants.SVG_TERMINAL_VALUE);
        }
        if (form.equals(GVTAttributedCharacterIterator.
                        TextAttribute.ARABIC_INITIAL)) {
            return glyphForm.equals(SVGConstants.SVG_INITIAL_VALUE);
        }
        if (form.equals(GVTAttributedCharacterIterator.
                        TextAttribute.ARABIC_MEDIAL)) {
            return glyphForm.equals(SVGConstants.SVG_MEDIAL_VALUE);
        }
        return false;
    }

    /**
     * Indicates whether or not the specified glyph can be displayed by this
     * font.
     *
     * @param name The name of the glyph to check.
     *
     * @return True if the glyph can be displayed.
     */
    public boolean canDisplayGivenName(String name) {
        for (int i = 0; i < glyphNames.length; i++) {
            if (glyphNames[i] != null && glyphNames[i].equals(name)
                && languageMatches(glyphLangs[i])
                && orientationMatches(glyphOrientations[i])) {
                return true;
            }
        }
        return false;
    }


    /**
     * Indicates whether or not the specified character can be
     * displayed by this font.
     *
     * @param c The character to check.
     *
     * @param True if the character can be displayed.
     */
    public boolean canDisplay(char c) {
        for (int i = 0; i < glyphUnicodes.length; i++) {
            if (glyphUnicodes[i].indexOf(c) != -1
                && languageMatches(glyphLangs[i])
                && orientationMatches(glyphOrientations[i])) {
                return true;
            }
        }
        return false;
    }

    /**
     * Checks whether this Font can display the characters in the
     * specified character array starting at start and ending at limit.
     *
     * @param text An array containing the characters to check.
     * @param start The index of the first character to check.
     * @param limit The index of the last character to check.
     *
     * @return The index of the first character it can't display or -1 if
     * it can display the whole string.
     */
    public int canDisplayUpTo(char[] text, int start, int limit) {
        StringCharacterIterator sci =
            new StringCharacterIterator(new String(text));
        return canDisplayUpTo(sci, start, limit);
    }

    /**
     * Checks whether this Font can display the characters in the
     * specified character iterator starting at start and ending at limit.
     *
     * @param iter The iterator containing the characters to check.
     * @param start The index of the first character to check.
     * @param limit The index of the last character to check.
     *
     * @return The index of the first character it can't display or -1 if
     * it can display the whole string.
     */
    public int canDisplayUpTo(CharacterIterator iter, int start, int limit) {

        AttributedCharacterIterator aci = null;
        if (iter instanceof AttributedCharacterIterator) {
            aci = (AttributedCharacterIterator)iter;
        }

        char c = iter.setIndex(start);
        int currentIndex = start;

        while (c != iter.DONE && currentIndex < limit) {

            boolean foundMatchingGlyph = false;

            for (int i = 0; i < glyphUnicodes.length; i++) {
                if (glyphUnicodes[i].indexOf(c) == 0
                    && languageMatches(glyphLangs[i])
                    && orientationMatches(glyphOrientations[i])
                    && formMatches(glyphUnicodes[i], glyphForms[i], 
                                   aci, currentIndex)) {  
                    // found a possible match

                    if (glyphUnicodes[i].length() == 1)  { // not a ligature
                        foundMatchingGlyph = true;
                        break;

                    } else {
                        // glyphCodes[i] is a ligature so try and
                        // match the rest of the glyphCode chars
                        boolean matched = true;
                        for (int j = 1; j < glyphUnicodes[i].length(); j++) {
                            c = iter.next();
                            if (glyphUnicodes[i].charAt(j) != c) {
                                matched = false;
                                break;
                            }
                        }
                        if (matched) { // found a matching ligature!
                            foundMatchingGlyph = true;
                            break;

                        } else {
                            // did not match ligature, keep looking
                            // for another glyph
                            c = iter.setIndex(currentIndex);
                        }
                    }
                }
            }
            if (!foundMatchingGlyph) {
                return currentIndex;
            }
            c = iter.next();
            currentIndex = iter.getIndex();
        }
        return -1;
    }

    /**
     * Checks whether or not this font can display the characters in the
     * specified String.
     *
     * @param str The string containing the characters to check.
     *
     * @return The index of the first character it can't display or -1 if
     * it can display the whole string.
     */
    public int canDisplayUpTo(String str) {
        StringCharacterIterator sci = new StringCharacterIterator(str);
        return canDisplayUpTo(sci, 0, str.length());
    }

    /**
     * Returns a new GVTGlyphVector object for the specified array of
     * characters.
     *
     * @param frc The current font render context.
     * @param chars The array of chars that the glyph vector will represent.
     *
     * @return The new glyph vector.
     */
    public GVTGlyphVector createGlyphVector(FontRenderContext frc,
                                            char[] chars) {
         StringCharacterIterator sci =
             new StringCharacterIterator(new String(chars));
         return createGlyphVector(frc, sci);
    }

    /**
     * Returns a new GVTGlyphVector object for the characters in the
     * specified character iterator.
     *
     * @param frc The current font render context.
     * @param ci The character iterator that the glyph vector will represent.
     *
     * @return The new glyph vector.
     */
    public GVTGlyphVector createGlyphVector(FontRenderContext frc,
                                            CharacterIterator ci) {

        AttributedCharacterIterator aci = null;
        if (ci instanceof AttributedCharacterIterator) {
            aci = (AttributedCharacterIterator)ci;
        }

        Vector glyphs = new Vector();
        char c = ci.first();
        while (c != ci.DONE) {
            boolean foundMatchingGlyph = false;
            for (int i = 0; i < glyphUnicodes.length; i++) {
                if (glyphUnicodes[i].indexOf(c) == 0 &&
                    languageMatches(glyphLangs[i]) &&
                    orientationMatches(glyphOrientations[i]) &&
                    formMatches(glyphUnicodes[i], glyphForms[i], aci,
                                ci.getIndex())) {  // found a possible match

                    if (glyphUnicodes[i].length() == 1)  { // not a ligature
                        Element glyphElement = glyphElements[i];
                        SVGGlyphElementBridge glyphBridge =
                            (SVGGlyphElementBridge)ctx.getBridge
                                (glyphElement);
                        Glyph glyph;
                        if (aci != null) {
                            aci.setIndex(ci.getIndex());
                            Paint fillPaint = (Paint)aci.getAttribute
                                (TextAttribute.FOREGROUND);
                            Paint strokePaint = (Paint)aci.getAttribute
                                (GVTAttributedCharacterIterator.
                                 TextAttribute.STROKE_PAINT);
                            Stroke stroke = (Stroke)aci.getAttribute
                                (GVTAttributedCharacterIterator.
                                 TextAttribute.STROKE);
                            glyph = glyphBridge.createGlyph
                                (ctx, glyphElement, textElement, i, fontSize,
                                 fontFace, fillPaint, strokePaint, stroke);
                        } else {
                             glyph = glyphBridge.createGlyph
                                 (ctx, glyphElement, textElement, i, fontSize,
                                  fontFace, null, null, null);
                        }
                        glyphs.add(glyph);
                        foundMatchingGlyph = true;
                        break;

                    } else {
                        // glyphCodes[i] is a ligature so try and
                        // match the rest of the glyphCode chars
                        int current = ci.getIndex();
                        boolean matched = true;
                        for (int j = 1; j < glyphUnicodes[i].length(); j++) {
                            c = ci.next();
                            if (glyphUnicodes[i].charAt(j) != c) {
                                matched = false;
                                break;
                            }
                        }
                        if (matched) { // found a matching ligature!

                            Element glyphElement = glyphElements[i];
                            SVGGlyphElementBridge glyphBridge
                                = (SVGGlyphElementBridge)ctx.getBridge
                                (glyphElement);
                            Glyph glyph;
                            if (aci != null) {
                                aci.setIndex(ci.getIndex());
                                Paint fillPaint = (Paint)aci.getAttribute
                                    (TextAttribute.FOREGROUND);
                                Paint strokePaint = (Paint)aci.getAttribute
                                    (GVTAttributedCharacterIterator.
                                     TextAttribute.STROKE_PAINT);
                                Stroke stroke = (Stroke)aci.getAttribute
                                    (GVTAttributedCharacterIterator.
                                     TextAttribute.STROKE);
                                glyph = glyphBridge.createGlyph
                                    (ctx, glyphElement, textElement, i,
                                     fontSize, fontFace, fillPaint,
                                     strokePaint, stroke);
                            } else {
                                glyph = glyphBridge.createGlyph
                                    (ctx, glyphElement, textElement, i,
                                     fontSize, fontFace, null, null, null);
                            }
                            glyphs.add(glyph);
                            foundMatchingGlyph = true;
                            break;

                        } else {
                            // did not match ligature, keep looking
                            // for another glyph
                            c = ci.setIndex(current);
                        }
                    }
                }
            }
            if (!foundMatchingGlyph) {
                // add the missing glyph
                SVGGlyphElementBridge glyphBridge =
                    (SVGGlyphElementBridge)ctx.getBridge(missingGlyphElement);
                Glyph glyph;
                if (aci != null) {
                    aci.setIndex(ci.getIndex());
                    Paint fillPaint = (Paint)aci.getAttribute
                        (TextAttribute.FOREGROUND);
                    Paint strokePaint = (Paint)aci.getAttribute
                        (GVTAttributedCharacterIterator.
                         TextAttribute.STROKE_PAINT);
                    Stroke stroke = (Stroke)aci.getAttribute
                        (GVTAttributedCharacterIterator.TextAttribute.STROKE);
                    glyph = glyphBridge.createGlyph
                        (ctx, missingGlyphElement, textElement, -1, fontSize,
                         fontFace, fillPaint, strokePaint, stroke);
                } else {
                    glyph = glyphBridge.createGlyph
                        (ctx, missingGlyphElement, textElement, -1, fontSize,
                         fontFace, null, null, null);
                }
                glyphs.add(glyph);

            }
            c = ci.next();
        }

        // turn the vector of glyphs into an array;
        int numGlyphs = glyphs.size();
        Glyph[] glyphArray = new Glyph[numGlyphs];
        for (int i =0; i < numGlyphs; i++) {
            glyphArray[i] = (Glyph)glyphs.get(i);
        }
        // return a new SVGGVTGlyphVector
        return new SVGGVTGlyphVector(this, glyphArray, frc);
    }

    /**
     * Returns a new GVTGlyphVector object for the glyphs in the
     * the glyph code array.
     *
     * @param frc The current font render context.
     * @param glyphCodes An array containin the ids of the glyphs that
     * the glyph vector will represent.
     *
     * @return The new glyph vector.
     */
    public GVTGlyphVector createGlyphVector(FontRenderContext frc,
                                            int[] glyphCodes,
                                            CharacterIterator ci) {
        // costruct a string from the glyphCodes
        String str = "";
        for (int i = 0; i < glyphCodes.length; i++) {
            str += glyphUnicodes[glyphCodes[i]];
        }
        StringCharacterIterator sci = new StringCharacterIterator(str);
        return createGlyphVector(frc, sci);
    }

    /**
     * Returns a new GVTGlyphVector object for the specified String.
     *
     * @param frc The current font render context.
     * @param str The string that the glyph vector will represent.
     *
     * @return The new glyph vector.
     */
    public GVTGlyphVector createGlyphVector(FontRenderContext frc,
                                            String str) {
        StringCharacterIterator sci = new StringCharacterIterator(str);
        return createGlyphVector(frc, sci);
    }

    /**
     * Creates a new GVTFont object by replicating this font object and
     * applying a new size to it.
     *
     * @param size The size of the new font.
     *
     * @param return The new font object.
     */
    public GVTFont deriveFont(float size) {
        return new SVGGVTFont(size, fontFace, glyphUnicodes, glyphNames,
                              glyphLangs, glyphOrientations, glyphForms, ctx,
                              glyphElements, missingGlyphElement,
                              hkernElements, vkernElements, textElement);
    }

    /**
     * Returns the line metrics for the specified text.
     *
     * @param chars The character array containing the text.
     * @param beginIndex The index of the first character.
     * @param limit The limit of characters.
     * @param frc The current font render context.
     *
     * @return The new GVTLineMetrics object.
     */
    public GVTLineMetrics getLineMetrics(char[] chars, int beginIndex,
                                         int limit,
                                         FontRenderContext frc) {
        StringCharacterIterator sci =
            new StringCharacterIterator(new String(chars));
        return getLineMetrics(sci, beginIndex, limit, frc);
    }

    /**
     * Returns the line metrics for the specified text.
     *
     * @param ci The character iterator containing the text.
     * @param beginIndex The index of the first character.
     * @param limit The limit of characters.
     * @param frc The current font render context.
     *
     * @return The new GVTLineMetrics object.
     */
    public GVTLineMetrics getLineMetrics(CharacterIterator ci, int beginIndex,
                                         int limit, FontRenderContext frc) {
        if (lineMetrics != null) 
            return lineMetrics;

        float fontHeight = fontFace.getUnitsPerEm();
        float scale = fontSize/fontHeight;

        float ascent = fontFace.getAscent() * scale;
        float descent = fontFace.getDescent() * scale;

        float[] baselineOffsets = new float[3];
        baselineOffsets[Font.ROMAN_BASELINE]   = 0;
        baselineOffsets[Font.CENTER_BASELINE]  = (ascent+descent)/2-ascent;
        baselineOffsets[Font.HANGING_BASELINE] = -ascent;

        float stOffset    = fontFace.getStrikethroughPosition() * -scale;
        float stThickness = fontFace.getStrikethroughThickness() * scale;
        float ulOffset    = fontFace.getUnderlinePosition() * scale;
        float ulThickness = fontFace.getUnderlineThickness() * scale;
        float olOffset    = fontFace.getOverlinePosition() * -scale;
        float olThickness = fontFace.getOverlineThickness() * scale;


        lineMetrics = new GVTLineMetrics
            (ascent, Font.ROMAN_BASELINE, baselineOffsets, descent, 
             fontHeight, fontHeight, limit-beginIndex, 
             stOffset, stThickness, 
             ulOffset, ulThickness, 
             olOffset, olThickness);
        return lineMetrics;
    }

    /**
     * Returns the line metrics for the specified text.
     *
     * @param str The string containing the text.
     * @param frc The current font render context.
     *
     * @return The new GVTLineMetrics object.
     */
    public GVTLineMetrics getLineMetrics(String str, FontRenderContext frc) {
        StringCharacterIterator sci = new StringCharacterIterator(str);
        return getLineMetrics(sci, 0, str.length(), frc);
    }

    /**
     * Returns the line metrics for the specified text.
     *
     * @param str The string containing the text.
     * @param beginIndex The index of the first character.
     * @param limit The limit of characters.
     * @param frc The current font render context.
     *
     * @return The new GVTLineMetrics object.
     */
    public GVTLineMetrics getLineMetrics(String str, int beginIndex, int limit,
                                         FontRenderContext frc) {
        StringCharacterIterator sci = new StringCharacterIterator(str);
        return getLineMetrics(sci, beginIndex, limit, frc);
    }

    /**
     * Returns the size of this font.
     *
     * @return The font size.
     */
    public float getSize() {
        return fontSize;
    }

    /**
     * Returns a string representation of this font.
     * This is for debugging purposes only.
     *
     * @return A string representation of this font.
     */
    public String toString() {
        return fontFace.getFamilyName() + " " + fontFace.getFontWeight() + " "
              + fontFace.getFontStyle();
    }
}
