/*
 * BackupFont.java
 * ===========================================================================
 * Licensed Materials - Property of IBM
 * "Restricted Materials of IBM"
 *
 * IBM SDK, Java(tm) 2 Technology Edition, v1.4
 * (C) Copyright IBM Corp. 1999, 2003. All Rights Reserved
 * ===========================================================================
 */

import java.io.*;
import java.util.zip.*;
import java.awt.image.*;

public class BackupFont {

    private String   bfName;
    private int      uOffset    = 0;
    private int      uPrevChar  = -1;
    private byte []  FntTable;
    private int  []  uIndex;
    private int      uIndexSize;
    private boolean  valid, validRange;
    private ZipFile  zf;
    private int      startUC = -1;
    private int      lastUC = -1;

    /***************************************************************/
    /*  Public Methods                                             */
    /***************************************************************/

    /*
     * Constructs a new <tt>BackupFont</tt> from the specified file name.
     * The file can be either a binary two bits per pixel font, or
     * be an archive containing ranges of the font.

     * If the file is an archive, it must must have the file extensions
     * .zip or .uga.
     *
     * @param  filename  The name of the font file
     */
    public BackupFont(String fname) {
        bfName = fname;
        valid = readBackupFont() == 0;
    }


    /**
     * Set the active codepoint for the font.  This must be done before any
     * of the methods which retrieve data from the font.
     *
     * If the codepoint is not in the font, false is returned, and the
     * previously defined codepoint is still valid.
     *
     * @param   uc  Unicode character to set (range 0 - 10ffff)
     * @return  true if the codepoint is in the backup font
     */
    public boolean setCodepoint(int uc) {

        if ((uc < startUC || uc > lastUC) && zf != null) {
            readRangeFont(uc);
        }
        if (!validRange)
            return false;

        int inx = (uc-startUC)>>8;
        int offset = uIndex[inx >= uIndexSize ? uIndexSize-1 : inx];
        if (uc >= uPrevChar && (uc>>8 == uPrevChar>>8))
            offset = uOffset;

        int count=0;
        while ((FntTable[offset]&0xff) == 0x81) {
            int i = (int)((FntTable[offset+1]&0xff) << 16) +
                         ((FntTable[offset+2]&0xff) << 8) +
                         (FntTable[offset+3]&0xff);
            if (i > uc) {
                return false;
            }
            if (i == uc) {
                if (FntTable[offset+4]==0 || FntTable[offset+5]==0)
                    return false;
                uOffset = offset;
                uPrevChar = uc;
                return true;
            }
            offset += (((FntTable[offset+4]&0xff) * (FntTable[offset+5]&0xff) + 3)/4) + 8;
        }
        return false;
    }

    /**
     * Returns the currently active codepoint in the backup font.
     * @return A unicode codepoint
     */
    public int getCodepoint() {
        return uPrevChar;
    }

    /**
     * Returns the width in pels of the current codepoint
     * @return The width of the current codepoint
     */
    public int getWidth() {
        return uOffset!=0 ? (FntTable[uOffset + 4]&0xff) : 0;
    }


    /**
     * Returns the height in pels of the current codepoint
     * @return The height of the current codepoint
     */
    public int getHeight() {
        return uOffset!=0 ? (FntTable[uOffset + 5]&0xff) : 0;
    }


    /**
     * Returns the number of horizontal pels from the left side of the grid to
     * the left side of the raster for this codepoint.
     * @return The x position for the current codepoint
     */
    public int getXpos() {
        return uOffset!=0 ? (FntTable[uOffset + 6]&0xff) : 0;
    }


    /**
     * Returns the number of vertical pels from the top of the grid to
     * the top of the raster for this codepoint.
     * @return The x position for the current codepoint
     */
    public int getYpos() {
        return uOffset!=0 ? (FntTable[uOffset + 7]&0xff) : 0;
    }


    /**
     * Returns the raster bits for this codepoint.  The data is packed 4 bits
     * per byte with the upper bits representing the leftmost bits.
     * @return A byte array with the raster data
     */
    public byte [] getData() {
        if (uOffset == 0)
            return new byte[0];
        int len = (((FntTable[uOffset+4]&0xff) * (FntTable[uOffset+5]&0xff) + 3)/4);
        byte [] a = new byte[len];
        System.arraycopy(FntTable, uOffset+8, a, 0, len);
        return a;
    }

    private int [] colors = {0x00d8f0ff, 0x00a0a0a0, 0x00505050, 0x00000000};


    /**
     * Sets the array of colors.  There a four colors in the array and each is an
     * RGB value.  The four colors represent:
     * <br>0   background
     * <br>1   light gray
     * <br>2   dark gray
     * <b4>3   black
     * @return true if colors were set
     */
    public boolean setColors(int [] newcolors) {
        if (newcolors.length == 4) {
            colors = newcolors;
            return true;
        }
        return false;
    }


    /**
     * Returns the current color array as RGB values.
     * @return a 4 element array of RGB values
     */
    public int [] getColors() {
        return colors;
    }


