/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package java.awt;

import com.android.internal.awt.AndroidGraphics2D;

import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.LineMetrics;
import java.awt.font.TextAttribute;
import java.awt.font.TransformAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.text.CharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.harmony.awt.gl.font.AndroidGlyphVector;
import org.apache.harmony.awt.gl.font.CommonGlyphVector;
import org.apache.harmony.awt.gl.font.FontPeerImpl;
import org.apache.harmony.awt.gl.font.FontMetricsImpl;
import org.apache.harmony.awt.gl.font.LineMetricsImpl;
import org.apache.harmony.awt.internal.nls.Messages;
import org.apache.harmony.luni.util.NotImplementedException;
import org.apache.harmony.misc.HashCode;

/**
 * The Font class represents fonts for rendering text. This class allow to map
 * characters to glyphs.
 * <p>
 * A glyph is a shape used to render a character or a sequence of characters.
 * For example one character of Latin writing system represented by one glyph,
 * but in complex writing system such as South and South-East Asian there is
 * more complicated correspondence between characters and glyphs.
 * <p>
 * The Font object is identified by two types of names. The logical font name is
 * the name that is used to construct the font. The font name is the name of a
 * particular font face (for example, Arial Bold). The family name is the font's
 * family name that specifies the typographic design across several faces (for
 * example, Arial). In all the Font is identified by three attributes: the
 * family name, the style (such as bold or italic), and the size.
 * 
 * @since Android 1.0
 */
public class Font implements Serializable {

    /**
     * The Constant serialVersionUID.
     */
    private static final long serialVersionUID = -4206021311591459213L;

    // Identity Transform attribute
    /**
     * The Constant IDENTITY_TRANSFORM.
     */
    private static final TransformAttribute IDENTITY_TRANSFORM = new TransformAttribute(
            new AffineTransform());

    /**
     * The Constant PLAIN indicates font's plain style.
     */
    public static final int PLAIN = 0;

    /**
     * The Constant BOLD indicates font's bold style.
     */
    public static final int BOLD = 1;

    /**
     * The Constant ITALIC indicates font's italic style.
     */
    public static final int ITALIC = 2;

    /**
     * The Constant ROMAN_BASELINE indicated roman baseline.
     */
    public static final int ROMAN_BASELINE = 0;

    /**
     * The Constant CENTER_BASELINE indicates center baseline.
     */
    public static final int CENTER_BASELINE = 1;

    /**
     * The Constant HANGING_BASELINE indicates hanging baseline.
     */
    public static final int HANGING_BASELINE = 2;

    /**
     * The Constant TRUETYPE_FONT indicates a font resource of type TRUETYPE.
     */
    public static final int TRUETYPE_FONT = 0;

    /**
     * The Constant TYPE1_FONT indicates a font resource of type TYPE1.
     */
    public static final int TYPE1_FONT = 1;

    /**
     * The Constant LAYOUT_LEFT_TO_RIGHT indicates that text is left to right.
     */
    public static final int LAYOUT_LEFT_TO_RIGHT = 0;

    /**
     * The Constant LAYOUT_RIGHT_TO_LEFT indicates that text is right to left.
     */
    public static final int LAYOUT_RIGHT_TO_LEFT = 1;

    /**
     * The Constant LAYOUT_NO_START_CONTEXT indicates that the text in the char
     * array before the indicated start should not be examined.
     */
    public static final int LAYOUT_NO_START_CONTEXT = 2;

    /**
     * The Constant LAYOUT_NO_LIMIT_CONTEXT indicates that text in the char
     * array after the indicated limit should not be examined.
     */
    public static final int LAYOUT_NO_LIMIT_CONTEXT = 4;

    /**
     * The Constant DEFAULT_FONT.
     */
    static final Font DEFAULT_FONT = new Font("Dialog", Font.PLAIN, 12); //$NON-NLS-1$

    /**
     * The name of the Font.
     */
    protected String name;

    /**
     * The style of the Font.
     */
    protected int style;

    /**
     * The size of the Font.
     */
    protected int size;

    /**
     * The point size of the Font.
     */
    protected float pointSize;

    // Flag if the Font object transformed
    /**
     * The transformed.
     */
    private boolean transformed;

    // Set of font attributes
    /**
     * The requested attributes.
     */
    private Hashtable<Attribute, Object> fRequestedAttributes;

    // font peer object corresponding to this Font
    /**
     * The font peer.
     */
    private transient FontPeerImpl fontPeer;

    // number of glyphs in this Font
    /**
     * The num glyphs.
     */
    private transient int numGlyphs = -1;

    // code for missing glyph for this Font
    /**
     * The missing glyph code.
     */
    private transient int missingGlyphCode = -1;

    /**
     * Writes object to ObjectOutputStream.
     * 
     * @param out
     *            ObjectOutputStream.
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
    }

    /**
     * Reads object from ObjectInputStream object and set native platform
     * dependent fields to default values.
     * 
     * @param in
     *            ObjectInputStream object.
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws ClassNotFoundException
     *             the class not found exception.
     */
    private void readObject(java.io.ObjectInputStream in) throws IOException,
            ClassNotFoundException {
        in.defaultReadObject();

        numGlyphs = -1;
        missingGlyphCode = -1;

    }

