/*
 * %Z%%W% %I%
 * ===========================================================================
 * Licensed Materials - Property of IBM
 * "Restricted Materials of IBM"
 *
 * IBM SDK, Java(tm) 2 Technology Edition, v1.4
 * (C) Copyright IBM Corp. 1999, 2002. All Rights Reserved
 * ===========================================================================
 */
/*
 * fontgridawt - Display/Print a font grid using AWT
 *
 * Version:  1.4.1    31 July 2002
 *
 * Function:
 *     Show and print a grid showing all characters in a ward of an encoding
 *     for a font.  This is a test case for the Java 2 font support.
 *
 * Notes:
 *     The classes ConverterList and FontProperties are required.  To compile
 *     make sure the current directory is in the classpath:
 *         javac -classpath . fontgrid.java
 *
 *     This version uses AWT components and to display the font grid.
 *
 *     Any parameter on the command line is put into the window title.  This can be
 *     used to distinguish two instances running together.  If the parameter starts
 *     with 's' the grid size is suitable for a 600x800 display.
 *
 *     Printing can be done in either Java2D or AWT print.  Java2D printing will go
 *     to the default printer.
 *
 *     To see details of a character, click on it.  To see font details, click on
 *     the font description.
 *
 *     To see the description of the codepoint, you need to have the file
 *     UnicodeData.txt in the current directory or in the Java lib directory.
 *     This is the unicode character file from www.unicode.org.
 *
 * Author:
 *     Ken Borgendale (03/2000) from Swing version of fontgrid.java
 *
 * Todo:
 *     - Properties save/restore
 *     - Help and documentation
 *     - Font details
 *     - Codepage details
 */
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.applet.*;
import java.awt.font.*;
import java.io.*;
import java.util.*;
import java.text.*;
import java.lang.*;
import java.awt.print.*;

