// Copyright 2001 freehep
package org.freehep.graphicsio.font;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.ByteArrayOutputStream;
import java.awt.Shape;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.font.GlyphMetrics;
import java.awt.font.FontRenderContext;
import java.util.Date;
import java.text.DateFormat;

import org.freehep.util.io.EEXECEncryption;
import org.freehep.util.io.ASCIIHexOutputStream;
import org.freehep.util.io.CountedByteOutputStream;
import org.freehep.graphicsio.font.encoding.Lookup;
import org.freehep.graphicsio.font.encoding.CharTable;

/**
 * Font embedder for type 1 fonts. The output can be directly fed into a ps file
 * or as a FontFile to a pdf file.
 * <h3>Todo</h3>
 * <ul>
 *   <li>use subroutines for accents
 *   <li>add more hints
 * </ul>
 * @author Simon Fischer
 * @version $Id: FontEmbedderType1.java,v 1.3 2003/05/01 06:15:14 duns Exp $
 */
public class FontEmbedderType1 extends FontEmbedder {

    /** Defines whether or not eexec encryption is used. */
    private static final boolean ENCRYPT = true;

    /** Defines whether or not encrypted part should be hex encoded (otherwise it is binary). */
    private static final boolean HEX_ENC = true;

    /** Defines whether or not encrypted charstrings should be hex encoded
     *  Ghostview crashes when set to true. The freehep ps interpreter handles it correctly. */
    private static final boolean HEX_ENC_CHARSTRINGS = false;

    private PrintStream fontFile, encrypted;
    private CountedByteOutputStream byteCounter;
    private int asciiEnd, encEnd; // remember the lengths of the three portions
    private boolean addZeros;

    public FontEmbedderType1(FontRenderContext context,
                 OutputStream out,
                 boolean addZeros) {
        super(context);
        this.byteCounter = new CountedByteOutputStream(out);
        this.fontFile = new PrintStream(byteCounter);
        this.addZeros = addZeros;
        asciiEnd = encEnd = -1;
    }

    protected void writeWidths(double[] w) throws IOException { }

    protected void writeEncoding(CharTable t) throws IOException {
        fontFile.println("/Encoding 256 array");
        fontFile.println("0 1 255 {1 index exch /.notdef put} for"); // set undefined to .notdef ??
        for (int i = 0; i < 256; i++) {
            String charName = t.toName(i);
            if (charName != null)
            fontFile.println("dup "+i+" /"+charName+" put");
        }
        fontFile.println("readonly def");
    }

    protected void openIncludeFont() throws IOException {

        // begin clear text ascii portion
        fontFile.println("%!FontType1-1.0: " + getFont().getName()); // unknown version number
//        fontFile.println("%%CreationDate: " +
//                     DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).
//                     format(new Date()));
        fontFile.println("% Generated by: " + getClass().getName());
        fontFile.println("11 dict begin");

        fontFile.println("/FontInfo 8 dict dup begin");
        fontFile.println("/FullName ("+getFont().getPSName()+") readonly def");
        fontFile.println("/FamilyName ("+getFont().getFamily()+") readonly def");
        fontFile.println("end readonly def");

        fontFile.println("/FontName /"+getFontName()+" def");
        fontFile.println("/PaintType 0 def");
        fontFile.println("/FontType 1 def");
        fontFile.println("/FontMatrix ["+1/FONT_SIZE+" 0.0 0.0 "+1/FONT_SIZE+" 0.0 0.0] readonly def");
    }

    protected void closeIncludeFont() {

        Rectangle2D boundingBox = getFontBBox();
        int llx = (int)Math.round(boundingBox.getX());
        int lly = (int)Math.round(boundingBox.getY());
        int urx = (int)Math.round(boundingBox.getX()+boundingBox.getWidth());
        int ury = (int)Math.round(boundingBox.getY()+boundingBox.getHeight());
        fontFile.println("/FontBBox {"+llx+" "+lly+" "+urx+" "+ury+"} readonly def");

        fontFile.println("currentdict end");

        // begin encrypted portion
        if (ENCRYPT) {
            fontFile.print("currentfile eexec ");
            asciiEnd = byteCounter.getCount();
        }
        fontFile.flush();
    }

    protected void openGlyphs() throws IOException {
        // begin encryption
        if (ENCRYPT) {
            if (HEX_ENC) {
                encrypted =
                    new PrintStream(new EEXECEncryption(new ASCIIHexOutputStream(fontFile),
                                    EEXECEncryption.EEXEC_R));
            } else {
                encrypted =
                    new PrintStream(new EEXECEncryption(fontFile,
                                    EEXECEncryption.EEXEC_R));
            }
        } else {
            encrypted = fontFile;
        }

        // begin the Private dictionary
        encrypted.println("dup /Private 8 dict dup begin");
        encrypted.println("/RD {string currentfile exch readstring pop} executeonly def");
        encrypted.println("/ND {noaccess def} executeonly def");
        encrypted.println("/NP {noaccess put} executeonly def");
        encrypted.println("/BlueValues [] def"); // ???
        encrypted.println("/MinFeature {16 16} def");
        encrypted.println("/password 5839 def");
        encrypted.print("2 index ");
        encrypted.println("/CharStrings "+(getNODefinedChars()+1)+" dict dup begin");
    }

    protected void closeGlyphs() throws IOException {
        encrypted.println("end"); // end Private
        encrypted.println("end"); // end CharStrings
    }

    protected void closeEmbedFont() throws IOException {
        encrypted.println("readonly put");
        encrypted.println("noaccess put");
        encrypted.println("dup /FontName get exch definefont pop");
        encrypted.print("mark");
        if (ENCRYPT) {
            encrypted.print(" currentfile closefile ");
        }
        encrypted.flush();
        encEnd = byteCounter.getCount();
        if (!ENCRYPT) asciiEnd = encEnd;

        if (addZeros) {
            fontFile.println();
            for (int i = 0; i < 16; i++)
            fontFile.println("00000000000000000000000000000000");
            fontFile.println("cleartomark");
        }
    }

    protected void writeGlyph(String characterName, Shape glyph,
                  GlyphMetrics glyphMetrics) throws IOException {

        // FIXME: find out why Acrobat Reader displays some characters displaced when
        //        using the correct sidebearing. A value of 0 looks good
        double sidebearing = glyphMetrics != null ?
            glyphMetrics.getLSB() :
            0;
        //double sidebearing = 0;

        // write the binary charstring to a buffer
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        CharstringEncoder charString =
            (HEX_ENC_CHARSTRINGS ?
             new CharstringEncoder(new EEXECEncryption(new ASCIIHexOutputStream(bytes),
                                   EEXECEncryption.CHARSTRING_R)) :
             new CharstringEncoder(new EEXECEncryption(bytes,
                                   EEXECEncryption.CHARSTRING_R)));

        charString.startChar(sidebearing,
                     (glyphMetrics != null ?
                      glyphMetrics.getAdvance() :
                      getUndefinedWidth())); // bounds.getWidth());
        charString.drawPath(glyph);
        charString.endchar();

        // write the buffer to the encrypted fontFile
        byte[] binaryString = bytes.toByteArray();
        encrypted.print("/"+characterName + " " + binaryString.length + " RD ");
        for (int i = 0; i < binaryString.length; i++) {
            encrypted.write(binaryString[i] & 0x00ff);
        }
        encrypted.println("ND");
        encrypted.flush();
    }

    /** Returns the length of the ascii portion of the output. */
    public int getAsciiLength() { return asciiEnd; }

    /** Returns the length of the encrypted portion of the output. */
    public int getEncryptedLength() { return encEnd - asciiEnd; }

}