    /**
     * Instantiates a new Font with the specified attributes. The Font will be
     * created with default attributes if the attribute's parameter is null.
     * 
     * @param attributes
     *            the attributes to be assigned to the new Font, or null.
     */
    public Font(Map<? extends Attribute, ?> attributes) {
        Object currAttr;

        // Default values are taken from the documentation of the Font class.
        // See Font constructor, decode and getFont sections.

        this.name = "default"; //$NON-NLS-1$
        this.size = 12;
        this.pointSize = 12;
        this.style = Font.PLAIN;

        if (attributes != null) {

            fRequestedAttributes = new Hashtable<Attribute, Object>(attributes);

            currAttr = attributes.get(TextAttribute.SIZE);
            if (currAttr != null) {
                this.pointSize = ((Float)currAttr).floatValue();
                this.size = (int)Math.ceil(this.pointSize);
            }

            currAttr = attributes.get(TextAttribute.POSTURE);
            if (currAttr != null && currAttr.equals(TextAttribute.POSTURE_OBLIQUE)) {
                this.style |= Font.ITALIC;
            }

            currAttr = attributes.get(TextAttribute.WEIGHT);
            if ((currAttr != null)
                    && (((Float)currAttr).floatValue() >= (TextAttribute.WEIGHT_BOLD).floatValue())) {
                this.style |= Font.BOLD;
            }

            currAttr = attributes.get(TextAttribute.FAMILY);
            if (currAttr != null) {
                this.name = (String)currAttr;
            }

            currAttr = attributes.get(TextAttribute.TRANSFORM);
            if (currAttr != null) {
                if (currAttr instanceof TransformAttribute) {
                    this.transformed = !((TransformAttribute)currAttr).getTransform().isIdentity();
                } else if (currAttr instanceof AffineTransform) {
                    this.transformed = !((AffineTransform)currAttr).isIdentity();
                }
            }

        } else {
            fRequestedAttributes = new Hashtable<Attribute, Object>(5);
            fRequestedAttributes.put(TextAttribute.TRANSFORM, IDENTITY_TRANSFORM);

            this.transformed = false;

            fRequestedAttributes.put(TextAttribute.FAMILY, name);

            fRequestedAttributes.put(TextAttribute.SIZE, new Float(this.size));

            if ((this.style & Font.BOLD) != 0) {
                fRequestedAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
            } else {
                fRequestedAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
            }
            if ((this.style & Font.ITALIC) != 0) {
                fRequestedAttributes.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
            } else {
                fRequestedAttributes.put(TextAttribute.POSTURE, TextAttribute.POSTURE_REGULAR);
            }

        }
    }

    /**
     * Instantiates a new Font with the specified name, style and size.
     * 
     * @param name
     *            the name of font.
     * @param style
     *            the style of font.
     * @param size
     *            the size of font.
     */
    public Font(String name, int style, int size) {

        this.name = (name != null) ? name : "Default"; //$NON-NLS-1$
        this.size = (size >= 0) ? size : 0;
        this.style = (style & ~0x03) == 0 ? style : Font.PLAIN;
        this.pointSize = this.size;

        fRequestedAttributes = new Hashtable<Attribute, Object>(5);

        fRequestedAttributes.put(TextAttribute.TRANSFORM, IDENTITY_TRANSFORM);

        this.transformed = false;

        fRequestedAttributes.put(TextAttribute.FAMILY, this.name);
        fRequestedAttributes.put(TextAttribute.SIZE, new Float(this.size));

        if ((this.style & Font.BOLD) != 0) {
            fRequestedAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
        } else {
            fRequestedAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
        }
        if ((this.style & Font.ITALIC) != 0) {
            fRequestedAttributes.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
        } else {
            fRequestedAttributes.put(TextAttribute.POSTURE, TextAttribute.POSTURE_REGULAR);
        }
    }

    /**
     * Returns true if this Font has a glyph for the specified character.
     * 
     * @param c
     *            the character.
     * @return true if this Font has a glyph for the specified character, false
     *         otherwise.
     */
    public boolean canDisplay(char c) {
        FontPeerImpl peer = (FontPeerImpl)this.getPeer();
        return peer.canDisplay(c);
    }

    /**
     * Returns true if the Font can display the characters of the the specified
     * text from the specified start position to the specified limit position.
     * 
     * @param text
     *            the text.
     * @param start
     *            the start offset (in the character array).
     * @param limit
     *            the limit offset (in the character array).
     * @return the a character's position in the text that this Font can not
     *         display, or -1 if this Font can display all characters in this
     *         text.
     */
    public int canDisplayUpTo(char[] text, int start, int limit) {
        int st = start;
        int result;
        while ((st < limit) && canDisplay(text[st])) {
            st++;
        }

        if (st == limit) {
            result = -1;
        } else {
            result = st;
        }

        return result;
    }

    /**
     * Returns true if the Font can display the characters of the the specified
     * CharacterIterator from the specified start position and the specified
     * limit position.
     * 
     * @param iter
     *            the CharacterIterator.
     * @param start
     *            the start offset.
     * @param limit
     *            the limit offset.
     * @return the a character's position in the CharacterIterator that this
     *         Font can not display, or -1 if this Font can display all
     *         characters in this text.
     */
    public int canDisplayUpTo(CharacterIterator iter, int start, int limit) {
        int st = start;
        char c = iter.setIndex(start);
        int result;

        while ((st < limit) && (canDisplay(c))) {
            st++;
            c = iter.next();
        }
        if (st == limit) {
            result = -1;
        } else {
            result = st;
        }

        return result;
    }