    /**
     * Returns whether the backup font is valid.  This only guarantees that the
     * font exists, not that it is totally valid.
     * @return true if backup font is valid
     */
    public boolean isValid() {
        return valid;
    }


    /**
     * Returns an image representing the current codepoint.
     *
     * @return the image
     */
    public BufferedImage getImage() {
        if (uOffset == 0)
            return null;
        int h = getHeight();
        int w = getWidth();
        if (h==0 || w==0)
            return null;
        BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        int offset = uOffset + 8;
        int i, j;
        int shift = 6;
        int data = FntTable[offset++];
        for (i=0; i<h; i++) {
            for (j=0; j<w; j++) {
                int val = (data>>shift)&0x03;
                if (shift == 0) {
                    data = FntTable[offset++];
                    shift = 6;
                } else shift -= 2;
                bi.setRGB(j, i, colors[val]);
            }
        }
        return bi;
    }

    /***************************************************************/
    /*  Private Methods                                            */
    /***************************************************************/

    private static final byte font18 [] =  {
        1, -127, 0, 37, -52, 9, 9, 11, 11,
        5, -108, 8, 0, -124, 0, 5, 0, 1, -128, 0, -112, 0, 20, 0, 4,
        -128, 8, 5, -108, 1
    };
    private static final byte font14 [] = {
        1, -127, 0, 37, -52, 7, 7, 7, 8,
        6, 64, -128, -124, 0, 96, 2, 64, 4, -128, -128, 100, 0, 1
    };
    private static final byte fontnull [] = {1, 1};

    /*
     * Read the whole font
     */
    private int  readBackupFont() {
        FileInputStream iFile;
        boolean start;

        /*
         * Create a default font with just the 0x25cc character
         */
        FntTable = fontnull;
        uIndex = new int[1];
        uIndexSize = 1;
        uIndex[0] = 1;
        validRange = true;

        if (bfName==null || bfName.length()<1)
            return 1;

        if (bfName.indexOf("14") >= 0) {
            FntTable = font14;
        } else if (bfName.indexOf("18") >= 0) {
            FntTable = font18;
        }

        /*
         * Try to open the font
         */
        File f = new File(bfName);
        if (!f.canRead()) {
            if (bfName.charAt(0)=='/' || bfName.charAt(0)=='\\')
                return 1;
            String fname = System.getProperty("java.home") + File.separator +
                       "lib" + File.separator;
            f = new File(fname + bfName);
            if (!f.canRead()) {
                return 1;
            }
        }

        /*
         * Check if this is a zip file
         */
        String ext = "";
        int dotPos = bfName.lastIndexOf('.');
        if (dotPos > 0)
            ext = bfName.substring(dotPos);
        if (ext.equals(".zip") || ext.equals(".ZIP") ||
            ext.equals(".uga") || ext.equals(".UGA")) {
            try {
                zf = new ZipFile(f);
                validRange = false;
                return 0;
            } catch (Exception e) {
                e.printStackTrace(System.err);
                return 1;
            }
        }

        /*
         * Allocate space for the font and read it in
         */
        startUC = 0;
        lastUC  = 0x10fffd;
        int len = (int)f.length();
        byte [] fnt = new byte [len+2];
        fnt[0] = (byte)1;

        try {
            iFile = new FileInputStream(f);
        } catch (Exception e) {
                e.printStackTrace(System.err);
            return 1;
        }

        try {
            len = iFile.read(fnt, 1, len);
            if (len>0 && fnt[len]==(byte)1)
                len--;
        } catch (Exception e) {
            e.printStackTrace(System.err);
            return 1;
        }

        /*
         * Validate the font
         */
        uIndex = new int[768];
        uIndexSize = 768;
        if (validateTable(fnt, len) == 0) {
            FntTable = fnt;
            return 0;
        }
        uIndexSize = 1;
        uIndex[0] = 1;
        return 1;
    }

    /*
     * Read one range from a zip font
     */
    private int readRangeFont(int uc) {
        if (zf == null)
            return 1;
        startUC = uc &~ 0x7ff;
        lastUC  = startUC + 0x7ff;
        uPrevChar = -1;
        uOffset   = 0;
        FntTable = new byte [2];
        FntTable[0] = (byte)1;
        validRange = false;
        uOffset = 0;
        ZipEntry ze = zf.getEntry("U"+hex6(uc&~0x7ff)+".ucf");
        if (ze != null) {
            try {
                InputStream zis = zf.getInputStream(ze);
                int len = (int) ze.getSize();
                if (len<=0)
                    return 1;
                FntTable = new byte [len+2];
                FntTable[0] = (byte)1;
                int bread = 0;
                int roff  = 1;
                while (roff <= len) {
                    bread = zis.read(FntTable, roff, len-roff+1);
                    if (bread <= 0)
                        break;
                    roff += bread;
                }
                zis.close();
                uIndex = new int[8];
                uIndexSize = 8;
                validRange = validateTable(FntTable, len) == 0;
                return 0;
            } catch (Exception e) {
                return 1;
            }
        }
        return 1;

    }


