/*
 * $Id: CIDFontType2.java,v 1.5 2009-03-15 20:47:38 tomoke Exp $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.sun.pdfview.font;

import java.awt.geom.GeneralPath;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

import com.sun.pdfview.PDFObject;

/**
 * a font object derived from a CID font.
 *
 * @author Jonathan Kaplan
 */
public class CIDFontType2 extends TTFFont {

    /**
     * The width of each glyph from the DW and W arrays
     */
    private Map<Character, Float> widths = null;
    /**
     * The vertical width of each glyph from the DW2 and W2 arrays
     */
    private Map<Character, Float> widthsVertical = null;

    /*
     * the default width
     */
    private int defaultWidth = 1000;
    /*
     * the default vertical width
     */
    private int defaultWidthVertical = 1000;
    /** the CIDtoGID map, if any */
    private ByteBuffer cidToGidMap;

    /**
     * create a new CIDFontType2 object based on the name of a built-in font
     * and the font descriptor
     * @param baseName the name of the font, from the PDF file
     * @param fontObj a dictionary that contains the DW (defaultWidth) and
     * W (width) parameters
     * @param descriptor a descriptor for the font
     */
    public CIDFontType2(String baseName, PDFObject fontObj,
            PDFFontDescriptor descriptor) throws IOException {
        super(baseName, fontObj, descriptor);

        parseWidths(fontObj);

        // read the CIDSystemInfo dictionary (required)
        PDFObject systemInfoObj = fontObj.getDictRef("CIDSystemInfo");
        // read the cid to gid map (optional)
        PDFObject mapObj = fontObj.getDictRef("CIDToGIDMap");


        // only read the map if it is a stream (if it is a name, it
        // is "Identity" and can be ignored
        if (mapObj != null && (mapObj.getType() == PDFObject.STREAM)) {
            cidToGidMap = mapObj.getStreamBuffer();
        }
    }

    /** Parse the Widths array and DW object */
    private void parseWidths(PDFObject fontObj)
            throws IOException {
        // read the default width (otpional)
        PDFObject defaultWidthObj = fontObj.getDictRef("DW");
        if (defaultWidthObj != null) {
            defaultWidth = defaultWidthObj.getIntValue();
        }

        int entryIdx = 0;
        int first = 0;
        int last = 0;
        PDFObject[] widthArray;

        // read the widths table 
        PDFObject widthObj = fontObj.getDictRef("W");
        if (widthObj != null) {

            // initialize the widths array
            widths = new HashMap<Character, Float>();

            // parse the width array
            widthArray = widthObj.getArray();

            /* an entry can be in one of two forms:
             *   <startIndex> <endIndex> <value> or
             *   <startIndex> [ array of values ]
             * we use the entryIdx to differentitate between them
             */
            for (int i = 0; i < widthArray.length; i++) {
                if (entryIdx == 0) {
                    // first value in an entry.  Just store it
                    first = widthArray[i].getIntValue();
                } else if (entryIdx == 1) {
                    // second value -- is it an int or array?
                    if (widthArray[i].getType() == PDFObject.ARRAY) {
                        // add all the entries in the array to the width array
                        PDFObject[] entries = widthArray[i].getArray();
                        for (int c = 0; c < entries.length; c++) {
                            Character key = new Character((char) (c + first));

                            // value is width / default width
                            float value = entries[c].getIntValue();
                            widths.put(key, new Float(value));
                        }
                        // all done
                        entryIdx = -1;
                    } else {
                        last = widthArray[i].getIntValue();
                    }
                } else {
                    // third value.  Set a range
                    int value = widthArray[i].getIntValue();

                    // set the range
                    for (int c = first; c <= last; c++) {
                        widths.put(new Character((char) c), new Float(value));
                    }

                    // all done
                    entryIdx = -1;
                }

                entryIdx++;
            }
        }

        // read the optional vertical default width
        defaultWidthObj = fontObj.getDictRef("DW2");
        if (defaultWidthObj != null) {
            defaultWidthVertical = defaultWidthObj.getIntValue();
        }

        // read the vertical widths table
        widthObj = fontObj.getDictRef("W2");
        if (widthObj != null) {

            // initialize the widths array
            widthsVertical = new HashMap<Character, Float>();

            // parse the width2 array
            widthArray = widthObj.getArray();

            /* an entry can be in one of two forms:
             *   <startIndex> <endIndex> <value> or
             *   <startIndex> [ array of values ]
             * we use the entryIdx to differentitate between them
             */
            entryIdx = 0;
            first = 0;
            last = 0;

            for (int i = 0; i < widthArray.length; i++) {
                if (entryIdx == 0) {
                    // first value in an entry.  Just store it
                    first = widthArray[i].getIntValue();
                } else if (entryIdx == 1) {
                    // second value -- is it an int or array?
                    if (widthArray[i].getType() == PDFObject.ARRAY) {
                        // add all the entries in the array to the width array
                        PDFObject[] entries = widthArray[i].getArray();
                        for (int c = 0; c < entries.length; c++) {
                            Character key = new Character((char) (c + first));

                            // value is width / default width
                            float value = entries[c].getIntValue();
                            widthsVertical.put(key, new Float(value));
                        }
                        // all done
                        entryIdx = -1;
                    } else {
                        last = widthArray[i].getIntValue();
                    }
                } else {
                    // third value.  Set a range
                    int value = widthArray[i].getIntValue();

                    // set the range
                    for (int c = first; c <= last; c++) {
                        widthsVertical.put(new Character((char) c), new Float(value));
                    }

                    // all done
                    entryIdx = -1;
                }

                entryIdx++;
            }
        }
    }

    /** Get the default width in text space */
    @Override
    public int getDefaultWidth() {
        return defaultWidth;
    }

    /** Get the width of a given character */
    @Override
    public float getWidth(char code, String name) {
        if (widths == null) {
            return 1f;
        }
        Float w = widths.get(new Character(code));
        if (w == null) {
            return 1f;
        }

        return w.floatValue() / getDefaultWidth();
    }

    /** Get the default vertical width in text space */
    public int getDefaultWidthVertical() {
        return defaultWidthVertical;
    }

    /** Get the vertical width of a given character */
    public float getWidthVertical(char code, String name) {
        if (widthsVertical == null) {
            return 1f;
        }
        Float w = widthsVertical.get(new Character(code));
        if (w == null) {
            return 1f;
        }

        return w.floatValue() / getDefaultWidth();
    }

    /**
     * Get the outline of a character given the character code.  We
     * interpose here in order to avoid using the CMap of the font in
     * a CID mapped font.
     */
    @Override
    protected synchronized GeneralPath getOutline(char src, float width) {
        int glyphId = (int) (src & 0xffff);

        // check if there is a cidToGidMap
        if (cidToGidMap != null) {
            // read the map
            glyphId = cidToGidMap.getChar(glyphId * 2);
        }

        // call getOutline on the glyphId
        return getOutline(glyphId, width);
    }
}