    /**
     * Returns true if this Font can display a specified String.
     * 
     * @param str
     *            the String.
     * @return the a character's position in the String that this Font can not
     *         display, or -1 if this Font can display all characters in this
     *         text.
     */
    public int canDisplayUpTo(String str) {
        char[] chars = str.toCharArray();
        return canDisplayUpTo(chars, 0, chars.length);
    }

    /**
     * Creates a GlyphVector of associating characters to glyphs based on the
     * Unicode map of this Font.
     * 
     * @param frc
     *            the FontRenderContext.
     * @param chars
     *            the characters array.
     * @return the GlyphVector of associating characters to glyphs based on the
     *         Unicode map of this Font.
     */
    public GlyphVector createGlyphVector(FontRenderContext frc, char[] chars) {
        return new AndroidGlyphVector(chars, frc, this, 0);
    }

    /**
     * Creates a GlyphVector of associating characters contained in the
     * specified CharacterIterator to glyphs based on the Unicode map of this
     * Font.
     * 
     * @param frc
     *            the FontRenderContext.
     * @param iter
     *            the CharacterIterator.
     * @return the GlyphVector of associating characters contained in the
     *         specified CharacterIterator to glyphs based on the Unicode map of
     *         this Font.
     */
    public GlyphVector createGlyphVector(FontRenderContext frc, CharacterIterator iter) {
        throw new RuntimeException("Not implemented!"); //$NON-NLS-1$    
    }

    /**
     * Creates a GlyphVector of associating characters to glyphs based on the
     * Unicode map of this Font.
     * 
     * @param frc
     *            the FontRenderContext.
     * @param glyphCodes
     *            the specified integer array of glyph codes.
     * @return the GlyphVector of associating characters to glyphs based on the
     *         Unicode map of this Font.
     * @throws NotImplementedException
     *             if this method is not implemented by a subclass.
     */
    public GlyphVector createGlyphVector(FontRenderContext frc, int[] glyphCodes)
            throws org.apache.harmony.luni.util.NotImplementedException {
        throw new RuntimeException("Not implemented!"); //$NON-NLS-1$
    }

    /**
     * Creates a GlyphVector of associating characters to glyphs based on the
     * Unicode map of this Font.
     * 
     * @param frc
     *            the FontRenderContext.
     * @param str
     *            the specified String.
     * @return the GlyphVector of associating characters to glyphs based on the
     *         Unicode map of this Font.
     */
    public GlyphVector createGlyphVector(FontRenderContext frc, String str) {
        return new AndroidGlyphVector(str.toCharArray(), frc, this, 0);

    }

    /**
     * Returns the font style constant value corresponding to one of the font
     * style names ("BOLD", "ITALIC", "BOLDITALIC"). This method returns
     * Font.PLAIN if the argument is not one of the predefined style names.
     * 
     * @param fontStyleName
     *            font style name.
     * @return font style constant value corresponding to the font style name
     *         specified.
     */
    private static int getFontStyle(String fontStyleName) {
        int result = Font.PLAIN;

        if (fontStyleName.toUpperCase().equals("BOLDITALIC")) { //$NON-NLS-1$
            result = Font.BOLD | Font.ITALIC;
        } else if (fontStyleName.toUpperCase().equals("BOLD")) { //$NON-NLS-1$
            result = Font.BOLD;
        } else if (fontStyleName.toUpperCase().equals("ITALIC")) { //$NON-NLS-1$
            result = Font.ITALIC;
        }

        return result;
    }

    /**
     * Decodes the specified string which described the Font. The string should
     * have the following format: fontname-style-pointsize. The style can be
     * PLAIN, BOLD, BOLDITALIC, or ITALIC.
     * 
     * @param str
     *            the string which describes the font.
     * @return the Font from the specified string.
     */
    public static Font decode(String str) {
        // XXX: Documentation doesn't describe all cases, e.g. fonts face names
        // with
        // symbols that are suggested as delimiters in the documentation.
        // In this decode implementation only ***-***-*** format is used with
        // '-'
        // as the delimiter to avoid unexpected parse results of font face names
        // with spaces.

        if (str == null) {
            return DEFAULT_FONT;
        }

        StringTokenizer strTokens;
        String delim = "-"; //$NON-NLS-1$
        String substr;

        int fontSize = DEFAULT_FONT.size;
        int fontStyle = DEFAULT_FONT.style;
        String fontName = DEFAULT_FONT.name;

        strTokens = new StringTokenizer(str.trim(), delim);

        // Font Name
        if (strTokens.hasMoreTokens()) {
            fontName = strTokens.nextToken(); // first token is the font name
        }

        // Font Style or Size (if the style is undefined)
        if (strTokens.hasMoreTokens()) {
            substr = strTokens.nextToken();

            try {
                // if second token is the font size
                fontSize = Integer.parseInt(substr);
            } catch (NumberFormatException e) {
                // then second token is the font style
                fontStyle = getFontStyle(substr);
            }

        }

        // Font Size
        if (strTokens.hasMoreTokens()) {
            try {
                fontSize = Integer.parseInt(strTokens.nextToken());
            } catch (NumberFormatException e) {
            }
        }

        return new Font(fontName, fontStyle, fontSize);
    }

    /**
     * Performs the specified affine transform to the Font and returns a new
     * Font.
     * 
     * @param trans
     *            the AffineTransform.
     * @return the Font object.
     * @throws IllegalArgumentException
     *             if affine transform parameter is null.
     */
    @SuppressWarnings("unchecked")
    public Font deriveFont(AffineTransform trans) {

        if (trans == null) {
            // awt.94=transform can not be null
            throw new IllegalArgumentException(Messages.getString("awt.94")); //$NON-NLS-1$
        }

        Hashtable<Attribute, Object> derivefRequestedAttributes = (Hashtable<Attribute, Object>)fRequestedAttributes
                .clone();

        derivefRequestedAttributes.put(TextAttribute.TRANSFORM, new TransformAttribute(trans));

        return new Font(derivefRequestedAttributes);

    }