    /*
     * Validate the font
     */
    private int validateTable(byte [] fnt, int len) {
        fnt[len+1] = 1;

        for (int i=1; i<len;) {
            if ((fnt[i]&0xff) != 0x81) {
                System.err.println("Invalid backup font: "+ i );
                fnt[0] = 1;
                return 1;
            }

            int uc = (int)((fnt[i+1]&0xff)<<16) + ((fnt[i+2]&0xff)<<8) +
                          (fnt[i+3]&0xff);
            int inx = (uc-startUC)>>8;
            if (inx < uIndexSize && uIndex[inx] == 0) {
                uIndex[inx] = i;
            }
            i += (((fnt[i+4]&0xff) * (fnt[i+5]&0xff) + 3)/4) + 8;

        }
        return 0;
    }


    private static char extchar [] = {'.', '+', '*', '@'};

    /*
     * Test cases and utilities for the backup font
     */
    public static void main (String[] args) {
        if (args.length<1) {
            System.out.println("BackupFont: name required");
            return;
        }
        BackupFont bf = new BackupFont(args[0]);

        /*
         * Break up a single flat font into 2K pieces
         * java BackupFOnt font.ucf break
         */
        if (args.length==2 && "break".equals(args[1])) {
            if (bf.zf != null) {
                System.out.println("Cannot break up zip file");
                return;
            }
            bf.breakFont();
        }

        /*
         * Combine a zip file into a single flat font
         * java BackupFont font.zip combine font.ucf
         */
        else if (args.length==3 && "combine".equals(args[1])) {
            if (bf.zf == null) {
                System.out.println("Cannot combine flat font file");
                return;
            }
            try {
                File f = new File(args[2]);
                FileOutputStream os = new FileOutputStream(f);
                for (int i=0; i<0x10ffff; i+=2048) {
                    if (bf.readRangeFont(i) == 0) {
                        os.write(bf.FntTable, 1, bf.FntTable.length-2);
                    }
                }
                os.close();
            } catch (Exception e) {
                e.printStackTrace(System.err);
            }
        }

        /*
         * Create ascii form of font: java BackupFont name start end
         */
        else if (args.length==3) {
            try {
                int first = Integer.parseInt(args[1], 16);
                int last  = Integer.parseInt(args[2], 16);
                int count = 0;
                for (int i=first; i<=last; i++) {
                    if (bf.setCodepoint(i)) {
                        int w = bf.getWidth();
                        char [] line = new char[w];
                        int h = bf.getHeight();
                        System.out.println("c="+hex6(i)+
                                           " x="+bf.getXpos()+" y="+bf.getYpos()+
                                           " w="+w+" h="+h);
                        byte [] data = bf.getData();
                        int offset = 0;
                        int shift  = 0;
                        int dat    = 0;
                        for (int j=0; j<h; j++ ) {
                            for (int k=0; k<w; k++) {
                                if (shift == 0) {
                                    dat = data[offset++];
                                    shift = 8;
                                }
                                shift -= 2;
                                int val = (dat>>shift)&0x03;
                                line[k] = extchar[val];
                            }
                            System.out.println(line);
                        }
                        System.out.println();
                        count++;
                    }
                }
                System.err.println("Character count = "+count);
            } catch (Exception e) {
                e.printStackTrace(System.err);
            }
        }
        else {
            System.out.println("Unknown function requested");
        }
    }

    /*
     * Break up the font into 2K codepoint pieces
     */
    private void breakFont() {
        try {
            int startOff = 1;
            int firstChar = 0;
            int offset=1;
            File f;
            FileOutputStream os;

            while ((FntTable[offset]&0xff) == 0x81) {
                int uc = (int)((FntTable[offset+1]&0xff) << 16) +
                             ((FntTable[offset+2]&0xff) << 8) +
                             (FntTable[offset+3]&0xff);
                if (uc >= firstChar + 0x800) {
                    f = new File("U"+hex6(firstChar)+".ucf");
                    os = new FileOutputStream(f);
                    os.write(FntTable, startOff, offset-startOff);
                    System.out.println("write "+f);
                    os.close();
                    startOff = offset;
                    firstChar = uc & ~0x07ff;
                }
                offset += (((FntTable[offset+4]&0xff) * (FntTable[offset+5]&0xff) + 3)/4) + 8;
            }
            f = new File("U"+hex6(firstChar)+".ucf");
            os = new FileOutputStream(f);
            os.write(FntTable, startOff, offset-startOff);
            System.out.println("write "+f);
            os.close();
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }


    final static char hexarray[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};

    /*
     * hex6: Create a 6 character string representing an int
     */
    public static String hex6(int val) {
        char [] hexs = new char[6];
        hexs[0] = hexarray[(val>>20)&0x0f];
        hexs[1] = hexarray[(val>>16)&0x0f];
        hexs[2] = hexarray[(val>>12)&0x0f];
        hexs[3] = hexarray[(val>>8)&0x0f];
        hexs[4] = hexarray[(val>>4)&0x0f];
        hexs[5] = hexarray[val&0x0f];
        return new String(hexs);
    }
}
