// Copyright 2001-2006, FreeHEP.

package com.cherry.lib.doc.office.thirdpart.emf.font;

import java.io.IOException;

import com.cherry.lib.doc.office.java.awt.Rectangle;
import com.cherry.lib.doc.office.java.awt.geom.AffineTransform;
import com.cherry.lib.doc.office.java.awt.geom.GeneralPath;

/**
 * GLYPH Table.
 * 
 * @author Simon Fischer
 * @version $Id: TTFGlyfTable.java 8584 2006-08-10 23:06:37Z duns $
 */
public class TTFGlyfTable extends TTFVersionTable
{

    /**
     * If this variable is set to false then the glyphs will not be read until
     * they are retrieved with <tt>getGlyph(int)</tt>.
     */
    private static final boolean READ_GLYPHS = false;

    public abstract class Glyph
    {

        public int xMin, yMin, xMax, yMax;

        public abstract String getType();

        public abstract GeneralPath getShape();

        public void read() throws IOException
        {
            xMin = ttf.readFWord();
            yMin = ttf.readFWord();
            xMax = ttf.readFWord();
            yMax = ttf.readFWord();
        }

        public Rectangle getBBox()
        {
            return new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin);
        }

        public String toString()
        {
            return "[" + getType() + "] (" + xMin + "," + yMin + "):(" + xMax + "," + yMax + ")";
        }