    /**
     * Returns a new Font that is a copy of the current Font modified so that
     * the size is the specified size.
     * 
     * @param size
     *            the size of font.
     * @return the Font object.
     */
    @SuppressWarnings("unchecked")
    public Font deriveFont(float size) {
        Hashtable<Attribute, Object> derivefRequestedAttributes = (Hashtable<Attribute, Object>)fRequestedAttributes
                .clone();
        derivefRequestedAttributes.put(TextAttribute.SIZE, new Float(size));
        return new Font(derivefRequestedAttributes);
    }

    /**
     * Returns a new Font that is a copy of the current Font modified so that
     * the style is the specified style.
     * 
     * @param style
     *            the style of font.
     * @return the Font object.
     */
    @SuppressWarnings("unchecked")
    public Font deriveFont(int style) {
        Hashtable<Attribute, Object> derivefRequestedAttributes = (Hashtable<Attribute, Object>)fRequestedAttributes
                .clone();

        if ((style & Font.BOLD) != 0) {
            derivefRequestedAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
        } else if (derivefRequestedAttributes.get(TextAttribute.WEIGHT) != null) {
            derivefRequestedAttributes.remove(TextAttribute.WEIGHT);
        }

        if ((style & Font.ITALIC) != 0) {
            derivefRequestedAttributes.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
        } else if (derivefRequestedAttributes.get(TextAttribute.POSTURE) != null) {
            derivefRequestedAttributes.remove(TextAttribute.POSTURE);
        }

        return new Font(derivefRequestedAttributes);
    }

    /**
     * Returns a new Font that is a copy of the current Font modified to match
     * the specified style and with the specified affine transform applied to
     * its glyphs.
     * 
     * @param style
     *            the style of font.
     * @param trans
     *            the AffineTransform.
     * @return the Font object.
     */
    @SuppressWarnings("unchecked")
    public Font deriveFont(int style, AffineTransform trans) {

        if (trans == null) {
            // awt.94=transform can not be null
            throw new IllegalArgumentException(Messages.getString("awt.94")); //$NON-NLS-1$
        }
        Hashtable<Attribute, Object> derivefRequestedAttributes = (Hashtable<Attribute, Object>)fRequestedAttributes
                .clone();

        if ((style & BOLD) != 0) {
            derivefRequestedAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
        } else if (derivefRequestedAttributes.get(TextAttribute.WEIGHT) != null) {
            derivefRequestedAttributes.remove(TextAttribute.WEIGHT);
        }

        if ((style & ITALIC) != 0) {
            derivefRequestedAttributes.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
        } else if (derivefRequestedAttributes.get(TextAttribute.POSTURE) != null) {
            derivefRequestedAttributes.remove(TextAttribute.POSTURE);
        }
        derivefRequestedAttributes.put(TextAttribute.TRANSFORM, new TransformAttribute(trans));

        return new Font(derivefRequestedAttributes);
    }

    /**
     * Returns a new Font that is a copy of the current Font modified so that
     * the size and style are the specified size and style.
     * 
     * @param style
     *            the style of font.
     * @param size
     *            the size of font.
     * @return the Font object.
     */
    @SuppressWarnings("unchecked")
    public Font deriveFont(int style, float size) {
        Hashtable<Attribute, Object> derivefRequestedAttributes = (Hashtable<Attribute, Object>)fRequestedAttributes
                .clone();

        if ((style & BOLD) != 0) {
            derivefRequestedAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
        } else if (derivefRequestedAttributes.get(TextAttribute.WEIGHT) != null) {
            derivefRequestedAttributes.remove(TextAttribute.WEIGHT);
        }

        if ((style & ITALIC) != 0) {
            derivefRequestedAttributes.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
        } else if (derivefRequestedAttributes.get(TextAttribute.POSTURE) != null) {
            derivefRequestedAttributes.remove(TextAttribute.POSTURE);
        }

        derivefRequestedAttributes.put(TextAttribute.SIZE, new Float(size));
        return new Font(derivefRequestedAttributes);

    }

    /**
     * Returns a new Font object with a new set of font attributes.
     * 
     * @param attributes
     *            the map of attributes.
     * @return the Font.
     */
    @SuppressWarnings("unchecked")
    public Font deriveFont(Map<? extends Attribute, ?> attributes) {
        Attribute[] avalAttributes = this.getAvailableAttributes();

        Hashtable<Attribute, Object> derivefRequestedAttributes = (Hashtable<Attribute, Object>)fRequestedAttributes
                .clone();
        Object currAttribute;
        for (Attribute element : avalAttributes) {
            currAttribute = attributes.get(element);
            if (currAttribute != null) {
                derivefRequestedAttributes.put(element, currAttribute);
            }
        }
        return new Font(derivefRequestedAttributes);
    }