public class fontgridawt extends Panel implements ActionListener, ItemListener,
                                     KeyListener, MouseListener, MouseMotionListener
{
    int        fInUnicode = 1;
    Choice     cFnt;
    Choice     cPage;
    Choice     cSize;
    Choice     cEncode;
    Choice     cStyle;
    String     sCurFont   = null;
    int        fCurSize   = 18;
    int        fCurStyle  = Font.PLAIN;
    int        iStart     = 0;
    boolean    inDBCS     = false;
    int        cEncodeMax;
    int        fCurGrid   = -1;
    Panel      cPanel;
    Label      tInfo;
    Font       dFont;
    Font       dFont2;
    Font       dFont3;
    Frame      f;
    static  int      uOffset    = 0;
    static  int      uPrevChar  = 0;
    static  byte []  UniTable;
    fontgridawt  thisfontgrid;
    Insets     insets = new Insets( 1, 1, 1, 1 );
    Frame      fDetail;
    Frame      fFontDetail;
    charDetails thischar;
    fontDetails thisfont;
    String     fOldEncode = "";
    FontProperties fprop;
    int        uniPlane;

    final int  DRAWCHARS       = 0;
    final int  DRAWSTRING      = 1;
    final int  DRAWGLYPHVECTOR = 2;
    final int  DRAWTEXTLAYOUT  = 3;
    final int  DRAWLABEL       = 4;

    Label []   fLabels;
    int        fDraw           = DRAWSTRING;
    int        fDetailDraw     = DRAWGLYPHVECTOR;
    boolean    fLabelPos       = false;

    MenuBar    menuBar;

    Menu       fileMenu;
    Menu       viewMenu;
    Menu       optionsMenu;
    Menu       detailsMenu;
    Menu       helpMenu;
    Menu       uniPlaneMenu;

    CheckboxMenuItem  drawcharsMenu;
    CheckboxMenuItem  drawstringMenu;
    CheckboxMenuItem  glyphvectorMenu;
    CheckboxMenuItem  textlayoutMenu;
    CheckboxMenuItem  labelMenu;
    CheckboxMenuItem  charglyphvectorMenu;
    CheckboxMenuItem  chartextlayoutMenu;
    CheckboxMenuItem  charshapelayoutMenu;
    CheckboxMenuItem  unicodeMenu;
    CheckboxMenuItem  codepageMenu;
    CheckboxMenuItem  foxMenu;
    CheckboxMenuItem  langMenu;
    CheckboxMenuItem  indicMenu;
    CheckboxMenuItem  pixelsMenu;
    CheckboxMenuItem  cAntiA;
    CheckboxMenuItem  cFract;
    CheckboxMenuItem  cAllFnt;
    CheckboxMenuItem  cVerbose;
    CheckboxMenuItem  cDynamicFont; // New feature in 1.3
    CheckboxMenuItem  [] Plane;

    public  gridCanvas grid;
    public  boolean   fSmall;
    public  boolean   fVerbose;
    public  boolean   fQuiet;
    public  ConverterList CvL = new ConverterList();

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

    private Font []   newFonts;                                                                  // used for dynamic font loading
    private String [] newFontNames;                                                            // for dynamic font loading
    final private String javaVersion = System.getProperty("java.version");
    final private String fontPath  = System.getProperty("java.home") + File.separator +
                  "lib" + File.separator + "fonts" ;


    /*
     * hex4: Create a 4 or 5 character string representing a int
     */
    public static String hex4(int val) {
        char [] hexs;
        if ((val&0xffffff) >= 0x10000) {
            hexs = new char[5];
            hexs[0] = hexarray[(val>>16)&0x0f];
            hexs[1] = hexarray[(val>>12)&0x0f];
            hexs[2] = hexarray[(val>>8)&0x0f];
            hexs[3] = hexarray[(val>>4)&0x0f];
            hexs[4] = hexarray[val&0x0f];
        } else {
            hexs = new char[4];
            hexs[0] = hexarray[(val>>12)&0x0f];
            hexs[1] = hexarray[(val>>8)&0x0f];
            hexs[2] = hexarray[(val>>4)&0x0f];
            hexs[3] = hexarray[val&0x0f];
        }
        return new String(hexs);
    }

    /*
     * hex2: Create a 2 character string representing a short int
     */
    public static String hex2(int val) {
        char hexs[] = new char[2];
        hexs[0] = hexarray[(val>>4)&0x0f];
        hexs[1] = hexarray[val&0x0f];
        return new String(hexs);
    }

    /*
     * init: Create the user interface
     */
    public void init(Frame f) {
        int i;

        thisfontgrid = this;
        dFont = new Font("dialog", 1, 12);
        dFont2 = new Font("dialog", 0, 12);
        dFont3 = new Font("dialog", 1, 12);

        this.f = f;
        if (fSmall)
            f.setSize(590, 580);
        else
            f.setSize(640, 725);

        createMenuBar();

        setLayout(new BorderLayout());

        grid = new gridCanvas();
        grid.addKeyListener(this);
        grid.addMouseListener(this);
        grid.addMouseMotionListener(this);
        addKeyListener(this);
        addMouseListener(this);
        addMouseMotionListener(this);


        /*
         * Set up the bottom panel
         */
        tInfo = new Label();
        tInfo.setFont(dFont2);
        tInfo.setForeground(new Color(0, 0, 127));
        tInfo.setAlignment(Label.CENTER);
        displayMessage();
        add(tInfo, BorderLayout.SOUTH);

        /*
         * Set up the top area
         */
        Panel p = new Panel();
        cPanel = p;
        add(p, BorderLayout.NORTH);

        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints cns = new GridBagConstraints();
        p.setLayout(gridbag);
        cns.fill = GridBagConstraints.HORIZONTAL;
        cns.insets = new Insets(0, 2, 0, 2);

        cns.weightx = 35;
        cFnt = addCombo(p, "Select font name to display", cns);
        sCurFont = setupFonts(cFnt, 0);

        cns.weightx = 15;
        cStyle = addCombo(p, "Select font style to display", cns);
        cStyle.addItem("plain");
        cStyle.addItem("italic");
        cStyle.addItem("bold");
        cStyle.addItem("bolditalic");

        cns.weightx = 10;
        cSize = addCombo(p, "Select font size to display", cns);
        cSize.addItem("6");
        cSize.addItem("8");
        cSize.addItem("9");
        cSize.addItem("10");
        cSize.addItem("11");
        cSize.addItem("12");
        cSize.addItem("13");
        cSize.addItem("14");
        cSize.addItem("15");
        cSize.addItem("16");
        cSize.addItem("17");
        cSize.addItem("18");
        cSize.addItem("19");
        cSize.addItem("20");
        cSize.addItem("22");
        cSize.addItem("24");
        cSize.addItemListener(this);
        if (fSmall) {
            cSize.select(7);  /* 14 point */
            fCurSize = 14;
        } else {
            cSize.select(11);  /* 18 point */
            fCurSize = 18;
        }
        grid.setFont(sCurFont, fCurStyle, fCurSize);

        /*
         * Make the codepage list, but do not enable it
         */
        cEncode = addCombo(null, "Select codepage to display", cns);

        /*
         * Make the range list
         */
        cns.weightx = 35;
        cPage = addCombo(p, "Select range of unicode characters", cns);
        for (i=0;i < 256; i++) {
            String from = hex4(i*256);
            String to = hex4(i*256+255);
            cPage.addItem(from + " - " + to + "  " + getRangeName((char)(i*256)));
        }
        grid.setCodepage(0);
    }


    /*
     * Set up the Unicode plane
     */
    void setPlane(int plane) {
        int i;
        int base;
        boolean saveSetup;

        if (plane < 0 || plane > 16)
            plane = 0;
        base = plane << 16;
        uniPlane = base;
        cPage.removeAll();
        for (i=0;i < 256; i++) {
            String from = hex4(base);
            String to = hex4(base+255);
            cPage.addItem(from + " - " + to + "  " + getRangeName(base));
            base += 256;
        }
        cPage.select(0);
        setCpSelection(1);
        codepageMenu.setState(false);
        unicodeMenu.setState(true);
    }


    /*
     * Add a combo box
     */
    private Choice addCombo(Panel p, String helptxt, GridBagConstraints c) {
        Choice b = new Choice();
        b.setFont(dFont);
        b.addKeyListener(this);
        b.addItemListener(this);
        if (p != null) {
            ((GridBagLayout)p.getLayout()).setConstraints(b, c);
            p.add(b);
        }
        return b;
    }

    /*
     * Change between codepage and range selection
     */
    private void setCpSelection(int which) {
        if (which == fInUnicode)
            return;

        GridBagConstraints cns = new GridBagConstraints();
        cns.fill = GridBagConstraints.HORIZONTAL;
        cns.insets = new Insets(0, 2, 0, 2);
        cns.weightx = 35;
        if (which == 0) {
            cPanel.remove(cPage);
            ((GridBagLayout)cPanel.getLayout()).setConstraints(cEncode, cns);
            cPanel.add(cEncode);
            cPanel.validate();
        } else {
            cPanel.remove(cEncode);
            ((GridBagLayout)cPanel.getLayout()).setConstraints(cPage, cns);
            cPanel.add(cPage);
            cPanel.validate();
        }
        fInUnicode = which;
    }

    /*
     * Set up the converter list
     */
    private void setConverters() {
        ConverterList cvl = new ConverterList();
        cEncodeMax = cvl.getCount();
        String name;
        int    which = 0;

        cEncode.removeAll();
        for (int i=0; i<cEncodeMax; i++) {
            name = (String)cvl.nextElement();
            cEncode.addItem(name + " - " + cvl.getDescription());
            if (name.equals("Cp1252"))
                which = i;
        }
        cEncode.select(which);
    }


    /*
     * Setup Load dynamic fonts in Java 1.3 and above
     */
    private void setDynamicFont() {
        /*
         * Get all font names shipped with JDK under fonts directory
         */
        if (javaVersion.compareTo("1.5.0") >= 0) {   // Reenable by setting to 1.3.0
            try {
                File path = new File(fontPath);
                if (path.exists() && path.isDirectory()) {
                    newFontNames = path.list();
                    if (newFontNames != null) {
                        newFonts = new Font[newFontNames.length];
                    }
                }
                for (int index = 0; index < newFontNames.length; index++) {
                    if (newFontNames[index].endsWith(".ttf")) {
                        File file = new File(fontPath + File.separator + newFontNames[index]);

                        FileInputStream fis = new FileInputStream(file);
                        /* Delete this line to build in Java 1.2 */
                    //  newFonts[index] = Font.createFont(Font.TRUETYPE_FONT, fis);
                    }
                }
                cDynamicFont    = addcbitem(optionsMenu, "Dynamic Font", false);
            } catch (Exception e) {
                System.out.println("Dynamic font exception");
            }
        }
    }


    /*
     * Make a normal menu item
     */
    private MenuItem additem(Menu m, String s, int key) {
        MenuItem item = new MenuItem(s);
        m.add(item);
        item.setFont(dFont3);
        item.addActionListener(this);
        if (key != 0) {
            item.setShortcut(new MenuShortcut(key));
        }
        return item;
    }


    /*
     * Make a Checkbox Menu item
     */
    private CheckboxMenuItem addcbitem(Menu m, String s, boolean state) {
        CheckboxMenuItem item = new CheckboxMenuItem(s, state);
        m.add(item);
        item.setFont(dFont3);
        item.addItemListener(this);
        return item;
    }


    /*
     * Set up a Menu bar
     */
    private void createMenuBar() {
        menuBar = new MenuBar();
        f.setMenuBar(menuBar);

        fileMenu = new Menu("File");
        fileMenu.setFont(dFont3);
        menuBar.add(fileMenu);
        additem(fileMenu, "Print2D", KeyEvent.VK_F10);
        additem(fileMenu, "PrintAWT", KeyEvent.VK_F11);
        fileMenu.addSeparator();
        additem(fileMenu, "Exit", KeyEvent.VK_F3);

        viewMenu = new Menu("View");
        viewMenu.setFont(dFont3);
        menuBar.add(viewMenu);
        unicodeMenu     = addcbitem(viewMenu, "Unicode",     true);
        codepageMenu    = addcbitem(viewMenu, "Codepage",    false);
        viewMenu.addSeparator();
        cAllFnt         = addcbitem(viewMenu, "All fonts",   false);
        setDynamicFont();
        if (javaVersion.compareTo("1.4.0") >= 0)
        addPlaneMenu();
        viewMenu.addSeparator();
        additem(viewMenu, "First Page", KeyEvent.VK_HOME);
        additem(viewMenu, "Next Page", KeyEvent.VK_PAGE_DOWN);
        additem(viewMenu, "Prev Page", KeyEvent.VK_PAGE_UP);

        optionsMenu = new Menu("Options");
        optionsMenu.setFont(dFont3);
        menuBar.add(optionsMenu);
        drawstringMenu  = addcbitem(optionsMenu, "drawString",  true);
        drawcharsMenu   = addcbitem(optionsMenu, "drawChars",   false);
        glyphvectorMenu = addcbitem(optionsMenu, "GlyphVector", false);
        textlayoutMenu  = addcbitem(optionsMenu, "TextLayout",  false);
        labelMenu       = addcbitem(optionsMenu, "Label",       false);
        cAntiA          = addcbitem(optionsMenu, "Antialias",   false);
        cFract          = addcbitem(optionsMenu, "Fractional",  false);
        cVerbose        = addcbitem(optionsMenu, "Verbose",     false);
        optionsMenu.addSeparator();
        foxMenu         = addcbitem(optionsMenu, "Latin text",    true);
        indicMenu       = addcbitem(optionsMenu, "Indic text",    false);
        langMenu        = addcbitem(optionsMenu, "Other scripts", false);

        detailsMenu = new Menu("Details");
        detailsMenu.setFont(dFont3);
        menuBar.add(detailsMenu);
        pixelsMenu          = addcbitem(detailsMenu, "Show pixels", false);
        detailsMenu.addSeparator();
        charglyphvectorMenu = addcbitem(detailsMenu, "GlyphVector", true);
        chartextlayoutMenu  = addcbitem(detailsMenu, "TextLayout",  false);
        charshapelayoutMenu  = addcbitem(detailsMenu, "Shape",  false);

        helpMenu = new Menu("Help");
        helpMenu.setFont(dFont3);
        menuBar.add(helpMenu);
        additem(helpMenu, "About fontgrid", KeyEvent.VK_F1);
    }

    /*
     * Do the paint of the grid
     */
    public void paint(Graphics g) {
        grid.paintComponent(g);
    }

    /*
     * Add menu to modify Unicode plane.  This does not work
     * before Java 1.4.0 and is disabled.
     */
    public void addPlaneMenu() {
        if (javaVersion.compareTo("1.4.0") < 0)
            return;
        viewMenu.addSeparator();
        uniPlaneMenu = new Menu("Unicode plane");
        Plane = new CheckboxMenuItem[17];
        Plane[0]  = addcbitem(uniPlaneMenu, "Base mapping plane (BMP)",  true);
        Plane[1]  = addcbitem(uniPlaneMenu, "Plane 1 - Music and Math",  false);
        Plane[2]  = addcbitem(uniPlaneMenu, "Plane 2 - CJK Extension B", false);
        Plane[3]  = addcbitem(uniPlaneMenu, "Plane 3",                   false);
        Plane[4]  = addcbitem(uniPlaneMenu, "Plane 4",                   false);
        Plane[5]  = addcbitem(uniPlaneMenu, "Plane 5",                   false);
        Plane[6]  = addcbitem(uniPlaneMenu, "Plane 6",                   false);
        Plane[7]  = addcbitem(uniPlaneMenu, "Plane 7",                   false);
        Plane[8]  = addcbitem(uniPlaneMenu, "Plane 8",                   false);
        Plane[9]  = addcbitem(uniPlaneMenu, "Plane 9",                   false);
        Plane[10] = addcbitem(uniPlaneMenu, "Plane 10",                  false);
        Plane[11] = addcbitem(uniPlaneMenu, "Plane 11",                  false);
        Plane[12] = addcbitem(uniPlaneMenu, "Plane 12",                  false);
        Plane[13] = addcbitem(uniPlaneMenu, "Plane 13",                  false);
        Plane[14] = addcbitem(uniPlaneMenu, "Plane 14 - Tags",           false);
        Plane[15] = addcbitem(uniPlaneMenu, "Plane 15 - User Defined",   false);
        Plane[16] = addcbitem(uniPlaneMenu, "Plane 16",                  false);
        viewMenu.add(uniPlaneMenu);
    }


    /*
     * Display a message on the info line
     */
    public void displayMessage(String text) {
        if (text==null || text.length()==0) {
            tInfo.setText(" ");
        } else {
            tInfo.setText(text);
        }
    }


    /*
     * Null out the message on the info line
     */
    public void displayMessage() {
        displayMessage(" ");
    }


    /*
     * Create a new frame for a character detail report
     */
    public void initDetail() {
        fDetail = new Frame();
        if (fSmall) {
            fDetail.setSize(250, 330);
            fDetail.setLocation(470, 170);
        } else {
            fDetail.setSize(300, 420);
            fDetail.setLocation(590, 0);
        }
        thischar = new charDetails(fDetail, this);
    }

    /*
     * Create a new frame for a font detail report
     */
    public void initFontDetail() {
        fFontDetail = new Frame();
        if (fSmall) {
            fFontDetail.setSize(300, 330);
            fFontDetail.setLocation(470, 130);
        } else {
            fFontDetail.setSize(400, 400);
            fFontDetail.setLocation(590, 40);
        }
        thisfont = new fontDetails(fFontDetail, this);
    }


    /*
     * Set up the font combo box
     */
    private String setupFonts(Choice b, int full) {
        int    i;
        int    which;
        String nfnts[];
        Collator col = Collator.getInstance();
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();

        col.setStrength(Collator.PRIMARY);
        col.setDecomposition(Collator.NO_DECOMPOSITION);

        if (full>0) {
            if (full == 2) {
                nfnts = new String[newFonts.length];
                for (i=0; i<newFonts.length; i++){
                    nfnts[i] = newFonts[i].getFontName();
                }
            } else {
                Font afnts[] = ge.getAllFonts();
                nfnts = new String[afnts.length];
                for (i=0; i<afnts.length; i++) {
                    nfnts[i] = afnts[i].getFontName();
                }
            }
        } else {
            nfnts = ge.getAvailableFontFamilyNames();
        }
        Arrays.sort(nfnts, col);

        b.removeAll();
        which = 0;
        for (i=0; i<nfnts.length; i++) {
            b.addItem(nfnts[i]);
            if (nfnts[i].equals("Dialog"))
                which = i;
            if (nfnts[i].equals("dialog"))
                which = i;
            if (nfnts[i].equals("dialog.plain"))
                which = i;
            if (which==0 && nfnts[i].equals("Lucida Sans Regular"))
                which = i;
        }
        b.select(which);
        return  nfnts[which];
    }


    /*
     * Run user interface actions
     */
    public void actionPerformed(ActionEvent ev) {
        String label = ev.getActionCommand();
        int    i;

        if(label.equals("Exit")) {
            System.exit(0);
        }

        if(label.equals("Print2D")) {
            PrinterJob job = PrinterJob.getPrinterJob();
            PageFormat pf  = new PageFormat();
            Paper      pap = new Paper();
            pap.setImageableArea(18.0, 18.0, pap.getWidth()-36, pap.getHeight()-36);
            pf.setPaper(pap);
            job.setPrintable(grid, pf);
            job.setCopies(1);
            try {
                job.print();
            } catch (Exception ex) {
                System.out.println("Printing error: " + ex.toString());
            }
        }

        if (label.equals("PrintAWT")) {
            PrintJob pj = Toolkit.getDefaultToolkit().getPrintJob((Frame)f,
                        "fontgrid Printing...", (Properties)null);

            if (pj != null) {
                Graphics g;
                Dimension pageDim = pj.getPageDimension();
                int pageRes = pj.getPageResolution();

                g = pj.getGraphics();
                if (g != null) {
                    grid.paintGrid(g, true);
                    g.dispose();
                } else {
                    System.err.println("Could not get Graphics handle.");
                }
                System.err.println("PrintJob ended.");
                pj.end();

            } else {
                System.err.println("PrintJob cancelled.");
            }
        }

        if (label.equals("Next Page")) {
            moveNext();
        }

        if (label.equals("First Page")) {
            iStart = 0;
            if (uniPlane != 0)
                setPlane(0);
            displayMessage();
            fCurGrid = -1;
            if (!grid.getCodepage().equals("Unicode"))
                fOldEncode = grid.getCodepage();
            cPage.select(0);
            setCpSelection(1);
            grid.setCodepage(0);
            this.repaint();
        }

        if(label.equals("Prev Page")) {
            movePrev();
        }

    }


    /*
     * Perform the next page or page down function
     */
    public void moveNext() {
        displayMessage();
        fCurGrid = -1;
        if (fInUnicode==1) {
            if (iStart<0xff00)
                iStart += 256;
            cPage.select(iStart/256);
            grid.setCodepage(uniPlane + iStart);
        } else {
            if (inDBCS) {
                iStart = CvL.nextPosition(iStart);
                grid.setCodepage(grid.getCodepage(), iStart);
                inDBCS = iStart!=0;
                grid.repaint();
            } else {
                int which = cEncode.getSelectedIndex();
                if (which+1 < cEncodeMax) {
                    cEncode.select(which+1);
                    grid.setCodepage(cEncode.getItem(which+1));
                }
            }
        }
        this.repaint();
    }


    /*
     * Perform the Prev page or page up function
     */
    public void movePrev() {
        displayMessage();
        fCurGrid = -1;
        if (fInUnicode==1) {
            if (iStart>0)
                iStart -= 256;
            cPage.select(iStart/256);
            grid.setCodepage(uniPlane + iStart);
        } else {
            if (inDBCS) {
                iStart = CvL.prevPosition(iStart);
                grid.setCodepage(grid.getCodepage(), iStart);
                inDBCS = iStart!=0;
                grid.repaint();
            } else {
                int which = cEncode.getSelectedIndex();
                if (which > 0) {
                    cEncode.select(which-1);
                    grid.setCodepage(cEncode.getItem(which-1));
                }
            }
        }
        this.repaint();
    }


    /*
     * Process a change in one of the choice or check boxes.
     */
    public void itemStateChanged(ItemEvent ie) {
        Object obj = ie.getItem();
        String str = obj.toString();
        byte   bytes[];
        String s;
        int    i, j;

        obj = ie.getSource();

        /*
         * Change the style
         */
        if (obj.equals(cStyle)) {
            if (str.compareTo("plain") == 0) {
                fCurStyle = Font.PLAIN;
            } else if (str.compareTo("bold") == 0) {
                fCurStyle = Font.BOLD;
            } else if (str.compareTo("italic") == 0) {
                fCurStyle = Font.ITALIC;
            } else if (str.compareTo("bolditalic") == 0) {
                fCurStyle = Font.BOLD | Font.ITALIC;
            }
            grid.setFont(sCurFont, fCurStyle, fCurSize);
            this.repaint();
        }

        /*
         * Change the codepage
         */
        if (obj.equals(cEncode)) {
            if (str.compareTo("Unicode")==0) {
                if (!grid.getCodepage().equals("Unicode"))
                    fOldEncode = grid.getCodepage();
                setCpSelection(1);
                if (cPage != null)
                    iStart = cPage.getSelectedIndex()*256;
                grid.setCodepage(uniPlane + iStart);
            } else {
                setCpSelection(0);
                int offset = str.indexOf(' ');
                grid.setCodepage(offset>0 ? str.substring(0, offset) : str, 0);
                iStart = 0;
            }
            displayMessage();
            fCurGrid = -1;
            this.repaint();
        }

        if (Plane != null) {
            for (i=0; i<17; i++) {
                if (obj.equals(Plane[i])) {
                    for (j=0; j<17; j++) {
                        Plane[j].setState(j==i);
                    }
                    setPlane(i);
                    iStart = 0;
                    grid.setCodepage(uniPlane);
                    this.repaint();
                    break;
                }
            }
        }

        /*
         * Change the verbose setting
         */
        if (obj.equals(cVerbose)) {
            fVerbose = cVerbose.getState();
            System.out.println("Vebose flag set to " + fVerbose);
        }




        /*
         * Set or reset the Antialias flag
         */
        if (obj.equals(cAntiA)) {
            if (fVerbose) {
                System.out.println("Antialias set to " + cAntiA.getState());
            }
            this.repaint();
        }


        /*
         * Set or reset the Fractional metrics flag
         */
        if (obj.equals(cFract)) {
            if (fVerbose) {
                System.out.println("Fractional metrics set to " + cFract.getState());
            }
            this.repaint();
        }


        /*
         * Switch between family names and full font names
         */
        if (obj.equals(cAllFnt)) {
            if (cDynamicFont != null)
                cDynamicFont.setState(false);
            if (fVerbose) {
                System.out.println("All fonts set to " + cAllFnt.getState());
            }
            sCurFont = setupFonts(cFnt, cAllFnt.getState() ? 1 : 0);
            grid.setFont(sCurFont, fCurStyle, fCurSize);
            this.repaint();
        }

        /*
         * Switch between dynamic and registered font names
         */
        if (cDynamicFont != null && obj.equals(cDynamicFont)) {
            cAllFnt.setState(false);
            if (fVerbose) {
                System.out.println("Dynamic font set to " + cAllFnt.getState());
            }
            sCurFont = setupFonts(cFnt, cDynamicFont.getState() ? 2 : 0);
            grid.setFont(sCurFont, fCurStyle, fCurSize);
            this.repaint();
        }


        /*
         * Set font size
         */
        if (obj.equals(cSize)) {
            fCurSize = new Integer(str).intValue();
            grid.setFont(sCurFont, fCurStyle, fCurSize);
            this.repaint();
        }


        /*
         * Set font name
         */
        if (obj.equals(cFnt)) {
            sCurFont = str;
            grid.setFont(sCurFont, fCurStyle, fCurSize);
            this.repaint();
        }



        /*
         * Set range within Unicode
         */
        if (obj.equals(cPage)) {
            iStart = cPage.getSelectedIndex() * 256;
            grid.setCodepage(uniPlane + iStart);
            this.repaint();
        }



        /*
         * Change to codepage mode
         */
        if (obj.equals(codepageMenu)) {
            /* Implement radio button (codepage, unicode) */
            codepageMenu.setState(true);
            unicodeMenu.setState(false);

            setConverters();
            setCpSelection(0);
            String cp = cEncode.getSelectedItem();
            int    offset = cp.indexOf(" ");
            grid.setCodepage(offset>0 ? cp.substring(0, offset) : cp);
            this.repaint();
        }


        /*
         * Change to unicode mode
         */
        if (obj.equals(unicodeMenu)) {
            /* Implement radio button (codepage, unicode) */
            codepageMenu.setState(false);
            unicodeMenu.setState(true);

            if (!grid.getCodepage().equals("Unicode"))
                fOldEncode = grid.getCodepage();
            setCpSelection(1);
            grid.setCodepage(uniPlane + iStart);
            this.repaint();
        }


       /*
        * Set text drawing mode to DrawChars
        */
        if (obj.equals(drawcharsMenu)) {
            /* Implement radio button (text drawing mode) */
            drawcharsMenu.setState(true);
            drawstringMenu.setState(false);
            glyphvectorMenu.setState(false);
            textlayoutMenu.setState(false);

            labelDraw(false);
            fDraw = DRAWCHARS;
            this.repaint();
        }

       /*
        * Set text drawing mode to DrawString
        */
        if (obj.equals(drawstringMenu)) {
            /* Implement radio button (text drawing mode) */
            drawcharsMenu.setState(false);
            drawstringMenu.setState(true);
            glyphvectorMenu.setState(false);
            textlayoutMenu.setState(false);
            labelDraw(false);

            fDraw = DRAWSTRING;
            this.repaint();
        }

       /*
        * Set text drawing mode to GlyphVector
        */
        if (obj.equals(glyphvectorMenu)) {
            /* Implement radio button (text drawing mode) */
            drawcharsMenu.setState(false);
            drawstringMenu.setState(false);
            glyphvectorMenu.setState(true);
            textlayoutMenu.setState(false);
            labelDraw(false);

            fDraw = DRAWGLYPHVECTOR;
            this.repaint();
        }

       /*
        * Set text drawing mode to TextLayout
        */
        if (obj.equals(textlayoutMenu)) {
            /* Implement radio button (text drawing mode) */
            drawcharsMenu.setState(false);
            drawstringMenu.setState(false);
            glyphvectorMenu.setState(false);
            textlayoutMenu.setState(true);
            labelDraw(false);

            fDraw = DRAWTEXTLAYOUT;
            this.repaint();
        }

       /*
        * Set text drawing mode to Label
        */
        if (obj.equals(labelMenu)) {
            /* Implement radio button (text drawing mode) */
            drawcharsMenu.setState(false);
            drawstringMenu.setState(false);
            glyphvectorMenu.setState(false);
            textlayoutMenu.setState(false);
            labelDraw(true);

            fDraw = DRAWLABEL;
            this.repaint();
        }

       /*
        * Set character detail drawing mode to GlyphVector
        */
        if (obj.equals(charglyphvectorMenu)) {
            /* Implement radio button (char detail drawing mode) */
            charglyphvectorMenu.setState(true);
            chartextlayoutMenu.setState(false);
            charshapelayoutMenu.setState(false);

            fDetailDraw = DRAWGLYPHVECTOR;
            if (thischar != null)
                thischar.repaint();
        }


       /*
        * Set character detail drawing mode to TextLayout
        */
        if (obj.equals(chartextlayoutMenu)) {
            /* Implement radio button (char detail drawing mode) */
            charglyphvectorMenu.setState(false);
            chartextlayoutMenu.setState(true);
            charshapelayoutMenu.setState(false);

            fDetailDraw = DRAWTEXTLAYOUT;
            if (thischar != null)
                thischar.repaint();
        }

       /*
        * Set character detail drawing mode to Shape
        */
        if (obj.equals(charshapelayoutMenu)) {
            /* Implement radio button (char detail drawing mode) */
            charglyphvectorMenu.setState(false);
            chartextlayoutMenu.setState(false);
            charshapelayoutMenu.setState(true);

            fDetailDraw = DRAWLABEL;
            if (thischar != null)
                thischar.repaint();
        }


       /*
        * Set text lines to latin
        */
        if (obj.equals(foxMenu)) {
            /* Implement radio button (text lines) */
            foxMenu.setState(true);
            langMenu.setState(false);
            indicMenu.setState(false);

            grid.setLineText(0);
            this.repaint();
        }

       /*
        * Set text lines to Other
        */
        if (obj.equals(langMenu)) {
            /* Implement radio button (text lines) */
            foxMenu.setState(false);
            langMenu.setState(true);
            indicMenu.setState(false);

            grid.setLineText(1);
            this.repaint();
        }

       /*
        * Set text lines to Other
        */
        if (obj.equals(indicMenu)) {
            /* Implement radio button (text lines) */
            foxMenu.setState(false);
            langMenu.setState(false);
            indicMenu.setState(true);

            grid.setLineText(1);
            this.repaint();
        }
    }


    /*
     * Set up or tear down label drawing mode
     */
    private void labelDraw(boolean isLabel) {
        int  i;

        if (isLabel) {
            labelMenu.setState(true);
            if (fLabels == null) {
                thisfontgrid.setVisible(false);
                fLabels = new Label[259];
                for (i=0; i<259; i++) {
                    fLabels[i] = new Label();
                    fLabels[i].addMouseListener(this);
                    thisfontgrid.add(fLabels[i]);
                }
                thisfontgrid.setVisible(true);
                fLabelPos = false;
            }
        } else {
            labelMenu.setState(false);
            if (fLabels != null) {
                thisfontgrid.setVisible(false);
                for (i=0; i<259; i++) {
                    thisfontgrid.remove(fLabels[i]);
                }
                fLabels = null;
                thisfontgrid.setVisible(true);
            }
        }
    }


    /*
     * We should implement some basic keyboards events
     */
    public void keyTyped(KeyEvent e) {
    }


    /*
     * Handle key events
     */
    public void keyPressed(KeyEvent e) {
        int code = e.getKeyCode();

        if (code == KeyEvent.VK_PAGE_UP) {
            movePrev();
            e.consume();
        }
        if (code == KeyEvent.VK_PAGE_DOWN) {
            moveNext();
            e.consume();
        }
        if (code == KeyEvent.VK_F3) {
            System.exit(0);
        }
    }


    /*
     * Ignore key release
     */
    public void keyReleased(KeyEvent e) {
    }


   /*
    * Check for equality of a byte array
    */
    public static boolean equals(byte [] b1, byte [] b2) {
        if (b1.length != b2.length)
            return false;
        for (int i=0; i<b1.length; i++) {
            if (b1[i] != b2[i])
                return false;
        }
        return true;
    }


    /*
     * On a mouse click, activate the character detail frame
     */
    public void mouseClicked(MouseEvent e) {
        int  pos = grid.overGrid(e.getX(), e.getY());

        /* Check if we are over a label */
        Object src = e.getSource();
        if (src != thisfontgrid) {
            if (fLabels != null) {
                for (int i=0; i<256; i++) {
                    if (src.equals(fLabels[i])) {
                        pos = i;
                        break;
                    }
                }
            }
        }

        int  ch  = grid.gridChar(pos);
        if (fVerbose) {
            System.out.println("Mouse click at: " + e.getX() + ", " + e.getY() +
                                " pos=" + pos + " char=" + hex4(ch));
        }
        if (ch == 0xfffe) {          /* DBCS Starter */
            iStart = CvL.selectPosition(iStart, pos);
            grid.setCodepage("", iStart);
            inDBCS = true;
            displayMessage();
            fCurGrid = -1;
            this.repaint();
        }
        if (pos >= 0 && ch!=0xfffe && ch!=0xffff) {  /* Normal char */
            if (thischar == null)
                initDetail();
            thischar.setFont(sCurFont, fCurStyle);
            if (fInUnicode == 1) {
                /*
                 * If we are in Unicode, but with a previous codepage, report the
                 * codepoint in that old codepage.  This allows the user to reverse
                 * map a unicode character.
                 */
                if (fOldEncode.length()>0) {
                    try {
                        char [] chrs;
                        int iLen;
                        if (ch >= 0x10000) {
                            chrs = new char[2];
                            chrs[0] = (char)(((ch-0x10000)/0x0400) | 0xd800);
                            chrs[1] = (char)(((ch-0x10000)%0x0400) | 0xdc00);
                            iLen  = 2;
                        } else {
                            chrs = new char[1];
                            chrs[0] = (char)ch;
                            iLen  = 1;
                        }
                        String s = new String(chrs, 0, iLen);
                        byte [] cpbytes = s.getBytes(fOldEncode);
                        byte [] subbytes = "\u02ff".getBytes(fOldEncode);
                        if (equals(cpbytes, subbytes))
                            cpbytes = null;    /* Codepoint not assigned */
                        thischar.setChar(ch, fOldEncode, cpbytes);
                    } catch (Exception ex) {
                        thischar.setChar(ch);
                    }
                } else {
                    thischar.setChar(ch);
                }
            } else {
                byte [] b;
                if (iStart == 0) {
                    b = new byte[1];
                    b[0] = (byte)pos;
                } else if (iStart <= 0x100) {
                    b = new byte[2];
                    b[0] = (byte)(iStart);
                    b[1] = (byte)pos;
                } else if (iStart < 0x10000) {
                    b = new byte[3];
                    b[0] = (byte)(iStart>>8);
                    b[1] = (byte)iStart;
                    b[2] = (byte)pos;
                } else {
                    b = new byte[4];
                    b[0] = (byte)(iStart>>16);
                    b[1] = (byte)(iStart>>8);
                    b[2] = (byte)iStart;
                    b[3] = (byte)pos;
                }
                thischar.setChar(ch, grid.getCodepage(), b);
            }
            thischar.activate();
        } else {                     /* Outside grid */
            if (pos == -2) {
                if (thisfont == null)
                     initFontDetail();
                 thisfont.setFont(sCurFont, fCurStyle, fCurSize);
                 thisfont.activate();
            }
            if (pos==-1 && inDBCS) {
                iStart >>= 8;
                if (iStart == 1)
                    iStart = 0;
                grid.setCodepage(grid.getCodepage(), iStart);
                inDBCS = iStart != 0;
                displayMessage();
                fCurGrid = -1;
                this.repaint();
            }
        }
    }

    public void mousePressed(MouseEvent e) {
    }

    public void mouseReleased(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public void mouseDragged(MouseEvent e) {
    }


    /*
     * When the mouse moves over a grid, put out details about the char
     */
    public void mouseMoved(MouseEvent e) {
        int  pos;
        int  ch;
        String s;
        pos = grid.overGrid(e.getX(), e.getY());
        if (pos != fCurGrid) {
            if (pos < 0) {
                displayMessage();
            } else {
                ch = grid.gridChar(pos);
                if (iStart > 0) {
                    s = "Code=";
                    if (iStart>0x10000) s += hex2(iStart>>16);
                    if (iStart>0x100) s += hex2(iStart>>8);
                    s += hex2(iStart) + hex2(pos);
                } else {
                    s = "Code=" + pos + " (" + hex2(pos) + ")";
                }
                if (ch < 0xfffe) {
                    s = s + "   Char=" + hex4(ch);
                    int glyph = grid.getGlyph(ch);
                    if (glyph != 0) {
                        s = s + "   Glyph=" + (glyph>>24) + "," + hex4(glyph);
                    }
                    String s2 = getUnicodeInfo(ch, 1);
                    if (s2.equals("<control>")) {
                        String desc2 = getUnicodeInfo(ch, 10);   /* Old name */
                        if (!desc2.equals(""))
                            s2 = desc2;
                    }
                    if (s2.length() > 0 ) {
                        s2 = s2.toLowerCase();
                        s = s + " - " + s2;
                    }
                } else {
                    if (ch == 0xfffe)
                        s = s + " - <Lead byte>";
                    if (ch == 0xffff)
                        s = s + " - <Unassigned>";
                }
                displayMessage(s);
            }
            fCurGrid = pos;
        }
    }


    /*
     * read in the Unicode Table, which must be in the lib directory
     */
    private static int  readUnicodeTable() {
        FileInputStream iFile;
        UniTable = new byte[1];
        UniTable[0] = (byte)1;

        String fname = System.getProperty("java.home") + File.separator +
                       "lib" + File.separator;

        File f = new File("UnicodeData.txt");
        if (!f.canRead()) {
            f = new File("unicode.tab");
            if (!f.canRead()) {
                f = new File(fname + "UnicodeData.txt");
                if (!f.canRead()) {
                    f = new File(fname + "unicode.tab");
                    if (!f.canRead()) {
                        return 1;
                    }
                }
            }
        }

        int len = (int)f.length();
        UniTable = new byte [len+2];
        UniTable[0] = (byte)1;

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

        try {
            len = iFile.read(UniTable, 0, len);
        } catch (Exception e) {
            UniTable[0] = 1;
            return 1;
        }

        UniTable[len]   = 0;
        UniTable[len+1] = 1;

        for (int i=0; i<len; i++) {
            if (UniTable[i] == (byte)'\r' || UniTable[i] == (byte)'\n') {
                UniTable[i] = 0;
            }
        }
        return 0;
    }


    /*
     * Get a hex value from an offset in the unicode table
     */
    private static int  getUnicodeValue(int offset) {
        int    val;
        int    gotone;
        char   ch;

        val = 0;
        gotone = 0;
        for(;;) {
            ch = (char)(UniTable[offset++]&0xff);
            if (ch>='0' && ch<='9') {
                ch -= '0';
            } else {
                if (ch>='a' && ch<='f') {
                    ch -= 'a'-10;
                } else {
                    if (ch>='A' && ch<='F') {
                        ch -= 'A'-10;
                    } else {
                        if (ch == ';')
                            break;
                        return 0x1000000;
                    }
                }
            }
            val *= 16;
            val += ch;
            gotone = 1;
        }
        if (gotone == 0)
            return 0x1000000;
        return val;
    }


    /*
     * Get the entire unicode info string for a character
     */
    public static String getUnicodeInfo(int uc) {
        if (uc>=0x3400 && uc<=0x4dff)
            return "3400;<CJK Extension A>;Lo;0;L;;;;;N;;;;;";
        if (uc>=0x4e00 && uc<=0x9fa5)
            return "4E00;<CJK Ideograph>;Lo;0;L;;;;;N;;;;;";
        if (uc>=0xac00 && uc<=0xd7a3)
            return "AC00;<Hangul Syllable>;Lo;0;L;;;;;N;;;;;";
        if (uc>=0xd800 && uc<=0xdbff)
            return "DB80;<High Surrogate>;Cs;0;L;;;;;N;;;;;";
        if (uc>=0xdc00 && uc<=0xdfff)
            return "DC00;<Low Surrogate>;Cs;0;L;;;;;N;;;;;";
        if (uc>=0xe000 && uc<=0xf83c)
            return "E000;<User defined character>;Co;0;L;;;;;N;;;;;";
        if (uc>=0x20000 && uc<=0x2a6ff)
            return "20000;<CJK Extension B>;Lo;0;L;;;;;N;;;;;";

        if (UniTable == null)
            readUnicodeTable();

        int offset = 0;
        if (uc >= uPrevChar)
            offset = uOffset;

        while (UniTable[offset] != 1) {
            int  i = getUnicodeValue(offset);
            int  thisuc = i;
            if (thisuc > uc && i != 0x1000000)
                break;
            int  endoffset = offset+1;
            while (UniTable[endoffset] != 0)
                endoffset++;

            if (thisuc == uc && i != 0x1000000) {
                uOffset = offset;
                uPrevChar = uc;
                return new String(UniTable, offset, endoffset-offset);
            }

            offset = endoffset;
            while (UniTable[offset] == 0)
                offset++;
        }
        return "";
    }


    /*
     * Get a field of the unicode info string for a character
     */
    public static String getUnicodeInfo(int ch, int token) {
        return getUnicodeInfo(getUnicodeInfo(ch), token);
    }


    /*
     * Get a single field from the unicode string
     */
    public static String getUnicodeInfo(String s, int token) {
        int  offset = 0;
        int  endoffset = 0;

        for (int i=0; i<token; i++) {
            try {
                offset = s.indexOf(';', offset+1);
            } catch(Exception ex) {
                offset = -1;
            }
            if (offset<0)
                return "";
        }
        endoffset = s.indexOf(';', offset+1);
        if (endoffset>offset) {
            return s.substring(offset+1, endoffset);
        }
        return "";
    }



    public String getRangeName(int uni) {
        switch (uni>>8) {
        case 0x00: return "Latin 1";
        case 0x01: return "Latin 2";
        case 0x02: return "Accents";
        case 0x03: return "Greek";
        case 0x04: return "Cyrillic";
        case 0x05: return "Hebrew";
        case 0x06: return "Arabic";
        case 0x09: return "Devanagari / Bengali";
        case 0x0a: return "Gurmukhi / Gujarati";
        case 0x0b: return "Oriya / Tamil";
        case 0x0c: return "Telugu / Kannada";
        case 0x0d: return "Malayalam";
        case 0x0e: return "Thai / Lao";
        case 0x0f: return "Tibetan";
        case 0x10: return "Georgian";
        case 0x11: return "Hangul choseong";
        case 0x1e: return "Latin extended";
        case 0x1f: return "Greek extended";
        case 0x20: return "Punctuation / currency";
        case 0x21: return "Symbols / arrows";
        case 0x22: return "Math";
        case 0x23: return "Technical";
        case 0x24: return "Enclosed";
        case 0x25: return "Boxes";
        case 0x26: return "Dingbats";
        case 0x27: return "Zapf Dingbats";
        case 0x30: return "CJK symbols / kana";
        case 0x31: return "Bopomofo / Hangul";
        case 0x32: return "CJK enclosed";
        case 0x33: return "CJK squared";
        case 0x34: return "CJK Extension A";
        case 0x4E: return "CJK";
        case 0xA0: return "Yi";
        case 0xAC: return "Hangul syllables";
        case 0xE0: return "Private use";
        case 0xF8: return "Corporate Use";
        case 0xF9: return "CJK Compatibility";
        case 0xFD: return "Arabic Ligatures";
        case 0xFE: return "Shaped Arabic 1";
        case 0xFF: return "Compatibility";
        case 0x130: return "Italic / Gothic";
        case 0x1D1: return "Musical";
        case 0x1D4: return "Math";
        case 0x200: return "CJK Extension B";
        case 0x2f8: return "CJK Compat B";
        case 0xe00: return "Tags";
        }
        return "";
    }


    /*
     * Return the name of a drawing mode
     */
    public String getDrawModeName() {
        switch (fDraw) {
        case DRAWCHARS:       return "drawChars";
        case DRAWSTRING:      return "drawString";
        case DRAWGLYPHVECTOR: return "GlyphVector";
        case DRAWTEXTLAYOUT:  return "TextLayout";
        case DRAWLABEL:       return "Label";
        }
        return "Unknown";
    }


    /*
     * Main program for fontgrid application
     */
    public static void main(String args[]) {
        String fname;
        WindowListener l = new WindowAdapter() {
            public void windowClosing(WindowEvent e) {System.exit(0);}
        };

        if (args.length>0)
            fname = args[0];
        else
            fname = "";
        Frame f = new Frame("fontgridawt " + fname);
        f.addWindowListener(l);

        fontgridawt fontg = new fontgridawt();

        Dimension sz = Toolkit.getDefaultToolkit().getScreenSize();
        //  System.out.println("Screen size = " + sz.width + " x " + sz.height);
        if (sz.width < 1000) {
           fontg.fSmall = true;
        }

        if (args.length>0 && args[0].charAt(0) == 's')
            fontg.fSmall = true;
        fontg.init(f);

        f.add(fontg);
        f.show();
    }


    /*
     * The gridCanvas class is a Swing component which displays or prints
     * the actual font grid (as opposed to the user interface).
     */
    public class gridCanvas extends Component implements Printable {
        String  line1;
        String  line2;
        String  line3;
        int     fVector[];
        int     fStart       = 0;
        String  fEncode      = "Unicode";
        int     fInUnicode = 1;
        String  sCurFont;
        public  Font    fCurFont     = null;
        int     fCurSize     = 18;
        int     fCurStyle    = Font.PLAIN;
        Font    fMonoFont    = new Font("monospaced", Font.PLAIN, 12);
        Font    fSans12      = new Font("sansserif", Font.PLAIN, 12);
        Font    fSansSerif   = new Font("sansserif", Font.PLAIN, 14);
        Font    fSerifFont   = new Font("serif", Font.BOLD, 14);
        Color   cNormal      = Color.black;
        Color   cUnknown     = Color.red;
        Color   cOther       = new Color(0, 127,   0);   /* Dark green */
        Color   cLast        = new Color(0,   0, 127);   /* Dark blue  */
        int     CpType;

        private final  String  StyleName[] = {"plain", "bold", "italic", "bolditalic"};
        private final  char    hexarray[]  = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};

        /*
         * Create the object
         */
        public gridCanvas() {
            fVector = new int[256];
            CpType = 1;           /* Unicode */
            setLineText(0);       /* quick brown fox */
        }

        /*
         * Set up the line text
         */
        public void setLineText(int which) {
            switch (which) {
            case 0:
                line1 = "the quick brown fox jumps over the lazy dog. 1234567890";
                line2 = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. ";
                line3 = "\u5316\u679c\u8766\u7623\ub488\u3400\uD840\uDC01\uD840\uDC10" +
                        " \u00c3N\u00c2OI\u00c4AW\u00d3O\u00d5 1\u20ac0$3% !@#*";
                break;

            case 1:
                line1 = "\u0395\u03bb\u03bb\u03ac\u03b4\u03b1 \u03b5\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac  " +
                        "\u0420\u043e\u0441\u0441\u0438\u044f \u0440\u0443\u0441\u0441\u043a\u0438\u0439  " +
                        "\u010cesk\u00e1 \u010de\u0161tina  " +
                        "Ti\u1ec3ng Vi\u1ec7t";
                line2 = "\u65e5\u672c \u65e5\u672c\u8a9e   " +
                        "\ub300\ud55c\ubbfc\uad6d \ud55c\uad6d\uc5b4   " +
                        "\u4e2d\u534e\u4eba\u6c11\u5171\u548c\u56fd \u4e2d\u6587 \u4e2d\u83ef\u6c11\u570b";
                line3 = "\u092d\u093e\u0930\u0924 \u0939\u093f\u0902\u0926\u0940   " +
                        "\u0e44\u0e17\u0e22 \u0e44\u0e17\u0e22   " +
                        "\u05d9\u05e9\u05e8\u05d0\u05dc \u05e2\u05d1\u05e8\u05d9\u05ea   " +
                        "\u0645\u0635\u0631 \u0627\u0644\u0639\u0631\u0628\u064a\u0629";
                break;

            case 2:
                line1 = "hi: \u0939\u093f\u0902\u0926\u0940 \u092d\u093e\u0930\u0924  " +
                        "ta: \u0ba4\u0bae\u0bbf\u0bb4\u0bcd \u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe  " +
                        "te: \u0c24\u0c46\u0c32\u0c41\u0c17\u0c41 \u0c2d\u0c3e\u0c30\u0c24 \u0c26\u0c47\u0c36\u0c02";
                line2 = "pa: \u0a2a\u0a70\u0a1c\u0a3e\u0a2c\u0a40 \u0a2d\u0a3e\u0a30\u0a24  " +
                        "gu: \u0a97\u0ac1\u0a9c\u0ab0\u0abe\u0aa4\u0ac0 \u0aad\u0abe\u0ab0\u0aa4  " +
                        "kn: \u0c95\u0ca8\u0ccd\u0ca8\u0ca1 \u0cad\u0cbe\u0cb0\u0ca4  ";
                line3 = "mr: \u092e\u0930\u093e\u0920\u0940 \u092d\u093e\u0930\u0924  " +
                        "sa: \u0938\u0902\u0938\u094d\u0915\u0943\u0924 \u092d\u093e\u0930\u0924\u092e\u094d";
                break;
            }
        }


        /*
         * Display the grid
         */
        public void paintComponent(Graphics g) {
            paintGrid(g, false);
        }


        /*
         * Draw text by the selected method (not including label)
         */
        public void drawText(Graphics g, String line, int x, int y) {
            Graphics2D g2 = null;
            int  draw = DRAWSTRING;
            if (g instanceof Graphics2D) {
                g2 = (Graphics2D) g;
                draw = fDraw;
            }
            switch (draw) {
            case DRAWSTRING:
            default:
                g.drawString(line, x, y);
                break;

            case DRAWCHARS:
                g.drawChars(line.toCharArray(), 0, line.length(), x, y);
                break;

            case DRAWGLYPHVECTOR:
                GlyphVector gv  = g2.getFont().createGlyphVector(g2.getFontRenderContext(), line);
                g2.drawGlyphVector(gv, x, y);
                break;

            case DRAWTEXTLAYOUT:
                TextLayout tl = new TextLayout(line, g2.getFont(),
                                    g2.getFontRenderContext());
                tl.draw(g2, x, y);
                break;
            }
        }


        /*
         * Draw text into a label
         */
        private void drawLabel(Graphics g, Label l, String line, int x, int y) {
            int fontsize = g.getFont().getSize();
            l.setBounds(x , y - fontsize, 600, fontsize + fontsize/4);
            l.setFont(g.getFont());
            l.setForeground(g.getColor());
            l.setText(line);
        }


        /*
         * Display or print the grid
         */
        public void paintGrid(Graphics g, boolean isPrint) {
            int iCount, i;
            int yPos, xPos;
            int yGrid, xGrid, yMore, yText;
            int Row, Col;
            int linesize;
            char s1[] = {'0', '0'};
            int gridsize = (fSmall && !isPrint) ? 22 : 30;
            int gridoffset = (fSmall && !isPrint) ? 4 : 6;
            int gridfull = gridsize*16;
            int griddescend = (fSmall && !isPrint) ? 7: 10;
            FontRenderContext frc = new FontRenderContext(new AffineTransform(), false, false);

            long t1 = System.currentTimeMillis();

            boolean anti = cAntiA.getState();
            boolean frac = cFract.getState();

            // AWT print is not a Graphics 2D instance
            int   draw = DRAWCHARS;
            Graphics2D  g2 = null;
            if (g instanceof Graphics2D) {
                draw = fDraw;
                g2 = (Graphics2D)g;

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                 anti ? RenderingHints.VALUE_ANTIALIAS_ON :
                                        RenderingHints.VALUE_ANTIALIAS_OFF);
                g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                                 frac ? RenderingHints.VALUE_FRACTIONALMETRICS_ON :
                                        RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
            }


            // Set positions for display and print
            if (isPrint) {
                yText   = 65;
                yPos    = 160;
                xPos    = 65;
                yMore   = yPos+gridfull+10;
            } else {
                yText   = 45;
                yPos    = 115;
                xPos    = 60;
                yMore   = yPos+gridfull+10;
            }
            xGrid   = xPos-gridoffset;
            yGrid   = yPos+griddescend-gridsize;

            // Clear the background
            if (!isPrint) {
                g.setColor(Color.white);
                g.fillRect(0, 0, 768, 1024);
            }
            g.setColor(cNormal);

            /*
             * Draw the header information
             */
            g.setFont(fSerifFont);
            drawText(g, sCurFont, xGrid+72, yText);
            g.setFont(fSans12);
            drawText(g, "Font Name:", xGrid, yText);
            drawText(g, "Font Size:", xGrid, yText+15);
            drawText(g, fCurSize + ((fCurStyle>0) ? ("   " + StyleName[fCurStyle]) : ""),
                     xGrid+72, yText+15);
            FontMetrics fm = g.getFontMetrics(fCurFont);
            String fs = fm.getHeight() + "," + fm.getAscent() + "," +
                        fm.getDescent() + "," + fm.getLeading();
            drawText(g, "Metrics:", xGrid+340, yText+15);
            drawText(g, fs, xGrid+412, yText+15);
            if (fInUnicode == 0) {
                String enc = fEncode;
                if (iStart != 0) {
                    enc += " [";
                    if (iStart>0x10000)
                        enc += hex2(iStart>>16) + " ";
                    if (iStart>0x100)
                        enc += hex2(iStart>>8) + " ";
                    enc += hex2(iStart) + "]";
                }
                enc = enc + " - " + ConverterList.getDescription(fEncode);
                drawText(g, "Encoding:",  xGrid, yText+30);
                drawText(g, enc, xGrid+72, yText+30);
            } else {
                drawText(g, "Unicode:", xGrid, yText+30);
                drawText(g, hex4(fStart) + " - " + hex4(fStart+255) + "  " +
                       getRangeName((char)fStart), xGrid+72, yText+30);
            }
            drawText(g, "Text mode:", xGrid+340, yText+30);
            drawText(g, getDrawModeName(), xGrid+412, yText+30);

            g.setFont(fCurFont);

            GlyphVector gv = fCurFont.createGlyphVector(frc, "\uf8f8");
            int lastFontID = gv.getGlyphCode(0)>>24;
            if (lastFontID == 0)
                lastFontID = 255;

            /*
             * Output the grid
             */
            g.setFont(fSansSerif);
            g.drawLine(xGrid, yGrid+gridfull, xGrid+gridfull, yGrid+gridfull);
            g.drawLine(xGrid+gridfull, yGrid, xGrid+gridfull, yGrid+gridfull);
            for (Row=0; Row<16; Row++) {
               s1[0] = hexarray[Row];
               s1[1] = '-';
               g.drawChars (s1, 0, 2, xPos-25, yPos + (Row*gridsize));
               g.drawLine(xGrid, yGrid+(Row*gridsize), xGrid+gridfull, yGrid+(Row*gridsize));

               for(Col=0; Col< 16; Col++) {
                   s1[0] = '-';
                   s1[1] = hexarray[Col];
                   g.drawChars (s1, 0, 2, xPos+(Col*gridsize)+3, yPos-24);
                   g.drawLine(xGrid+(Col*gridsize), yGrid, xGrid+(Col*gridsize), yGrid+gridfull);
               }
            }

            /*
             * Output the characters
             */
            g.setFont(fCurFont);
            for (Row=0; Row<16;Row++) {
                for (Col=0; Col<16; Col++) {
                    iCount = fVector[(Row * 16) + Col];
                    int iLen   = 1;
                    s1[0] = (char)iCount;
                    if (iCount>=0x10000) {
                        s1[0] = (char)(((iCount-0x10000)/0x0400) | 0xd800);
                        s1[1] = (char)(((iCount-0x10000)%0x0400) | 0xdc00);
                        iLen  = 2;
                    }

                    /*
                     * Check for DBCS lead byte
                     */
                    if (iCount == 0xfffe || iCount == 0xffff) {
                        if (iCount == 0xfffe) {
                            g.setFont(fMonoFont);
                            g.setColor(cUnknown);
                            g.drawString(hex2(Row*16+Col), xPos+(Col*gridsize), yPos + (Row*gridsize));
                            g.setFont(fCurFont);
                            g.setColor (cNormal);
                        }
                        continue;
                    }

                    /*
                     * Set the color based on displayability and what font the glyph is in
                     */
                    if (iCount >= 0x10000 || fCurFont.canDisplay((char)iCount)) {
                        if (!isPrint) {
                            gv = fCurFont.createGlyphVector(frc, s1);
                            int fontID = gv.getGlyphCode(0) >> 24;
                            if (fontID > 0 && iCount < 0x10000) {
                                if (fontID==lastFontID)
                                    g.setColor (cLast);
                                else
                                   g.setColor (cOther);
                            }
                        }
                    } else {
                        g.setColor (cUnknown);
                    }

                    /*
                     * Draw the text as requested
                     */
                    switch (draw) {
                    case DRAWCHARS:
                    default:
                        g.drawChars( s1, 0, iLen, xPos + (Col*gridsize) , yPos + (Row*gridsize)   );
                        break;

                    case DRAWSTRING:
                        g.drawString( new String(s1, 0, iLen), xPos + (Col*gridsize) , yPos + (Row*gridsize)   );
                        break;

                    case DRAWGLYPHVECTOR:
                        gv  = fCurFont.createGlyphVector(g2.getFontRenderContext(),
                                                         new String(s1, 0, iLen) );
                        g2.drawGlyphVector(gv, xPos + (Col*gridsize) , yPos + (Row*gridsize));
                        break;

                    case DRAWTEXTLAYOUT:
                        TextLayout tl = new TextLayout(new String(s1, 0, iLen), fCurFont,
                                            g2.getFontRenderContext());
                        tl.draw(g2, xPos + (Col*gridsize) , yPos + (Row*gridsize));
                        break;
                    case DRAWLABEL:
                        Label l = fLabels[Row*16+Col];
                        if (!fLabelPos) {
                            l.setBounds(xPos-gridoffset+3 + (Col*gridsize) , yPos + ((Row-1) *gridsize) + griddescend +1,
                                  gridsize-4, gridsize-2);
                        }
                        l.setFont(fCurFont);
                        l.setForeground(g.getColor());
                        l.setText(new String(s1, 0, iLen));
                        break;
                    }
                    g.setColor (cNormal);
                }
            }

            /*
             * Output some sample text
             */
            linesize = fCurSize + fCurSize/4;
            if (draw == DRAWLABEL) {
                drawLabel(g, fLabels[256], line1, xGrid, yMore);
                drawLabel(g, fLabels[257], line2, xGrid, yMore+linesize);
                drawLabel(g, fLabels[258], line3, xGrid, yMore+linesize*2);
            } else {
                drawText(g, line1, xGrid, yMore);
                drawText(g, line2, xGrid, yMore+linesize);
                drawText(g, line3, xGrid, yMore+linesize*2);
            }

            fLabelPos = true;
            long t2 = System.currentTimeMillis();
            if (fVerbose) {
                System.out.println("Paint Time " + (t2-t1) + " milliseconds");
            }
        }


        /*
         * Print using the Java2D method
         */
        public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException {
            if (pageIndex>0)
                return Printable.NO_SUCH_PAGE;
            paintGrid(g, true);
            return Printable.PAGE_EXISTS;
        }


        /*
         * Set to display a unicode range
         */
        public void setCodepage(int start) {
            fInUnicode = 1;
            fEncode = "Unicode";
            CpType  = 1;
            inDBCS  = false;
            fStart  = start;
            for (int i=0; i<256; i++) {
                fVector[i] = start+i;
            }
        }


        /*
         * Get the current codepage
         */
        public String getCodepage() {
            return fEncode;
        }


        /*
         * Set to display a codepage
         */
        public void setCodepage(String name) {
            setCodepage(name, 0);
        }


        /*
         * Set to display a codepage range.  Only zero currently works.
         * Only SBCS codepages currently work.
         */
        public void setCodepage(String name, int start) {
            byte    byte1 [];
            byte    byte2 [];
            int     i;
            String  s;

            if (!name.equals("")) {
                if (name.equals("Unicode"))
                    setCodepage(start);

                fInUnicode = 0;

                i = name.indexOf(' ');
                if (i > 0)
                    name = name.substring(0, i);

                name = CvL.setConverter(name);
                if (name.equals("")) {
                    displayMessage("Encoding not found: " + name);
                    setCodepage(0);
                    return;
                }
                fEncode = name;
            }

            iStart  = start;
            fVector = CvL.getChars(iStart);
            CpType = 0;
        }



        /*
         * Return the character at that pixel, 0xffff if outside grid
         */
        public int overChar(int x, int y) {
            int  pos = overGrid(x, y);
            return (pos < 0) ? (char)0xFFFF : fVector[pos];
        }


        /*
         * Return the grid position (0-256), -1 if outside grid
         */
        public int  overGrid(int x, int y) {
            int gridsize = fSmall ? 22 : 30;
            int xgrid    = fSmall ? 56 : 54;
            int ygrid    = fSmall ? 100 : 95;

            if (y < ygrid && x > xgrid)
                return -2;
            if (x < xgrid || y < ygrid ||
                x >= (xgrid + gridsize*16) || y >= (ygrid + gridsize*16))
                return -1;

            return ((x-xgrid)/gridsize) + (((y-ygrid)/gridsize)*16);
        }


        /*
         * Return the character at a specified grid position
         */
        public int   gridChar(int pos) {
            if (pos>=0 && pos<256)
                return fVector[pos];
            else
                return (char)0xffff;
        }


        /*
         * Get the glyph for a specified character in the current font
         */
        public int   getGlyph(int ch) {
            char [] s1;
            if (ch<0x10000) {
                s1 = new char[1];
                s1[0] = (char)ch;
            } else {
                s1 = new char[2];
                s1[0] = (char)(((ch-0x10000)/0x0400) | 0xd800);
                s1[1] = (char)(((ch-0x10000)%0x0400) | 0xdc00);
            }
            FontRenderContext frc = new FontRenderContext(new AffineTransform(), false, false);
            GlyphVector gv = fCurFont.createGlyphVector(frc, s1);
            return gv.getGlyphCode(0);
        }


        /*
         * Set the font, style, and size
         */
        public void setFont(String name, int style, int size) {
            sCurFont  = name;
            fCurStyle = style;
            fCurSize  = size;
            fCurFont = new Font(sCurFont, fCurStyle, fCurSize);
            /* Change font details if it is up */
            if (thisfont != null)
                thisfont.setFont(sCurFont, fCurStyle, fCurSize);
        }

    }


    /*
     * The charDetails class is a Swing component which displays details
     * of a single charactor
     */
    public class charDetails extends Component implements ActionListener {
        private int     fCh;
        private Font    fFont;
        private boolean fActive;
        private String  fUnicode;
        private String  fsFont;
        private String  fsStyle;
        private int     fValue;
        private String  fsValue;
        private int     fType;
        private String  fsType;
        private String  fDescription;
        private String  fDirection;
        private byte [] fCodepoint;
        private String  fsCodepoint;
        private String  fEncoding;
        private Font    fSansSerif;
        private int     BigFontSize = 200;
        private Frame   thisFrame;
        private Button  bDetach;
        private Button  bClose;
        private int     ypos;
        private int     xname;
        private int     xequal;
        private int     xdata;
        private int     linesz;


        /*
         * Create the character canvas in the details frame
         */
        public charDetails(Frame f, KeyListener keylist) {

            thisFrame = f;
            Panel p = new Panel();
            WindowListener l = new WindowAdapter() {
                public void windowClosing(WindowEvent e) {deactivate();}
            };
            thisFrame.addWindowListener(l);

            /* Create the Detach button */
            bDetach = new Button("Detach");
            bDetach.setFont(dFont);
            bDetach.addActionListener(this);
            bDetach.addKeyListener(keylist);
            p.add(bDetach);

            /* Create the close button */
            bClose = new Button("Close");
            bClose.setFont(dFont);
            bClose.addActionListener(this);
            bClose.addKeyListener(keylist);
            p.add(bClose);

            /* Add the char display and button panels to the frame */
            f.setLayout(new BorderLayout());
            f.add(p, BorderLayout.SOUTH);
            f.add(this);

            /* Set up the display */
            if (fSmall) {
                fSansSerif  = new Font("sansserif", Font.PLAIN, 10);
                BigFontSize = 100;
            } else {
                fSansSerif  = new Font("sansserif", Font.PLAIN, 12);
                BigFontSize = 150;
            }

            /* Set positions */
            xname  = 12;
            xequal = fSmall ? 73 : 83;
            xdata  = fSmall ? 83 : 94;
            linesz = fSmall ? 10 : 12;

            /* Start out hidden */
            setFont("sansserif", 0);
            setChar('A');
            fActive = false;
        }


        /*
         * Set a unicode character for the details frame
         */
        public void setChar(int ch) {
            byte [] codept = new byte[2];
            codept[0] = (byte)(ch>>8);
            codept[1] = (byte)(ch);
            setChar(ch, "Unicode", codept);
        }


        /*
         * Set the character for the details frame
         */
        public void setChar(int ch, String Encoding, byte [] codept) {
            fCh       = ch;
            fType     = ch<0x10000 ? Character.getType((char)ch) : 0;
            fValue    = ch<0x10000 ? Character.getNumericValue((char)ch) : -1;
            if (fValue < 0) {
                fsValue = (fValue == -2) ? "<fraction>" : "";
            } else {
                fsValue = ""+fValue;
            }
            fsType    = typeName(fType);
            fUnicode  = getUnicodeInfo(ch);
            fDescription = getUnicodeInfo(fUnicode, 1);   /* Current name */
            if (!fDescription.equals("")) {
                if (fDescription.equals("<control>")) {
                    String desc2 = getUnicodeInfo(fUnicode, 10);   /* Old name */
                    if (!desc2.equals(""))
                        fDescription = desc2;
                }
                if (fDescription.charAt(0) != '<')
                    fDescription = fDescription.toLowerCase();
            }
            fDirection = getUnicodeInfo(fUnicode, 4);
            if (fDirection.equals("ON") && getUnicodeInfo(fUnicode, 9).equals("Y"))
                fDirection = "MN";
            fEncoding = Encoding;
            if (codept == null) {
                fCodepoint = new byte[1];
                fCodepoint[0] = 0;
                fsCodepoint = "<unassigned>";
            } else {
                fCodepoint = codept;
                fsCodepoint = "";
                for (int i=0; i<codept.length; i++) {
                    fsCodepoint = fsCodepoint + hex2(codept[i]);
                }
            }
        }


        /*
         * Set the font for the details frame
         */
        public void setFont(String fontname, int fontstyle) {
            fFont = new Font(fontname, fontstyle, BigFontSize);
            fsFont = fontname;
            fsStyle = "";
            switch (fontstyle) {
                case Font.BOLD:               fsStyle = "Bold";       break;
                case Font.ITALIC:             fsStyle = "Italic";     break;
                case Font.BOLD | Font.ITALIC: fsStyle = "BoldItalic"; break;
            }
        }


        /*
         * Activate this details frame
         */
        public void activate() {
            thisFrame.show();
            if (fActive)
                thisFrame.repaint();
            fActive = true;
        }


        /*
         * Deactivate this details frame
         */
        public void deactivate() {
            thisFrame.hide();
            fActive = false;
        }


        /*
         * Handle the buttons on the character details frame
         */
        public void actionPerformed(ActionEvent ev) {
            String label = ev.getActionCommand();
            if (label.equals("Detach")) {
                 if (this.equals(thischar)) {
                     thischar = null;
                     fDetail  = null;
                 };
                 bDetach.setEnabled(false);
            }

            if (label.equals("Close")) {
                deactivate();
            }
        }


        /*
         * Convert font metrics to a 2048/em based system
         */
        private int  intMetrics(double val) {
            return (int)(((val/BigFontSize) * 2048) + 0.5);
        }


        /*
         * Put out one line of details info
         */
        private void putLine(Graphics g, String name, String value) {
            if (value.equals(""))
                return;
            g.drawString(name,  xname,  ypos);
            g.drawString("=",   xequal, ypos);
            g.drawString(value, xdata,  ypos);
            ypos += linesz;
        }


        /*
         * Paint the character detail
         */
        public void paint(Graphics g) {
            paintComponent(g);
        };


        /*
         * paint the charDetails
         */
        public void paintComponent(Graphics g) {
            /* Paint the background */
            Graphics2D g2 = (Graphics2D)g;
            Dimension sz = getSize();
            g.setColor(Color.white);
            g.fillRect(0, 0, sz.width, sz.height);

            /* Get the font */
            char [] s1;
            if (fCh<0x10000) {
                s1 = new char[1];
                s1[0] = (char)fCh;
            } else {
                s1 = new char[2];
                s1[0] = (char)(((fCh-0x10000)/0x0400) | 0xd800);
                s1[1] = (char)(((fCh-0x10000)%0x0400) | 0xdc00);
            }
            g2.setFont(fFont);
            GlyphVector gv  = fFont.createGlyphVector(g2.getFontRenderContext(), s1);
            int glyph = gv.getGlyphCode(0);
            GlyphMetrics gm = gv.getGlyphMetrics(0);
            FontMetrics fm = g2.getFontMetrics();

                 ypos = BigFontSize*11/10;
            int  xpos = BigFontSize/2 + 10;
            int  xadv = (int)(gm.getAdvance()+0.5);
            g2.setColor(Color.blue);
            g2.drawRect( xpos, ypos-fm.getAscent(), xadv, fm.getDescent()+fm.getAscent());
            g2.setColor(Color.red);
            if (xadv<2) {
                g2.drawLine ( xpos-2, ypos, xpos+2, ypos);
            } else {
                g2.drawLine ( xpos, ypos, xpos+xadv, ypos);
            }

            g2.setColor(Color.black);
            if (fDetailDraw == DRAWLABEL) {
                Font myFont = fFont.deriveFont((float)fCurSize);
                g2.setFont(myFont);
                GlyphVector gvx = myFont.createGlyphVector(g2.getFontRenderContext(), s1);
                Shape shp = gvx.getGlyphOutline(0);

                float [] txvec = {BigFontSize/fCurSize, 0, 0, BigFontSize/fCurSize, xpos, ypos};
                AffineTransform tx = new AffineTransform(txvec);
                shp = tx.createTransformedShape(shp);
                g2.draw(shp);
            } else {
                if (fDetailDraw == DRAWTEXTLAYOUT) {
                    TextLayout tl = new TextLayout(new String(s1, 0, 1), fFont, g2.getFontRenderContext());
                    tl.draw(g2, xpos, ypos);
                } else {
                    g2.drawGlyphVector( gv, xpos, ypos);
                }
            }

            /* Convert metrics to integer for display*/
            Rectangle2D gb = gm.getBounds2D();
            int  advance = intMetrics(gm.getAdvance());
            int  lsb     = intMetrics(gm.getLSB());
            int  rsb     = intMetrics(gm.getRSB());
            int  minx    = intMetrics(gb.getMinX());
            int  miny    = intMetrics(-gb.getMaxY());
            int  maxx    = intMetrics(gb.getMaxX());
            int  maxy    = intMetrics(-gb.getMinY());

            /*
             * Print out glyph data
             */
            g.setFont(fSansSerif);
            ypos   = BigFontSize*3/2;
            putLine(g, "Font",  fsFont);
            putLine(g, "Style", fsStyle);
            if (!fEncoding.equals("Unicode")) {
                putLine(g, "Codepoint",  fEncoding + " - " + fsCodepoint);
            }
            putLine(g, "Unicode",  hex4(fCh));
            putLine(g, "Description", fDescription);
            putLine(g, "Type",     fsType);
            if (glyph > 0) {
                putLine(g, "GlyphCode", (glyph>>24)+","+hex4(glyph));
                putLine(g, "Width",    advance + "   [ " + lsb + "  " + rsb + " ]");
                putLine(g, "Bounds",   minx + "  " + miny + "  " + maxx + "  " + maxy);
            }
            putLine(g, "Value", fsValue);
            putLine(g, "Compose", getUnicodeInfo(fUnicode, 5));
            putLine(g, "Direction", directionName(fDirection));
        }


        /*
         * Convert the direction code in the Unicode table to a string
         */
        private String directionName(String d) {
            if (d.equals("B"))   return "Paragraph separator";
            if (d.equals("L"))   return "Left to right";
            if (d.equals("R"))   return "Right to left";
            if (d.equals("S"))   return "Segement separator";
            if (d.equals("AL"))  return "Right to left Arabic";
            if (d.equals("AN"))  return "Arabic number";
            if (d.equals("B"))   return "Block separator";
            if (d.equals("BN"))  return "Boundary neutral";
            if (d.equals("CS"))  return "Common number separator";
            if (d.equals("EN"))  return "European number";
            if (d.equals("ES"))  return "European number separator";
            if (d.equals("ET"))  return "European number terminator";
            if (d.equals("MN"))  return "Mirrored neutral";
            if (d.equals("ON"))  return "Other Neutral";
            if (d.equals("S"))   return "Segment separator";
            if (d.equals("WS"))  return "Whitespace";
            if (d.equals("LRE")) return "Left to right embedding";
            if (d.equals("LRO")) return "Left to right override";
            if (d.equals("NSM")) return "Non-spacing mark";
            if (d.equals("PDF")) return "Pop directional format";
            if (d.equals("RLE")) return "Right to left embedding";
            if (d.equals("RLO")) return "Right to left override";
            return "Unknown";
        }


        /*
         * Convert the Java character type code to a string
         */
        private String typeName(int type) {
            switch (type) {
            case Character.UNASSIGNED            : return "Unassigned";
            case Character.UPPERCASE_LETTER      : return "Uppercase Letter";
            case Character.LOWERCASE_LETTER      : return "Lowercase Letter";
            case Character.TITLECASE_LETTER      : return "Titlecase Letter";
            case Character.MODIFIER_LETTER       : return "Modifier Letter";
            case Character.OTHER_LETTER          : return "Other Letter";
            case Character.NON_SPACING_MARK      : return "Non Spacing Mark";
            case Character.ENCLOSING_MARK        : return "Enclosing Mark";
            case Character.COMBINING_SPACING_MARK: return "Combining Spacing Mark";
            case Character.DECIMAL_DIGIT_NUMBER  : return "Decimal Digit";
            case Character.LETTER_NUMBER         : return "Letter Number";
            case Character.OTHER_NUMBER          : return "Other Number";
            case Character.SPACE_SEPARATOR       : return "Space Separator";
            case Character.LINE_SEPARATOR        : return "Line Separator";
            case Character.PARAGRAPH_SEPARATOR   : return "Paragraph Separator";
            case Character.CONTROL               : return "Control";
            case Character.FORMAT                : return "Format";
            case Character.PRIVATE_USE           : return "Private Use";
            case Character.SURROGATE             : return "Surrogate ";
            case Character.DASH_PUNCTUATION      : return "Dash Punctuation";
            case Character.START_PUNCTUATION     : return "Start Punctuation ";
            case Character.END_PUNCTUATION       : return "End Punctuation";
            case Character.CONNECTOR_PUNCTUATION : return "Connector Punctuation";
            case Character.OTHER_PUNCTUATION     : return "Other Punctuation";
            case Character.MATH_SYMBOL           : return "Math Symbol";
            case Character.CURRENCY_SYMBOL       : return "Currency Symbol";
            case Character.MODIFIER_SYMBOL       : return "Modifier Symbol";
            case Character.OTHER_SYMBOL          : return "Other Symbol";
            }
            return "Unknown";
        }
    }   /* End of charDetails class */

    /*
     * The fontDetails class is a Swing component which displays details
     * of a single charactor
     */
    public class fontDetails extends Component implements ActionListener {

        private int     ypos;
        private int     xname;
        private int     xequal;
        private int     xdata;
        private int     linesz;
        private Font    fSansSerif;
        private Frame   thisFrame;
        private Button  bClose;
        private Button  bDetach;
        private Font    fFont;
        private boolean fActive;
        private String  fUnicode;
        private String  fsFont;
        private String  fsStyle;

        /*
         * Create the character canvas in the details frame
         */
        public fontDetails(Frame f, KeyListener keylist) {

            thisFrame = f;
            Panel p = new Panel();
            if (fVerbose)
                System.out.println("Initialize fontDetails");

            /* Create the Detach button */
            bDetach = new Button("Detach");
            bDetach.setFont(dFont);
            bDetach.addActionListener(this);
            bDetach.addKeyListener(keylist);
            p.add(bDetach);

            /* Create the close button */
            bClose = new Button("Close");
            bClose.setFont(dFont);
            bClose.addActionListener(this);
            bClose.addKeyListener(keylist);
            p.add(bClose);

            /* Add the char display and button panels to the frame */
            f.setLayout(new BorderLayout());
            f.add(p, BorderLayout.SOUTH);
            f.add(this);

            /* Set up the display */
            if (fSmall) {
                fSansSerif  = new Font("sansserif", Font.PLAIN, 10);
            } else {
                fSansSerif  = new Font("sansserif", Font.PLAIN, 12);
            }

            /* Set positions */
            xname  = 12;
            xequal = fSmall ? 73 : 83;
            xdata  = fSmall ? 83 : 94;
            linesz = fSmall ? 10 : 12;

            fActive = false;
        }


        /*
         * Put out one line of details info
         */
        private void putLine(Graphics g, String name, String value) {
            if (value.equals(""))
                return;
            g.drawString(name,  xname,  ypos);
            g.drawString("=",   xequal, ypos);
            g.drawString(value, xdata,  ypos);
            ypos += linesz;
        }


        /*
         * Set the font for the details frame
         */
        public void setFont(String fontname, int fontstyle, int fontsize) {
            fFont = new Font(fontname, fontstyle, fontsize);
            fsStyle = "";
            switch (fontstyle) {
                case Font.BOLD:               fsStyle = "Bold";       break;
                case Font.ITALIC:             fsStyle = "Italic";     break;
                case Font.BOLD | Font.ITALIC: fsStyle = "BoldItalic"; break;
            }
            if (fActive)
                thisFrame.repaint();
        }


        /*
         * Activate this details frame
         */
        public void activate() {
            thisFrame.show();
            if (fActive)
                thisFrame.repaint();
            fActive = true;
        }


        /*
         * Deactivate this details frame
         */
        public void deactivate() {
            thisFrame.hide();
            fActive = false;
        }


        /*
         * Handle the buttons on the character details frame
         */
        public void actionPerformed(ActionEvent ev) {
            String label = ev.getActionCommand();
            if (label.equals("Detach")) {
                 if (this.equals(thisfont)) {
                     thisfont     = null;
                     fFontDetail  = null;
                 };
                 bDetach.setEnabled(false);
            }
            if (label.equals("Close")) {
                deactivate();
            }
        }


        /*
         * Paint the font detail
         */
        public void paint(Graphics g) {
            paintComponent(g);
        };


        /*
         * paint the fontDetails
         */
        public void paintComponent(Graphics g) {
            /* Paint the background */
            int i;
            Graphics2D g2 = (Graphics2D)g;
            Dimension sz = getSize();
            g.setColor(Color.white);
            g.fillRect(0, 0, sz.width, sz.height);
            FontRenderContext frc = new FontRenderContext(new AffineTransform(), false, false);

            if (fprop == null)
                fprop = new FontProperties();

            /* Get the font */
            g2.setColor(Color.black);

            /* Convert metrics to integer for display*/
            LineMetrics lm = fFont.getLineMetrics("", frc);
            Rectangle2D gb = fFont.getMaxCharBounds(frc);
            double  minx   = gb.getMinX();
            double  miny   = -gb.getMaxY();
            double  maxx   = gb.getMaxX();
            double  maxy   = -gb.getMinY();

            /*
             * Print out glyph data
             */
            g.setFont(fSansSerif);
            ypos   = 30;
            putLine(g, "Font",  fFont.getFontName());
            putLine(g, "Style", fsStyle);
            putLine(g, "Size",  ""+fFont.getSize2D());
            putLine(g, "Family",  fFont.getFamily());
            putLine(g, "Height",  lm.getHeight() + "   [ " + lm.getAscent() + "  " +
                    lm.getDescent() + " " + lm.getLeading() + " ]");
            putLine(g, "Bounds",   minx + "  " + miny + "  " + maxx + "  " + maxy);
            putLine(g, "Glyphs",  ""+fFont.getNumGlyphs());
            putLine(g, "Italic",  ""+fFont.getItalicAngle());

            ypos += linesz;
            int count = fprop.getCompositeCount(fFont.getFontName());
            for (i=0; i<count; i++) {
                putLine(g, "Font "+i, fprop.getDescriptorName(fFont.getFontName(), i));
            }

            ypos += linesz;
            putLine(g, "File",     fprop.getFilename());
            putLine(g, "Locale",   fprop.getLanguage() + "_" + fprop.getRegion());
            putLine(g, "Encoding", fprop.getEncoding());
            putLine(g, "OSVersion", fprop.getOSVersion());
        }
    }
}