        public String toDetailedString()
        {
            return toString();
        }
    }

    // --------------------------------------------------------------------------------

    public class SimpleGlyph extends Glyph
    {

        private static final int ON_CURVE = 0;

        private static final int X_SHORT = 1;

        private static final int Y_SHORT = 2;

        private static final int REPEAT_FLAG = 3;

        private static final int X_SAME = 4;

        private static final int Y_SAME = 5;

        private static final int X_POSITIVE = 4;

        private static final int Y_POSITIVE = 5;

        public int numberOfContours;

        public int[] endPtsOfContours;

        public int[] instructions;

        public int[] flags;

        public int[] xCoordinates, yCoordinates;

        public boolean[] onCurve;

        public GeneralPath shape;

        public SimpleGlyph(int numberOfContours)
        {
            this.numberOfContours = numberOfContours;
            this.endPtsOfContours = new int[numberOfContours];
        }

        public String getType()
        {
            return "Simple Glyph";
        }

        public void read() throws IOException
        {
            super.read();

            for (int i = 0; i < endPtsOfContours.length; i++)
                endPtsOfContours[i] = ttf.readUShort();

            instructions = new int[ttf.readUShort()];
            for (int i = 0; i < instructions.length; i++)
                instructions[i] = ttf.readByte();

            int numberOfPoints = endPtsOfContours[endPtsOfContours.length - 1] + 1;
            flags = new int[numberOfPoints];
            xCoordinates = new int[numberOfPoints];
            yCoordinates = new int[numberOfPoints];
            onCurve = new boolean[numberOfPoints];
            int repeatCount = 0;
            int repeatFlag = 0;
            for (int i = 0; i < numberOfPoints; i++)
            {
                if (repeatCount > 0)
                {
                    flags[i] = repeatFlag;
                    repeatCount--;
                }
                else
                {
                    flags[i] = ttf.readRawByte();
                    if (TTFInput.flagBit(flags[i], REPEAT_FLAG))
                    {
                        repeatCount = ttf.readByte();
                        repeatFlag = flags[i];
                    }
                }
                TTFInput.checkZeroBit(flags[i], 6, "flags");
                TTFInput.checkZeroBit(flags[i], 7, "flags");
                onCurve[i] = TTFInput.flagBit(flags[i], ON_CURVE);
            }

            int last = 0;
            for (int i = 0; i < numberOfPoints; i++)
            {
                if (TTFInput.flagBit(flags[i], X_SHORT))
                {
                    if (TTFInput.flagBit(flags[i], X_POSITIVE))
                    {
                        last = xCoordinates[i] = last + ttf.readByte();
                    }
                    else
                    {
                        last = xCoordinates[i] = last - ttf.readByte();
                    }
                }
                else
                {
                    if (TTFInput.flagBit(flags[i], X_SAME))
                    {
                        last = xCoordinates[i] = last;
                    }
                    else
                    {
                        last = xCoordinates[i] = last + ttf.readShort();
                    }
                }
            }

            last = 0;
            for (int i = 0; i < numberOfPoints; i++)
            {
                if (TTFInput.flagBit(flags[i], Y_SHORT))
                {
                    if (TTFInput.flagBit(flags[i], Y_POSITIVE))
                    {
                        last = yCoordinates[i] = last + ttf.readByte();
                    }
                    else
                    {
                        last = yCoordinates[i] = last - ttf.readByte();
                    }
                }
                else
                {
                    if (TTFInput.flagBit(flags[i], Y_SAME))
                    {
                        last = yCoordinates[i] = last;
                    }
                    else
                    {
                        last = yCoordinates[i] = last + ttf.readShort();
                    }
                }
            }
        }

        public String toString()
        {
            String str = super.toString() + ", " + numberOfContours + " contours, endPts={";
            for (int i = 0; i < numberOfContours; i++)
                str += (i == 0 ? "" : ",") + endPtsOfContours[i];
            str += "}, " + instructions.length + " instructions";
            return str;
        }

        public String toDetailedString()
        {
            String str = toString() + "\n  instructions = {";
            for (int i = 0; i < instructions.length; i++)
            {
                str += Integer.toHexString(instructions[i]) + " ";
            }
            return str + "}";
        }

        public GeneralPath getShape()
        {
            if (shape != null)
            {
                return shape;
            }

            shape = new GeneralPath(GeneralPath.WIND_NON_ZERO);
            int p = 0;
            for (int i = 0; i < endPtsOfContours.length; i++)
            {
                int startIndex = p++;
                shape.moveTo(xCoordinates[startIndex], yCoordinates[startIndex]);
                boolean lastOnCurve = true;
                while (p <= endPtsOfContours[i])
                {

                    if (onCurve[p])
                    {
                        if (lastOnCurve)
                        {
                            shape.lineTo(xCoordinates[p], yCoordinates[p]);
                        }
                        else
                        {
                            shape.quadTo(xCoordinates[p - 1], yCoordinates[p - 1], xCoordinates[p],
                                yCoordinates[p]);
                        }
                        lastOnCurve = true;
                    }
                    else
                    {
                        if (!lastOnCurve)
                        {
                            int x1 = xCoordinates[p - 1];
                            int y1 = yCoordinates[p - 1];
                            int x2 = (int)((x1 + xCoordinates[p]) / 2.0);
                            int y2 = (int)((y1 + yCoordinates[p]) / 2.0);
                            shape.quadTo(x1, y1, x2, y2);
                        }
                        lastOnCurve = false;
                    }
                    p++;
                }
                if (!onCurve[p - 1])
                {
                    shape.quadTo(xCoordinates[p - 1], yCoordinates[p - 1],
                        xCoordinates[startIndex], yCoordinates[startIndex]);
                }
                else if ((xCoordinates[p - 1] != xCoordinates[startIndex])
                    || (yCoordinates[p - 1] != yCoordinates[startIndex]))
                {
                    shape.closePath();
                }
            }
            return shape;
        }
    }

    // --------------------------------------------------------------------------------

    public class CompositeGlyph extends Glyph
    {

        private static final int ARGS_WORDS = 0;

        private static final int ARGS_XY = 1;

        private static final int SCALE = 3;

        private static final int XY_SCALE = 6;

        private static final int TWO_BY_TWO = 7;

        private static final int MORE_COMPONENTS = 5;

        private GeneralPath shape;

        private int noComponents;

        public String getType()
        {
            return "Composite Glyph";
        }

        public GeneralPath getShape()
        {
            return shape;
        }

        public void read() throws IOException
        {
            super.read();
            shape = new GeneralPath();

            noComponents = 0;
            boolean more = true;
            while (more)
            {
                noComponents++;
                ttf.readUShortFlags();
                more = ttf.flagBit(MORE_COMPONENTS);
                int glyphIndex = ttf.readUShort();
                int arg1, arg2;
                if (ttf.flagBit(ARGS_WORDS))
                {
                    arg1 = ttf.readShort();
                    arg2 = ttf.readShort();
                }
                else
                {
                    arg1 = ttf.readChar();
                    arg2 = ttf.readChar();
                }
                AffineTransform t = new AffineTransform();
                if (ttf.flagBit(ARGS_XY))
                {
                    t.translate(arg1, arg2);
                }
                else
                {
                    System.err.println("TTFGlyfTable: ARGS_ARE_POINTS not implemented.");
                }

                if (ttf.flagBit(SCALE))
                {
                    double scale = ttf.readF2Dot14();
                    t.scale(scale, scale);
                }
                else if (ttf.flagBit(XY_SCALE))
                {
                    double scaleX = ttf.readF2Dot14();
                    double scaleY = ttf.readF2Dot14();
                    t.scale(scaleX, scaleY);
                }
                else if (ttf.flagBit(TWO_BY_TWO))
                {
                    System.err.println("TTFGlyfTable: WE_HAVE_A_TWO_BY_TWO not implemented.");
                }

                GeneralPath appendGlyph = (GeneralPath)getGlyph(glyphIndex).getShape().clone();
                appendGlyph.transform(t);
                shape.append(appendGlyph, false);
            }
        }

        public String toString()
        {
            return super.toString() + ", " + noComponents + " components";
        }

    }

    // --------------------------------------------------------------------------------

    public Glyph[] glyphs;

    private long[] offsets;

    public String getTag()
    {
        return "glyf";
    }

    public void readTable() throws IOException
    {
        glyphs = new Glyph[((TTFMaxPTable)getTable("maxp")).numGlyphs];
        offsets = ((TTFLocaTable)getTable("loca")).offset;

        if (READ_GLYPHS)
        {
            for (int i = 0; i < glyphs.length; i++)
            {
                if ((i > 0) && (offsets[i - 1] == offsets[i]))
                {
                    glyphs[i] = glyphs[i - 1];
                }
                else
                {
                    try
                    {
                        getGlyph(i);
                    }
                    catch(IOException e)
                    {
                        System.err.println("While reading glyph #" + i + " (offset " + offsets[i]
                            + "):");
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    public Glyph getGlyph(int i) throws IOException
    {
        if (glyphs[i] != null)
        {
            return glyphs[i];
        }
        else
        {
            ttf.pushPos();
            ttf.seek(offsets[i]);
            int numberOfContours = ttf.readShort();
            if (numberOfContours >= 0)
                glyphs[i] = new SimpleGlyph(numberOfContours);
            else
                glyphs[i] = new CompositeGlyph();
            glyphs[i].read();
            // System.out.println(i+": "+offsets[i]+"-"+ttf.getPointer());
            ttf.popPos();
            return glyphs[i];
        }
    }

    public String toString()
    {
        String str = super.toString();
        for (int i = 0; i < glyphs.length; i++)
            str += "\n  #" + i + ": " + glyphs[i];
        return str;
    }
}