    /**
     * Compares the specified Object with the current Font.
     * 
     * @param obj
     *            the Object to be compared.
     * @return true, if the specified Object is an instance of Font with the
     *         same family, size, and style as this Font, false otherwise.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }

        if (obj != null) {
            try {
                Font font = (Font)obj;

                return ((this.style == font.style) && (this.size == font.size)
                        && this.name.equals(font.name) && (this.pointSize == font.pointSize) && (this
                        .getTransform()).equals(font.getTransform()));
            } catch (ClassCastException e) {
            }
        }

        return false;
    }

    /**
     * Gets the map of font's attributes.
     * 
     * @return the map of font's attributes.
     */
    @SuppressWarnings("unchecked")
    public Map<TextAttribute, ?> getAttributes() {
        return (Map<TextAttribute, ?>)fRequestedAttributes.clone();
    }

    /**
     * Gets the keys of all available attributes.
     * 
     * @return the keys array of all available attributes.
     */
    public Attribute[] getAvailableAttributes() {
        Attribute[] attrs = {
                TextAttribute.FAMILY, TextAttribute.POSTURE, TextAttribute.SIZE,
                TextAttribute.TRANSFORM, TextAttribute.WEIGHT, TextAttribute.SUPERSCRIPT,
                TextAttribute.WIDTH
        };
        return attrs;
    }

    /**
     * Gets the baseline for this character.
     * 
     * @param c
     *            the character.
     * @return the baseline for this character.
     */
    public byte getBaselineFor(char c) {
        // TODO: implement using TT BASE table data
        return 0;
    }

    /**
     * Gets the family name of the Font.
     * 
     * @return the family name of the Font.
     */
    public String getFamily() {
        if (fRequestedAttributes != null) {
            fRequestedAttributes.get(TextAttribute.FAMILY);
        }
        return null;
    }

    /**
     * Returns the family name of this Font associated with the specified
     * locale.
     * 
     * @param l
     *            the locale.
     * @return the family name of this Font associated with the specified
     *         locale.
     */
    public String getFamily(Locale l) {
        if (l == null) {
            // awt.01='{0}' parameter is null
            throw new NullPointerException(Messages.getString("awt.01", "Locale")); //$NON-NLS-1$ //$NON-NLS-2$ 
        }
        return getFamily();
    }

    /**
     * Gets a Font with the specified attribute set.
     * 
     * @param attributes
     *            the attributes to be assigned to the new Font.
     * @return the Font.
     */
    public static Font getFont(Map<? extends Attribute, ?> attributes) {
        Font fnt = (Font)attributes.get(TextAttribute.FONT);
        if (fnt != null) {
            return fnt;
        }
        return new Font(attributes);
    }

    /**
     * Gets a Font object from the system properties list with the specified
     * name or returns the specified Font if there is no such property.
     * 
     * @param sp
     *            the specified property name.
     * @param f
     *            the Font.
     * @return the Font object from the system properties list with the
     *         specified name or the specified Font if there is no such
     *         property.
     */
    public static Font getFont(String sp, Font f) {
        String pr = System.getProperty(sp);
        if (pr == null) {
            return f;
        }
        return decode(pr);
    }

    /**
     * Gets a Font object from the system properties list with the specified
     * name.
     * 
     * @param sp
     *            the system property name.
     * @return the Font, or null if there is no such property with the specified
     *         name.
     */
    public static Font getFont(String sp) {
        return getFont(sp, null);
    }

    /**
     * Gets the font name.
     * 
     * @return the font name.
     */
    public String getFontName() {
        if (fRequestedAttributes != null) {
            fRequestedAttributes.get(TextAttribute.FAMILY);
        }
        return null;
    }

    /**
     * Returns the font name associated with the specified locale.
     * 
     * @param l
     *            the locale.
     * @return the font name associated with the specified locale.
     */
    public String getFontName(Locale l) {
        return getFamily();
    }

    /**
     * Returns a LineMetrics object created with the specified parameters.
     * 
     * @param chars
     *            the chars array.
     * @param start
     *            the start offset.
     * @param end
     *            the end offset.
     * @param frc
     *            the FontRenderContext.
     * @return the LineMetrics for the specified parameters.
     */
    public LineMetrics getLineMetrics(char[] chars, int start, int end, FontRenderContext frc) {
        if (frc == null) {
            // awt.00=FontRenderContext is null
            throw new NullPointerException(Messages.getString("awt.00")); //$NON-NLS-1$
        }

        // FontMetrics fm = AndroidGraphics2D.getInstance().getFontMetrics();
        FontMetrics fm = new FontMetricsImpl(this);
        float[] fmet = {
                fm.getAscent(), fm.getDescent(), fm.getLeading()
        };
        return new LineMetricsImpl(chars.length, fmet, null);
    }

    /**
     * Returns a LineMetrics object created with the specified parameters.
     * 
     * @param iter
     *            the CharacterIterator.
     * @param start
     *            the start offset.
     * @param end
     *            the end offset.
     * @param frc
     *            the FontRenderContext.
     * @return the LineMetrics for the specified parameters.
     */
    public LineMetrics getLineMetrics(CharacterIterator iter, int start, int end,
            FontRenderContext frc) {

        if (frc == null) {
            // awt.00=FontRenderContext is null
            throw new NullPointerException(Messages.getString("awt.00")); //$NON-NLS-1$
        }

        String resultString;
        int iterCount;

        iterCount = end - start;
        if (iterCount < 0) {
            resultString = ""; //$NON-NLS-1$
        } else {
            char[] chars = new char[iterCount];
            int i = 0;
            for (char c = iter.setIndex(start); c != CharacterIterator.DONE && (i < iterCount); c = iter
                    .next()) {
                chars[i] = c;
                i++;
            }
            resultString = new String(chars);
        }
        return this.getLineMetrics(resultString, frc);
    }

    /**
     * Returns a LineMetrics object created with the specified parameters.
     * 
     * @param str
     *            the String.
     * @param frc
     *            the FontRenderContext.
     * @return the LineMetrics for the specified parameters.
     */
    public LineMetrics getLineMetrics(String str, FontRenderContext frc) {
        // FontMetrics fm = AndroidGraphics2D.getInstance().getFontMetrics();
        FontMetrics fm = new FontMetricsImpl(this);
        float[] fmet = {
                fm.getAscent(), fm.getDescent(), fm.getLeading()
        };
        // Log.i("FONT FMET", fmet.toString());
        return new LineMetricsImpl(str.length(), fmet, null);

    }

    /**
     * Returns a LineMetrics object created with the specified parameters.
     * 
     * @param str
     *            the String.
     * @param start
     *            the start offset.
     * @param end
     *            the end offset.
     * @param frc
     *            the FontRenderContext.
     * @return the LineMetrics for the specified parameters.
     */
    public LineMetrics getLineMetrics(String str, int start, int end, FontRenderContext frc) {
        return this.getLineMetrics(str.substring(start, end), frc);
    }

    /**
     * Gets the logical bounds of the specified String in the specified
     * FontRenderContext. The logical bounds contains the origin, ascent,
     * advance, and height.
     * 
     * @param ci
     *            the specified CharacterIterator.
     * @param start
     *            the start offset.
     * @param end
     *            the end offset.
     * @param frc
     *            the FontRenderContext.
     * @return a Rectangle2D object.
     */
    public Rectangle2D getStringBounds(CharacterIterator ci, int start, int end,
            FontRenderContext frc) {
        int first = ci.getBeginIndex();
        int finish = ci.getEndIndex();
        char[] chars;

        if (start < first) {
            // awt.95=Wrong start index: {0}
            throw new IndexOutOfBoundsException(Messages.getString("awt.95", start)); //$NON-NLS-1$
        }
        if (end > finish) {
            // awt.96=Wrong finish index: {0}
            throw new IndexOutOfBoundsException(Messages.getString("awt.96", end)); //$NON-NLS-1$
        }
        if (start > end) {
            // awt.97=Wrong range length: {0}
            throw new IndexOutOfBoundsException(Messages.getString("awt.97", //$NON-NLS-1$
                    (end - start)));
        }

        if (frc == null) {
            throw new NullPointerException(Messages.getString("awt.00")); //$NON-NLS-1$
        }

        chars = new char[end - start];

        ci.setIndex(start);
        for (int i = 0; i < chars.length; i++) {
            chars[i] = ci.current();
            ci.next();
        }

        return this.getStringBounds(chars, 0, chars.length, frc);

    }

    /**
     * Gets the logical bounds of the specified String in the specified
     * FontRenderContext. The logical bounds contains the origin, ascent,
     * advance, and height.
     * 
     * @param str
     *            the specified String.
     * @param frc
     *            the FontRenderContext.
     * @return a Rectangle2D object.
     */
    public Rectangle2D getStringBounds(String str, FontRenderContext frc) {
        char[] chars = str.toCharArray();
        return this.getStringBounds(chars, 0, chars.length, frc);

    }

    /**
     * Gets the logical bounds of the specified String in the specified
     * FontRenderContext. The logical bounds contains the origin, ascent,
     * advance, and height.
     * 
     * @param str
     *            the specified String.
     * @param start
     *            the start offset.
     * @param end
     *            the end offset.
     * @param frc
     *            the FontRenderContext.
     * @return a Rectangle2D object.
     */
    public Rectangle2D getStringBounds(String str, int start, int end, FontRenderContext frc) {

        return this.getStringBounds((str.substring(start, end)), frc);
    }

    /**
     * Gets the logical bounds of the specified String in the specified
     * FontRenderContext. The logical bounds contains the origin, ascent,
     * advance, and height.
     * 
     * @param chars
     *            the specified character array.
     * @param start
     *            the start offset.
     * @param end
     *            the end offset.
     * @param frc
     *            the FontRenderContext.
     * @return a Rectangle2D object.
     */
    public Rectangle2D getStringBounds(char[] chars, int start, int end, FontRenderContext frc) {
        if (start < 0) {
            // awt.95=Wrong start index: {0}
            throw new IndexOutOfBoundsException(Messages.getString("awt.95", start)); //$NON-NLS-1$
        }
        if (end > chars.length) {
            // awt.96=Wrong finish index: {0}
            throw new IndexOutOfBoundsException(Messages.getString("awt.96", end)); //$NON-NLS-1$
        }
        if (start > end) {
            // awt.97=Wrong range length: {0}
            throw new IndexOutOfBoundsException(Messages.getString("awt.97", //$NON-NLS-1$
                    (end - start)));
        }

        if (frc == null) {
            throw new NullPointerException(Messages.getString("awt.00")); //$NON-NLS-1$
        }

        FontPeerImpl peer = (FontPeerImpl)this.getPeer();

        final int TRANSFORM_MASK = AffineTransform.TYPE_GENERAL_ROTATION
                | AffineTransform.TYPE_GENERAL_TRANSFORM;
        Rectangle2D bounds;

        AffineTransform transform = getTransform();

        // XXX: for transforms where an angle between basis vectors is not 90
        // degrees Rectanlge2D class doesn't fit as Logical bounds.
        if ((transform.getType() & TRANSFORM_MASK) == 0) {
            int width = 0;
            for (int i = start; i < end; i++) {
                width += peer.charWidth(chars[i]);
            }
            // LineMetrics nlm = peer.getLineMetrics();

            LineMetrics nlm = getLineMetrics(chars, start, end, frc);

            bounds = transform.createTransformedShape(
                    new Rectangle2D.Float(0, -nlm.getAscent(), width, nlm.getHeight()))
                    .getBounds2D();
        } else {
            int len = end - start;
            char[] subChars = new char[len];
            net.kdt.pojavlaunch.SystemCrackResolver.arraycopy(chars, start, subChars, 0, len);
            bounds = createGlyphVector(frc, subChars).getLogicalBounds();
        }
        return bounds;
    }

    /**
     * Gets the character's maximum bounds as defined in the specified
     * FontRenderContext.
     * 
     * @param frc
     *            the FontRenderContext.
     * @return the character's maximum bounds.
     */
    public Rectangle2D getMaxCharBounds(FontRenderContext frc) {
        if (frc == null) {
            // awt.00=FontRenderContext is null
            throw new NullPointerException(Messages.getString("awt.00")); //$NON-NLS-1$ 
        }

        FontPeerImpl peer = (FontPeerImpl)this.getPeer();

        Rectangle2D bounds = peer.getMaxCharBounds(frc);
        AffineTransform transform = getTransform();
        // !! Documentation doesn't describe meaning of max char bounds
        // for the fonts that have rotate transforms. For all transforms
        // returned bounds are the bounds of transformed maxCharBounds
        // Rectangle2D that corresponds to the font with identity transform.
        // TODO: resolve this issue to return correct bounds
        bounds = transform.createTransformedShape(bounds).getBounds2D();

        return bounds;
    }

    /**
     * Returns a new GlyphVector object performing full layout of the text.
     * 
     * @param frc
     *            the FontRenderContext.
     * @param chars
     *            the character array to be layout.
     * @param start
     *            the start offset of the text to use for the GlyphVector.
     * @param count
     *            the count of characters to use for the GlyphVector.
     * @param flags
     *            the flag indicating text direction: LAYOUT_RIGHT_TO_LEFT,
     *            LAYOUT_LEFT_TO_RIGHT.
     * @return the GlyphVector.
     */
    public GlyphVector layoutGlyphVector(FontRenderContext frc, char[] chars, int start, int count,
            int flags) {
        // TODO: implement method for bidirectional text.
        // At the moment only LTR and RTL texts supported.
        if (start < 0) {
            // awt.95=Wrong start index: {0}
            throw new ArrayIndexOutOfBoundsException(Messages.getString("awt.95", //$NON-NLS-1$
                    start));
        }

        if (count < 0) {
            // awt.98=Wrong count value, can not be negative: {0}
            throw new ArrayIndexOutOfBoundsException(Messages.getString("awt.98", //$NON-NLS-1$
                    count));
        }

        if (start + count > chars.length) {
            // awt.99=Wrong [start + count] is out of range: {0}
            throw new ArrayIndexOutOfBoundsException(Messages.getString("awt.99", //$NON-NLS-1$
                    (start + count)));
        }

        char[] out = new char[count];
        net.kdt.pojavlaunch.SystemCrackResolver.arraycopy(chars, start, out, 0, count);

        return new CommonGlyphVector(out, frc, this, flags);
    }

    /**
     * Returns the String representation of this Font.
     * 
     * @return the String representation of this Font.
     */
    @Override
    public String toString() {
        String stl = "plain"; //$NON-NLS-1$
        String result;

        if (this.isBold() && this.isItalic()) {
            stl = "bolditalic"; //$NON-NLS-1$
        }
        if (this.isBold() && !this.isItalic()) {
            stl = "bold"; //$NON-NLS-1$
        }

        if (!this.isBold() && this.isItalic()) {
            stl = "italic"; //$NON-NLS-1$
        }

        result = this.getClass().getName() + "[family=" + this.getFamily() + //$NON-NLS-1$
                ",name=" + this.name + //$NON-NLS-1$
                ",style=" + stl + //$NON-NLS-1$
                ",size=" + this.size + "]"; //$NON-NLS-1$ //$NON-NLS-2$
        return result;
    }

    /**
     * Gets the postscript name of this Font.
     * 
     * @return the postscript name of this Font.
     */
    public String getPSName() {
        FontPeerImpl peer = (FontPeerImpl)this.getPeer();
        return peer.getPSName();
    }

    /**
     * Gets the logical name of this Font.
     * 
     * @return the logical name of this Font.
     */
    public String getName() {
        return (this.name);
    }

    /**
     * Gets the peer of this Font.
     * 
     * @return the peer of this Font.
     * @deprecated Font rendering is platform independent now.
     */
    @Deprecated
    public java.awt.peer.FontPeer getPeer() {
        if (fontPeer == null) {
            fontPeer = (FontPeerImpl)Toolkit.getDefaultToolkit().getGraphicsFactory().getFontPeer(
                    this);
        }
        return fontPeer;

    }

    /**
     * Gets the transform acting on this Font (from the Font's attributes).
     * 
     * @return the transformation of this Font.
     */
    public AffineTransform getTransform() {
        Object transform = fRequestedAttributes.get(TextAttribute.TRANSFORM);

        if (transform != null) {
            if (transform instanceof TransformAttribute) {
                return ((TransformAttribute)transform).getTransform();
            }
            if (transform instanceof AffineTransform) {
                return new AffineTransform((AffineTransform)transform);
            }
        } else {
            transform = new AffineTransform();
        }
        return (AffineTransform)transform;

    }

    /**
     * Checks if this font is transformed or not.
     * 
     * @return true, if this font is transformed, false otherwise.
     */
    public boolean isTransformed() {
        return this.transformed;
    }

    /**
     * Checks if this font has plain style or not.
     * 
     * @return true, if this font has plain style, false otherwise.
     */
    public boolean isPlain() {
        return (this.style == PLAIN);
    }

    /**
     * Checks if this font has italic style or not.
     * 
     * @return true, if this font has italic style, false otherwise.
     */
    public boolean isItalic() {
        return (this.style & ITALIC) != 0;
    }

    /**
     * Checks if this font has bold style or not.
     * 
     * @return true, if this font has bold style, false otherwise.
     */
    public boolean isBold() {
        return (this.style & BOLD) != 0;
    }

    /**
     * Returns true if this Font has uniform line metrics.
     * 
     * @return true if this Font has uniform line metrics, false otherwise.
     */
    public boolean hasUniformLineMetrics() {
        FontPeerImpl peer = (FontPeerImpl)this.getPeer();
        return peer.hasUniformLineMetrics();
    }

    /**
     * Returns hash code of this Font object.
     * 
     * @return the hash code of this Font object.
     */
    @Override
    public int hashCode() {
        HashCode hash = new HashCode();

        hash.append(this.name);
        hash.append(this.style);
        hash.append(this.size);

        return hash.hashCode();
    }

    /**
     * Gets the style of this Font.
     * 
     * @return the style of this Font.
     */
    public int getStyle() {
        return this.style;
    }

    /**
     * Gets the size of this Font.
     * 
     * @return the size of this Font.
     */
    public int getSize() {
        return this.size;
    }

    /**
     * Gets the number of glyphs for this Font.
     * 
     * @return the number of glyphs for this Font.
     */
    public int getNumGlyphs() {
        if (numGlyphs == -1) {
            FontPeerImpl peer = (FontPeerImpl)this.getPeer();
            this.numGlyphs = peer.getNumGlyphs();
        }
        return this.numGlyphs;
    }

    /**
     * Gets the glyphCode which is used as default glyph when this Font does not
     * have a glyph for a specified Unicode.
     * 
     * @return the missing glyph code.
     */
    public int getMissingGlyphCode() {
        if (missingGlyphCode == -1) {
            FontPeerImpl peer = (FontPeerImpl)this.getPeer();
            this.missingGlyphCode = peer.getMissingGlyphCode();
        }
        return this.missingGlyphCode;
    }

    /**
     * Gets the float value of font's size.
     * 
     * @return the float value of font's size.
     */
    public float getSize2D() {
        return this.pointSize;
    }

    /**
     * Gets the italic angle of this Font.
     * 
     * @return the italic angle of this Font.
     */
    public float getItalicAngle() {
        FontPeerImpl peer = (FontPeerImpl)this.getPeer();
        return peer.getItalicAngle();
    }

    /**
     * Creates the font with the specified font format and font file.
     * 
     * @param fontFormat
     *            the font format.
     * @param fontFile
     *            the file object represented the input data for the font.
     * @return the Font.
     * @throws FontFormatException
     *             is thrown if fontFile does not contain the required font
     *             tables for the specified format.
     * @throws IOException
     *             signals that an I/O exception has occurred.
     */
    public static Font createFont(int fontFormat, File fontFile) throws FontFormatException,
            IOException {
        // ???AWT not supported
        InputStream is = new FileInputStream(fontFile);
        try {
            return createFont(fontFormat, is);
        } finally {
            is.close();
        }
    }

    /**
     * Creates the font with the specified font format and input stream.
     * 
     * @param fontFormat
     *            the font format.
     * @param fontStream
     *            the input stream represented input data for the font.
     * @return the Font.
     * @throws FontFormatException
     *             is thrown if fontFile does not contain the required font
     *             tables for the specified format.
     * @throws IOException
     *             signals that an I/O exception has occurred.
     */
    public static Font createFont(int fontFormat, InputStream fontStream)
            throws FontFormatException, IOException {

        // ???AWT not supported

        BufferedInputStream buffStream;
        int bRead = 0;
        int size = 8192;
        // memory page size, for the faster reading
        byte buf[] = new byte[size];

        if (fontFormat != TRUETYPE_FONT) { // awt.9A=Unsupported font format
            throw new IllegalArgumentException(Messages.getString("awt.9A")); //$NON-NLS-1$ 
        }

        /* Get font file in system-specific directory */

        File fontFile = Toolkit.getDefaultToolkit().getGraphicsFactory().getFontManager()
                .getTempFontFile();

        // BEGIN android-modified
        buffStream = new BufferedInputStream(fontStream, 8192);
        // END android-modified
        FileOutputStream fOutStream = new FileOutputStream(fontFile);

        bRead = buffStream.read(buf, 0, size);

        while (bRead != -1) {
            fOutStream.write(buf, 0, bRead);
            bRead = buffStream.read(buf, 0, size);
        }

        buffStream.close();
        fOutStream.close();

        Font font = null;

        font = Toolkit.getDefaultToolkit().getGraphicsFactory().embedFont(
                fontFile.getAbsolutePath());
        if (font == null) { // awt.9B=Can't create font - bad font data
            throw new FontFormatException(Messages.getString("awt.9B")); //$NON-NLS-1$
        }
        return font;
    }

}
