/*
 * %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
 * ===========================================================================
 */
/*
 * fontgrid - Display/Print a font grid
 *
 * Version:  2.0  19 June 2003
 *
 * 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 Swing UI components and to display the font grid.
 *
 *     This version supports display of almost all converters supported by Java.
 *
 *     See fontgrid.htm for usage details
 *
 * Author:
 *     Ken Borgendale - kwb@us.ibm.com Ken@Borgendale.com
 *
 */
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.metal.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.awt.image.*;
import java.applet.*;
import java.awt.font.*;
import java.io.*;
import java.util.*;
import java.text.*;
import java.lang.*;
import java.net.*;
import java.awt.print.*;

public class fontgrid extends JPanel implements ActionListener, ItemListener,
                     LayoutManager, KeyListener, MouseListener, MouseMotionListener
{
    int        fInUnicode = 1;
    JComboBox  cFnt;
    JComboBox  cPage;
    JComboBox  cSize;
    JComboBox  cEncode;
    JComboBox  cStyle;
    String     sCurFont   = null;
    int        fCurSize   = 18;
    int        fCurStyle  = Font.PLAIN;
    int        iStart     = 0;
    boolean    inDBCS     = false;
    int        cEncodeMax;
    int        fCurGrid   = -1;
    JPanel     cPanel;
    JLabel     tInfo;
    JScrollPane pane;
    Font       dFont;
    Font       dFont2;
    Font       dFont3;
    BackupFont bf;
    JFrame     f;
    static     UnicodeData uData;
    fontgrid   thisfontgrid;
    Insets     insets = new Insets( 1, 1, 1, 1 );
    JFrame      fDetail;
    JFrame      fFontDetail;
    JFrame      fHelp;
    JEditorPane fHelpDoc;
    charDetails thischar;
    fontDetails thisfont;
    String     fOldEncode = "";
    FontProperties fprop;
    int        uniPlane;
    Properties fgp;

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

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

    JMenuBar   menuBar;

    JMenu      fileMenu;
    JMenu      viewMenu;
    JMenu      optionsMenu;
    JMenu      drawMenu;
    JMenu      detailsMenu;
    JMenu      helpMenu;
    JMenu      uniPlaneMenu;

    JCheckBoxMenuItem  drawcharsMenu;
    JCheckBoxMenuItem  drawstringMenu;
    JCheckBoxMenuItem  glyphvectorMenu;
    JCheckBoxMenuItem  textlayoutMenu;
    JCheckBoxMenuItem  labelMenu;
    JCheckBoxMenuItem  charglyphvectorMenu;
    JCheckBoxMenuItem  chartextlayoutMenu;
    JCheckBoxMenuItem  charoutlineMenu;
    JCheckBoxMenuItem  charpixelMenu;
    JCheckBoxMenuItem  unicodeMenu;
    JCheckBoxMenuItem  codepageMenu;
    JCheckBoxMenuItem  foxMenu;
    JCheckBoxMenuItem  langMenu;
    JCheckBoxMenuItem  indicMenu;
    JCheckBoxMenuItem  notextMenu;
    JCheckBoxMenuItem  cAntiA;
    JCheckBoxMenuItem  cFract;
    JCheckBoxMenuItem  cAllFnt;
    JCheckBoxMenuItem  cBkFnt;
    JCheckBoxMenuItem  cVerbose;
    JCheckBoxMenuItem  cOrder;
    JCheckBoxMenuItem  cColor;
    JCheckBoxMenuItem  cDetails;
    JCheckBoxMenuItem  [] Plane;

    private KeyStroke pgup = KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0);
    private KeyStroke pgdn = KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0);
    private KeyStroke exit = KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0);

    public  gridCanvas grid;
    public  boolean   fSmall = false;
    public  boolean   fVerbose = false;
    public  boolean   fQuiet   = false;
    public  boolean   fUseColor = true;
    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'};

    final private String javaVersion = System.getProperty("java.version");
    final private String libPath  = System.getProperty("java.home") + File.separator + "lib";
    private String  user_dir  = null;
    private String  user_home = null;

    /*
     * Default constructor
     */
    public fontgrid() {
        this(new String[0]);
    }

    /*
     * Constructor with args
     */
    public fontgrid(String [] args) {
        thisfontgrid = this;

        /*
         * Determine user.home and user.dir
         */
        try {
            user_home = System.getProperty("user.home");
            user_dir  = System.getProperty("user.dir");
        } catch (Exception e) {};
        if (user_home == null)
            user_home = ".";
        if (user_dir == null)
            user_dir  = ".";

        /*
         * Set up the profile
         */
        int startarg = 0;
        String profile = null;
        if (args.length>0) {
            if (args[0].indexOf('=') < 0 && args[0].indexOf(':') < 0) {
                profile = args[0];
                startarg = 1;
            }
        }
        fgp = loadProperties(profile, "fontgrid");
        for (int i=startarg; i<args.length; i++) {
            int posequals = args[i].indexOf('=');
            int poscolon  = args[i].indexOf(':');
            if (posequals < 0 || (poscolon > 0 && poscolon<posequals))
                posequals = poscolon;
            if (posequals > 0)
            fgp.setProperty(args[i].substring(0, posequals),
                            args[i].substring(posequals+1));
        }

        /*
         * Check for size
         */
        this.f = f;
        Dimension sz = Toolkit.getDefaultToolkit().getScreenSize();
        int maxsmall = 1000;
        try {
            maxsmall = Integer.parseInt(fgp.getProperty("small", "1000"));
            if (sz.width < maxsmall)
                fSmall = true;
        } catch (Exception e) {
            fSmall = boolProperty("small");
        }
    }


    /*
     * hex4: Create a 4 to 6 character string representing a int
     */
    public static String hex4(int val) {
        char [] hexs;
        if ((val&0xffffff) >= 0x100000) {
            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];
        } else 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);
    }


    /*
     * Find and load fontgrid properties
     */
    private Properties loadProperties(String profile, String appl) {
        File f = null;
        InputStream is = null;
        Properties p = new Properties();

        try {
            if (profile != null) {
                f = new File(user_home, profile+".properties");
                if (f.canRead()) {
                    is = new FileInputStream(f);
                } else {
                    f = new File(user_dir, profile+".properties");
                    if (f.canRead())
                        is = new FileInputStream(f);
                }
            }
            if (is == null) {
                f = new File(user_home, appl+".properties");
                if (f.canRead()) {
                    is = new FileInputStream(f);
                } else {
                    f = new File(user_dir, appl+".properties");
                    if (f.canRead())
                        is = new FileInputStream(f);
                }
            }
            if (is != null) {
                try {
                    p.load(is);
                } catch (Exception e) {
                    System.err.println("Unable to load properties: " + f);
                }
            }
        } catch (IOException e) {
            e.printStackTrace(System.err);
        }
        return p;
    }


    /*
     * Save the fontgrid properties based on the current settings
     */
    private void putProperties(String dir, String appl) {
        Properties pr = new Properties();
        String p;

        pr.setProperty("all.fonts", ""+cAllFnt.getState());
        pr.setProperty("antialias", ""+cAntiA.getState());
        if ((p=fgp.getProperty("backup.small")) != null)
            pr.setProperty("backup.small", p);
        if ((p=fgp.getProperty("backup.font")) != null)
            pr.setProperty("backup.font", p);
        pr.setProperty("bitmap.font", ""+cBkFnt.getState());
        if ((p=(String)cEncode.getSelectedItem()) != null) {
            if (p.indexOf(' ') >= 0)
                p = p.substring(0, p.indexOf(' '));
            pr.setProperty("codepage", p);
        }
        pr.setProperty("details", ""+cDetails.getState());
        if (drawcharsMenu.getState())
            pr.setProperty("draw", "drawChars");
        if (drawstringMenu.getState())
            pr.setProperty("draw", "drawString");
        if (glyphvectorMenu.getState())
            pr.setProperty("draw", "GlyphVector");
        if (textlayoutMenu.getState())
            pr.setProperty("draw", "TextLayout");
        if (charglyphvectorMenu.getState())
            pr.setProperty("draw.details", "GlyphVector");
        if (chartextlayoutMenu.getState())
            pr.setProperty("draw.details", "TextLayout");
        if (charoutlineMenu.getState())
            pr.setProperty("draw.details", "outline");
        if (charpixelMenu.getState())
            pr.setProperty("draw.details", "pixels");
        pr.setProperty("font", (String)cFnt.getSelectedItem());
        pr.setProperty("fractional", ""+cFract.getState());
        if (foxMenu.getState())
            pr.setProperty("sample", "latin");
        if (langMenu.getState())
            pr.setProperty("sample", "other");
        if (indicMenu.getState())
            pr.setProperty("sample", "indic");
        if (notextMenu.getState())
            pr.setProperty("sample", "none");
        pr.setProperty("unicode", ""+unicodeMenu.getState());
        if ((p=fgp.getProperty("unicode.data")) != null)
            pr.setProperty("unicode.data", p);
        if ((p=fgp.getProperty("small")) != null)
            pr.setProperty("small", p);
        pr.setProperty("verbose", ""+cVerbose.getState());
        pr.setProperty("vertical", ""+cOrder.getState());

        try {
            File f = new File(dir, appl+".properties");
            FileOutputStream fos = new FileOutputStream(f);
            pr.store(fos, "fontgrid properties");
        } catch (IOException e) {
            System.out.println("Unable to write properties");
        }
    }


    /*
     * Return the state of a boolean property which default false
     */
    private boolean boolProperty(String name) {
        return fgp.getProperty(name, "false").equalsIgnoreCase("true");
    }

    private void setDefaultSize(boolean noSample) {
        if (fSmall) {
            f.setSize(410, 502 + (fDetails?45:0) + (noSample?0:60));
        } else {
            f.setSize(535, 630 + (fDetails?45:0) + (noSample?0:72));
        }
    }


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

        fVerbose  = boolProperty("verbose");
        fUseColor = !fgp.getProperty("use.colors", "true").equalsIgnoreCase("false");
        fDetails  = boolProperty("details");
        fUseBkFont = boolProperty("bitmap.font");
        String sample = fgp.getProperty("sample", "latin");
        String draw   = fgp.getProperty("draw", "drawString");
        String drawch = fgp.getProperty("draw.details", "pixels");
        boolean noSample = "none".equals(sample);

        this.f = f;
        setDefaultSize(noSample);
        if (fSmall) {
            dFont = new Font("dialog", 1, 11);
            dFont2 = new Font("dialog", 0, 11);
            dFont3 = new Font("dialog", 1, 11);
            bf = new BackupFont(fgp.getProperty("backup.small", "ucgl_14.zip"));
        } else {
            dFont = new Font("dialog", 1, 12);
            dFont2 = new Font("dialog", 0, 12);
            dFont3 = new Font("dialog", 1, 12);
            bf = new BackupFont(fgp.getProperty("backup.font", "ucgl_18.zip"));
        }


        inSetup = true;
        createMenuBar();

        setLayout(new BorderLayout());

        grid = new gridCanvas();
        grid.addKeyListener(this);
        grid.addMouseListener(this);
        grid.addMouseMotionListener(this);
        grid.setOrder(cOrder.getState());
        pane = new JScrollPane();
        pane.addKeyListener(this);
        setHotkeys(pane);
        pane.setViewportView(grid);
        add(pane, BorderLayout.CENTER);

        JPanel pBot = new JPanel();
        pBot.setLayout(new BorderLayout());



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

        /*
         * Set up the top area
         */
        JPanel p = new JPanel();
        cPanel = p;
        setHotkeys(p);
        p.setLayout(this);
        add(p, BorderLayout.NORTH);

        cFnt = addCombo((fUseBkFont?null:p), "Select font name to display");
        sCurFont = setupFonts(cFnt, (cAllFnt.getState()?1:0));

        cStyle = addCombo((fUseBkFont?null:p), "Select font style to display");
        cStyle.addItem("plain");
        cStyle.addItem("italic");
        cStyle.addItem("bold");
        cStyle.addItem("bolditalic");

        cSize = addCombo(p, "Select font size to display");
        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.setSelectedIndex(7);                          /* 14 point*/
            fCurSize = 14;
        } else {
            cSize.setSelectedIndex(11);                         /* 18 point*/
            fCurSize = 18;
        }
        grid.setFont(sCurFont, fCurStyle, fCurSize);

        boolean useCodepage = "false".equals(fgp.getProperty("unicode", "true"));

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

        /*
         * Make the range list
         */
        cPage = addCombo((useCodepage?p:null), "Select range of unicode characters");

        if (useCodepage)
            fInUnicode = 0;

        inSetup = false;

        /*
         * Set the menu status based on profile entries
         */
        if ("none".equals(sample))
            notextMenu.setState(true);
        else if ("other".equals(sample))
            langMenu.setState(true);
        else if ("indic".equals(sample))
            indicMenu.setState(true);
        else
            foxMenu.setState(true);

        if ("drawChars".equals(draw))
            drawcharsMenu.setState(true);
        else if ("GlyphVector".equals(draw))
            glyphvectorMenu.setState(true);
        else if ("TextLayout".equals(draw))
            textlayoutMenu.setState(true);
        else
            drawstringMenu.setState(true);

        if ("GlyphVector".equals(drawch))
            charglyphvectorMenu.setState(true);
        else if ("TextLayout".equals(drawch))
            chartextlayoutMenu.setState(true);
        else if ("outline".equals(drawch))
            charoutlineMenu.setState(true);
        else
            charpixelMenu.setState(true);

        String fsz = fgp.getProperty("font.size");
        if (fsz != null) {
            for (i=0; i<cSize.getItemCount(); i++) {
                if (fsz.equals((String)cSize.getItemAt(i))) {
                    cSize.setSelectedIndex(i);
                    break;
                }
            }
        }

        if (useCodepage) {
            setPlane(0, false);
            setConverters();
            String cp = (String) cEncode.getSelectedItem();
            int    offset = cp.indexOf(' ');
            grid.setCodepage(offset>0 ? cp.substring(0, offset) : cp);
        } else {
            grid.setCodepage(0);
            setPlane(0, true);
        }

        /*
         * read in the unicode data
         */
        uData = new UnicodeData(fgp.getProperty("unicode.data", "UnicodeData.txt"));
        f.validate();
    }

    private void setHotkeys(JComponent c) {
        c.registerKeyboardAction(this, "Next Page", pgdn, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        c.registerKeyboardAction(this, "Prev Page", pgup, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        c.registerKeyboardAction(this, "Exit",      exit, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    }


    /*
     * Set up the Unicode plane
     * This is only done when in JDK 1.4 and above, or when a backup font exists
     */
    void setPlane(int plane, boolean setunicode) {
        int i;
        int base;
        boolean saveSetup;

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


    /*
     * Add a combo box
     */
    private JComboBox addCombo(JPanel p, String helptxt) {
        JComboBox b = new JComboBox();
        b.setFont(dFont);
        b.addKeyListener(this);
        b.addItemListener(this);
        b.setMaximumRowCount(8);
        if (helptxt.length() > 0)
            b.setToolTipText(helptxt);
        setHotkeys(b);
        if (p != null) {
            p.add(b);
        }
        return b;
    }

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

        if (which == 0) {
            cPanel.remove(cPage);
            cPanel.add(cEncode);
        } else {
            cPanel.remove(cEncode);
            cPanel.add(cPage);
        }
        fInUnicode = which;
        f.validate();
    }

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

        cEncode.removeAllItems();
        String matchCp = fgp.getProperty("codepage", "Cp1252");
        for (int i=0; i<cEncodeMax; i++) {
            name = (String)cvl.nextElement();
            cEncode.addItem(name + " - " + cvl.getDescription());
            if (name.equals(matchCp))
                which = i;
        }
        cEncode.setSelectedIndex(which);
    }


    /*
     * Make a normal menu item
     */
    private JMenuItem additem(JMenu m, String s, int key) {
        JMenuItem item = new JMenuItem(s);
        m.add(item);
        item.setFont(dFont3);
        item.addActionListener(this);
        return item;
    }


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


    /*
     * Set up a Menu bar
     */
    private void createMenuBar() {
        menuBar = new JMenuBar();
        f.setJMenuBar(menuBar);
//      System.out.println("root="+menuBar.getRootPane());
//      setHotkeys(menuBar.getRootPane());

        fileMenu = new JMenu("File");
        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 JMenu("View");
        menuBar.add(viewMenu);
        boolean useCodepage = "false".equals(fgp.getProperty("unicode", "true"));
        unicodeMenu     = addcbitem(viewMenu, "Unicode",     !useCodepage);
        codepageMenu    = addcbitem(viewMenu, "Codepage",    useCodepage);

        if (javaVersion.compareTo("1.4.0") >= 0 || bf.isValid())
            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 JMenu("Options");
        menuBar.add(optionsMenu);
        inSetup = false;
        cOrder          = addcbitem(optionsMenu, "Vertical",    boolProperty("vertical"));
        inSetup = true;
        optionsMenu.addSeparator();
        cAllFnt         = addcbitem(optionsMenu, "Show all fonts",   boolProperty("all.fonts"));
        if (bf.isValid())
            cBkFnt      = addcbitem(optionsMenu, "Bitmap font only", fUseBkFont);
        cColor          = addcbitem(optionsMenu, "Use colors",  fUseColor);
        optionsMenu.addSeparator();
        cAntiA          = addcbitem(optionsMenu, "Antialias",   boolProperty("antialias"));
        cFract          = addcbitem(optionsMenu, "Fractional metrics",  boolProperty("fractional"));
        optionsMenu.addSeparator();
        cVerbose        = addcbitem(optionsMenu, "Verbose",     fVerbose);
        cDetails        = addcbitem(optionsMenu, "Details",     fDetails);
        optionsMenu.addSeparator();
        additem(optionsMenu, "Save options", KeyEvent.VK_F9);

        drawMenu = new JMenu("Draw");
        menuBar.add(drawMenu);
        drawstringMenu  = addcbitem(drawMenu, "drawString",  false);
        drawcharsMenu   = addcbitem(drawMenu, "drawChars",   false);
        glyphvectorMenu = addcbitem(drawMenu, "GlyphVector", false);
        textlayoutMenu  = addcbitem(drawMenu, "TextLayout",  false);
        labelMenu       = addcbitem(drawMenu, "Label",       false);
        drawMenu.addSeparator();
        foxMenu         = addcbitem(drawMenu, "Latin text",    false);
        indicMenu       = addcbitem(drawMenu, "Indic text",    false);
        langMenu        = addcbitem(drawMenu, "Other scripts", false);
        notextMenu      = addcbitem(drawMenu, "No text",       false);

        detailsMenu = new JMenu("Details");
        menuBar.add(detailsMenu);
        additem(detailsMenu, "Show font details", KeyEvent.VK_F6);
        detailsMenu.addSeparator();
        charpixelMenu       = addcbitem(detailsMenu, "Draw pixels", false);
        charoutlineMenu     = addcbitem(detailsMenu, "Draw outline",false);
        charglyphvectorMenu = addcbitem(detailsMenu, "GlyphVector", false);
        chartextlayoutMenu  = addcbitem(detailsMenu, "TextLayout",  false);

        helpMenu = new JMenu("Help");
        menuBar.add(helpMenu);
        additem(helpMenu, "General help",   KeyEvent.VK_F1);
        additem(helpMenu, "About fontgrid", KeyEvent.VK_F2);
    }

    /*
     * Add menu to modify Unicode plane.  This does not work
     * before Java 1.4.0 and is disabled.
     */
    public void addPlaneMenu() {
        viewMenu.addSeparator();
        uniPlaneMenu = new JMenu("Unicode plane");
        Plane = new JCheckBoxMenuItem[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 JFrame();
        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 JFrame();
        if (fSmall) {
            fFontDetail.setSize(300, 330);
            fFontDetail.setLocation(470, 130);
        } else {
            fFontDetail.setSize(400, 400);
            fFontDetail.setLocation(590, 40);
        }
        thisfont = new fontDetails(fFontDetail, this);
    }

    /*
     * Show the help panel
     */
    public void initHelp() {
        try {
            fHelp = new JFrame();
            fHelp.setSize(600, 420);
            String s = "file:"+user_dir+"/fontgrid.htm";
            fHelpDoc = new JEditorPane();
            fHelpDoc.setEditable(false);
            JScrollPane jsp = new JScrollPane(fHelpDoc);
            fHelpDoc.setPage(new URL(s));
            fHelp.getContentPane().add(jsp);
            fHelp.show();
        } catch (Exception e) {
            e.printStackTrace(System.err);
            displayMessage("Help file not available.");
        }
    }


    /*
     * Show the About panel
     */
    public void initAbout()  {
        JFrame fAbout = new JFrame();
        fAbout.setSize(300, 145);
        fAbout.setLocation(f.getX()+50, f.getY()+110);
        JPanel jp = new JPanel();
        fAbout.getContentPane().add(jp);
        Font f = new Font("dialog", 1, 16);
        JLabel l = new JLabel("fontgrid - display character grid");
        l.setFont(f);
        l.setForeground(Color.BLACK);
        jp.add(l);
        f = new Font("dialog", 0, 14);
        l = new JLabel("version 2.0  19 June 2003");
        l.setFont(f);
        l.setForeground(Color.BLACK);
        jp.add(l);
        l = new JLabel("Copyright \u00a9 1998, 2003 IBM Corp.");
        l.setFont(f);
        l.setForeground(Color.BLACK);
        jp.add(l);
        l = new JLabel("Ken Borgendale - kwb@us.ibm.com");
        l.setFont(f);
        l.setForeground(Color.BLACK);
        jp.add(l);
        l = new JLabel("Ken@Borgendale.com");
        l.setFont(f);
        l.setForeground(Color.BLACK);
        jp.add(l);
        fAbout.show();
    }



    /*
     * Set up the font combo box
     */
    private String setupFonts(JComboBox 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) {
            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.removeAllItems();
        which = 0;
        String matchfont = fgp.getProperty("font", "dialog");

        for (i=0; i<nfnts.length; i++) {
            b.addItem(nfnts[i]);
            if (nfnts[i].equals(matchfont))
                which = i;
            if (which==0) {
                if (nfnts[i].equals("dialog"))
                    which = i;
                if (nfnts[i].equals("Dialog"))
                    which = i;
                if (nfnts[i].equals("dialog.plain"))
                    which = i;
                if (nfnts[i].equals("Lucida Sans Regular"))
                    which = i;
            }
        }
        b.setSelectedIndex(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((JFrame)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("Save options")) {
             putProperties(user_home, "fontgrid");
        }

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

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

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

        if(label.equals("Show font details")) {
            if (thisfont == null)
                 initFontDetail();
            thisfont.setFont(sCurFont, fCurStyle, fCurSize);
            thisfont.activate();
        }

        if (label.equals("General help")) {
            initHelp();
        }

        if (label.equals("About fontgrid")) {
            initAbout();
        }
    }


    /*
     * Perform the next page or page down function
     */
    public void moveNext() {
        displayMessage();
        fCurGrid = -1;
        if (fInUnicode==1) {
            if (iStart<0xff00)
                iStart += 256;

            cPage.setSelectedIndex(iStart/256);
            cPage.requestFocus();
        } 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.setSelectedIndex(which+1);
                }
            }
            cEncode.requestFocus();
        }
    }


    /*
     * Perform the Prev page or page up function
     */
    public void movePrev() {
        displayMessage();
        fCurGrid = -1;
        if (fInUnicode==1) {
            if (iStart>0)
                iStart -= 256;
            cPage.setSelectedIndex(iStart/256);
            cPage.requestFocus();
        } 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.setSelectedIndex(which-1);
            }
            cEncode.requestFocus();
        }
    }


    /*
     * Process a change in one of the combo box or check boxes.
     */
    public void itemStateChanged(ItemEvent ie) {
        if (inSetup)
            return;

        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.trim().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);
            grid.repaint();
        }

        /*
         * Change the codepage
         */
        if (obj.equals(cEncode) && codepageMenu.getState()) {
            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;
            grid.repaint();
        }

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

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

        /*
         * Change the drawing order
         */
        if (obj.equals(cOrder)) {
            grid.setOrder(cOrder.getState());
            grid.repaint();
        }

        /*
         * Change the drawing order
         */
        if (obj.equals(cColor)) {
            fUseColor = cColor.getState();
            grid.repaint();
        }

        /*
         * Change the drawing order
         */
        if (obj.equals(cDetails)) {
            fDetails = cDetails.getState();
            setDefaultSize(grid.getLineText()==3);
            grid.repaint();
        }

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


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


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


        if (obj.equals(cBkFnt)) {
            fUseBkFont = cBkFnt.getState();
            if (fUseBkFont) {
                cPanel.remove(cFnt);
                cPanel.remove(cStyle);
                cPanel.remove(cSize);
            } else {
                cPanel.add(cFnt);
                cPanel.add(cStyle);
                cPanel.add(cSize);
            }
            f.validate();
            grid.repaint();
            if (thischar != null)
                thischar.repaint();
        }


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


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


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


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

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


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

            inSetup = false;
            if (!grid.getCodepage().equals("Unicode"))
                fOldEncode = grid.getCodepage();
            setCpSelection(1);
            grid.setCodepage(uniPlane + iStart);
            grid.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;
            grid.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;
            grid.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;
            grid.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;
            grid.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);
            charoutlineMenu.setState(false);
            charpixelMenu.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);
            charoutlineMenu.setState(false);
            charpixelMenu.setState(false);

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

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

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

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

            fDetailDraw = DRAWPIXELS;
            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);
            notextMenu.setState(false);

            if (grid.getLineText() == 3)
                setDefaultSize(false);
            grid.setLineText(0);
            grid.repaint();
        }

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

            if (grid.getLineText() == 3)
                setDefaultSize(false);
            grid.setLineText(1);
            grid.repaint();
        }

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

            if (grid.getLineText() == 3)
                setDefaultSize(false);
            grid.setLineText(2);
            grid.repaint();
        }

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

            if (grid.getLineText() != 3) {
                grid.setLineText(3);
                setDefaultSize(true);
            }
            grid.repaint();
        }

        inSetup = false;
    }


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

        if (isLabel) {
            labelMenu.setState(true);
            if (fLabels == null) {
                grid.setVisible(false);
                fLabels = new Label[259];
                for (i=0; i<259; i++) {
                    fLabels[i] = new Label();
                    fLabels[i].addMouseListener(this);
                    fLabels[i].setBackground(Color.white);
                    grid.add(fLabels[i]);
                }
                try { Thread.sleep(200);}catch(Exception e){};
                grid.setVisible(true);
                fLabelPos = false;
            }
        } else {
            labelMenu.setState(false);
            if (fLabels != null) {
                grid.setVisible(false);
                for (i=0; i<259; i++) {
                    grid.remove(fLabels[i]);
                }
                fLabels = null;
                try {Thread.sleep(200);}catch(Exception e){};
                grid.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;
    }


    /*
     * Private layout manager for CPanel
     */
    public void addLayoutComponent (String name, Component comp) {}
    public void layoutContainer (Container parent) {
        int w = cPanel.getWidth();
        int h = fSmall ? 23 : 24;
        if (fUseBkFont) {
            if (fInUnicode != 0) {
                cPage.setLocation(0, 0);
                cPage.setSize(w, h);
                cPage.validate();
            } else {
                cEncode.setLocation(0, 0);
                cEncode.setSize(w, h);
                cEncode.validate();
            }
        } else {
            int p65 = w*65/100;
            int p20 = w*20/100;
            int p15 = w*15/100;
            cFnt.setLocation(0, 0);
            cFnt.setSize(p65, h);
            cFnt.validate();
            cStyle.setLocation(p65, 0);
            cStyle.setSize(p20, h);
            cStyle.validate();
            cSize.setLocation(p65+p20, 0);
            cSize.setSize(p15, h);
            cSize.validate();
            if (fInUnicode != 0) {
                cPage.setLocation(0, h);
                cPage.setSize(w, h);
                cPage.validate();
            } else {
                cEncode.setLocation(0, h);
                cEncode.setSize(w, h);
                cEncode.validate();
            }
        }
    }

    public Dimension minimumLayoutSize (Container parent) {
        return minimumLayoutSize(parent);
    }

    public Dimension preferredLayoutSize (Container parent) {
        int w = cPanel.getWidth();
        int h = fSmall ? 23 : 24;
        return new Dimension(w, (fUseBkFont ? h : 2*h));
    }

    public void removeLayoutComponent (Component comp) {}




    /*
     * 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;
            grid.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 < 0 && inDBCS) {
                iStart >>= 8;
                if (iStart == 1)
                    iStart = 0;
                grid.setCodepage(grid.getCodepage(), iStart);
                inDBCS = iStart != 0;
                displayMessage();
                fCurGrid = -1;
                grid.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;
//      System.out.println("focus="+FocusManager.getCurrentManager().getFocusedWindow());
        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(pos);
                } else {
                    s = "Code=" + pos + " (" + hex2(pos) + ")";
                }
                if (ch != 0xfffe && ch != 0xffff) {
                    s = s + "   Char=" + hex4(ch);
                    int glyph = grid.getGlyph(ch);
                    if (glyph != 0 && !fUseBkFont) {
                        s = s + "   Glyph=" + (glyph>>24) + "," + hex4(glyph);
                    }
                    String s2 = uData.getUnicodeInfo(ch, 1);
                    if (s2.equals("<control>")) {
                        String desc2 = uData.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;
        }
    }



    public String getRangeName(int uni) {
        switch (uni>>8) {
        case 0x00: return "Latin 1";
        case 0x01: return "Latin Extended";
        case 0x02: return "IPA Extensions, Spacing Modifier Letters";
        case 0x03: return "Combining Diacritical Marks, Greek";
        case 0x04: return "Cyrillic";
        case 0x05: return "Cyrillic Supplementary, Hebrew";
        case 0x06: return "Arabic";
        case 0x07: return "Syriac, Thaana";
        case 0x09: return "Devanagari, Bengali";
        case 0x0a: return "Gurmukhi, Gujarati";
        case 0x0b: return "Oriya, Tamil";
        case 0x0c: return "Telugu, Kannada";
        case 0x0d: return "Malayalam, Sinhala";
        case 0x0e: return "Thai, Lao";
        case 0x0f: return "Tibetan";
        case 0x10: return "Myanmar, Georgian";
        case 0x11: return "Hangul Jamo";
        case 0x12: return "Ethiopic";
        case 0x13: return "Cherokee";
        case 0x14: return "United Canadian aboriginal";
        case 0x16: return "Ogham, Runic";
        case 0x17: return "Tagalog, Hanunoo, Buhin, Tagbanwa, Khmer";
        case 0x18: return "Mongolian";
        case 0x19: return "Limbu, Tai Le, Khmer SYmbols";
        case 0x1d: return "Phonectic extensions";
        case 0x1e: return "Latin extended additional";
        case 0x1f: return "Greek extended";
        case 0x20: return "Punctuation, Superscripts, Currency";
        case 0x21: return "Letterlike symbols, Number forms, Arrows";
        case 0x22: return "Mathematical operators";
        case 0x23: return "Miscellaneous technical";
        case 0x24: return "Enclosed alphanumerics";
        case 0x25: return "Box Drawing, Block elements, Geometric";
        case 0x26: return "Miscellaneous symbols";
        case 0x27: return "Dingbats";
        case 0x28: return "Braille patterns";
        case 0x29: return "Supplemental arrows-A / Math-B";
        case 0x2A: return "Supplemental math operators";
        case 0x2B: return "Miscellaneous symbols and arrows";
        case 0x2E: return "CJK radicals";
        case 0x2F: return "Kangxi radicals";
        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 unified ideographs";
        case 0xA0: return "Yi syllables";
        case 0xA4: return "Yi radicals";
        case 0xAC: return "Hangul syllables";
        case 0xE0: return "Private use";
        case 0xF8: return "Corporate Use";
        case 0xF9: return "CJK compatibility";
        case 0xFB: return "Arabic presentation";
        case 0xFD: return "Arabic ligatures";
        case 0xFE: return "Shaped Arabic 1";
        case 0xFF: return "Compatibility";
        case 0x100: return "Linear B";
        case 0x101: return "Aegean";
        case 0x103: return "Old Italic / Gothic";
        case 0x104: return "Deseret / Osmanya";
        case 0x108: return "Cypriot";
        case 0x1D0: return "Byzantine musical";
        case 0x1D1: return "Musical";
        case 0x1D3: return "Tai Xuan Jing";
        case 0x1D4: return "Math";
        case 0x200: return "CJK extension B";
        case 0x2f8: return "CJK compatibility B";
        case 0xe00: return "Tags";
        case 0xe01: return "Variation selectors";
        }
        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[]) {
//      try {
//          javax.swing.plaf.metal.MetalLookAndFeel.setCurrentTheme(new CJK16());
//          UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
//      } catch (Exception e) {
//          e.printStackTrace(System.err);
//      }


        WindowListener l = new WindowAdapter() {
            public void windowClosing(WindowEvent e) {System.exit(0);}
        };

        fontgrid fontg = new fontgrid(args);

        JFrame f = new JFrame("fontgrid " + fontg.fgp.getProperty("title", ""));

        f.addWindowListener(l);

        fontg.init(f);

        f.getContentPane().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 JComponent implements Printable, ImageObserver {
        String  line1;
        String  line2;
        String  line3;
        int     fVector[];
        int     fStart       = 0;
        String  fEncode      = "Unicode";
        int     fInUnicode = 1;
        String  sCurFont;
        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   cBackground  = Color.white;
        Color   cUnknown     = Color.red;
        Color   cLast        = new Color(0,   0, 127);     /* Dark blue  */
        Color   cUnkBack     = new Color(220, 242, 255);   /* Light cyan */
        Color   cUndefined   = new Color(127, 127, 127);   /* Dark gray */
        Color   cUndefBack   = new Color(232, 232, 232);   /* Light gray  */
        Color   cControl     = new Color(255, 255, 220);
        int     CpType;
        boolean ord          = false;
        int     Xgrid = 0;
        int     Ygrid = 0;
        int     gridsize = fSmall ? 22 : 30;
        int     lineText = 0;

        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'};
        private final  int colors [] = {cUnkBack.getRGB(), 0x00a0a0a0, 0x00505050, 0x00000000};

        /*
         * Create the object
         */
        public gridCanvas() {
            setDoubleBuffered(true);
            fVector = new int[256];
            CpType = 1;           /* Unicode */
            setLineText(3);       /* none */
        }

        /*
         * required to draw image
         */
        public boolean imageUpdate(Image img, int f, int x, int y, int w, int h) {
            return false;
        }

        public int getLineText() {
            return lineText;
        }

        /*
         * Set up the line text
         */
        public void setLineText(int which) {
            lineText = 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;
            case 3:
                line1 = "";
                line2 = "";
                line3 = "";
                break;
            }
        }


        /*
         * Display the grid
         */
        public void paintComponent(Graphics g) {
            if (!grid.isVisible())
                return;
            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 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   = 20;
                yPos    = (fDetails && !fUseBkFont) ? 90 : 45;
                xPos    = 35;
                yMore   = yPos+gridfull+10;
                Xgrid   = xPos - gridoffset;
                Ygrid   = yPos + griddescend-gridsize;
            }
            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
             */
            if (!fUseBkFont && fDetails) {
                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+(fSmall?280:320), yText+15);
                drawText(g, fs, xGrid+415, 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(fStart), xGrid+72, yText+30);
                }
                if (!fUseBkFont) {
                    drawText(g, "Text mode:", xGrid+(fSmall?290:340), yText+30);
                    drawText(g, getDrawModeName(), xGrid+415, yText+30);
                }
            }

            g.setFont(fCurFont);


            /*
             * Output the characters
             */
            GlyphVector gv;
            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 (CpType==0 && (iCount == 0xfffe || iCount == 0xffff)) {
                        if (iCount == 0xfffe) {
                            g.setFont(fMonoFont);
                            g.setColor(cUnknown);
                            if (ord) {
                                g.drawString(hex2(Row*16+Col), xPos+(Row*gridsize), yPos + (Col*gridsize));
                            } else {
                                g.drawString(hex2(Row*16+Col), xPos+(Col*gridsize), yPos + (Row*gridsize));
                            }
                            g.setFont(fCurFont);
                        }
                        continue;
                    }

                    /*
                     * Set the color based on displayability and what font the glyph is in
                     */
                    String udat = uData.getUnicodeInfo(iCount);
                    String s = UnicodeData.getUnicodeInfo(udat, 2);
                    gv = fCurFont.createGlyphVector(frc, s1);
                    int glyph = gv.getGlyphCode(0);
                    int fontID = glyph>>24;
                    Color bgColor = cBackground;
                    if (s.length()<1) {
                        bgColor = cUndefBack;
                    } else if (s.charAt(0)=='C') {
                        bgColor = cControl;
                    } else if (glyph==0 && fUseColor && !fUseBkFont) {
                        bgColor = cUnkBack;
                    }
                    if (bgColor != cBackground) {
                        g.setColor(bgColor);
                        g.fillRect(xGrid+(ord?Row:Col)*gridsize+1,
                                   yGrid+(ord?Col:Row)*gridsize+1,
                                   gridsize-1, gridsize-1);
                    }
                    g.setColor (cNormal);
                    if (!isPrint && fUseColor && fontID > 0)
                        g.setColor (cLast);

                    /*
                     * Use backup bitmap font
                     */
                    if (!isPrint && (glyph==0 || glyph==0xffff || fUseBkFont)) {
                        if (bf.setCodepoint(iCount)) {
                            colors[0] = bgColor.getRGB();
                            bf.setColors(colors);
                            Image bfi = (Image)bf.getImage();
                            g.drawImage(bfi,
                                    xGrid + ((ord?Row:Col)*gridsize) + bf.getXpos(),
                                    yGrid + ((ord?Col:Row)*gridsize) + bf.getYpos(),
                                    (ImageObserver)this);
                            continue;
                        }
                        if (s.length()==0 || fUseBkFont)
                            continue;
                        g.setColor (cUnknown);
                    }

                    /*
                     * Handle non-spacing marks outside of the complex text range
                     */
                    int xd = 0;
                    if ((iCount<0x590 || iCount>0x10A0) &&
                        iLen == 1 &&
                        "Mn".equals(UnicodeData.getUnicodeInfo(udat, 2)) ) {
                        GlyphMetrics gm = gv.getGlyphMetrics(0);
                        if (bf.setCodepoint(0x25cc)) {
                            xd = bf.getXpos() - gridoffset;
                            if (gm.getLSB() < -2.0 ) {
                                xd += bf.getWidth();
                            }
                            colors[0] = bgColor.getRGB();
                            bf.setColors(colors);
                            Image bfi = (Image)bf.getImage();
                            g.drawImage(bfi,
                                    xGrid + ((ord?Row:Col)*gridsize) + bf.getXpos(),
                                    yGrid + ((ord?Col:Row)*gridsize) + bf.getYpos(),
                                    (ImageObserver)this);
                        }
                    }


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

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

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

                    case DRAWTEXTLAYOUT:
                        TextLayout tl = new TextLayout(new String(s1, 0, iLen), fCurFont,
                                            g2.getFontRenderContext());
                        tl.draw(g2, xPos + xd + ((ord?Row:Col)*gridsize) ,
                                yPos + ((ord?Col: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;
                    }
                }
            }


            /*
             * Output the grid
             */
            g.setColor (cNormal);
            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[ord?1:0] = hexarray[Row];
               s1[ord?0: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[ord?1:0] = '-';
                   s1[ord?0: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);
               }
            }

            if (!fUseBkFont) {
                /*
                 * Output some sample text
                 */
                if (line1.length()>0) {
                    g.setColor (cNormal);
                    g.setFont(fCurFont);
                    linesize = fCurSize + fCurSize/4;
                    if (draw == DRAWLABEL) {
                        drawLabel(g, fLabels[256], line1, xGrid-20, yMore);
                        drawLabel(g, fLabels[257], line2, xGrid-20, yMore+linesize);
                        drawLabel(g, fLabels[258], line3, xGrid-20, yMore+linesize*2);
                    } else {
                        drawText(g, line1, xGrid-20, yMore);
                        drawText(g, line2, xGrid-20, yMore+linesize);
                        drawText(g, line3, xGrid-20, 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;
        }

        public void setOrder(boolean order) {
            ord = order;
        }


        /*
         * 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.
         */
        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) {
            if (y < Ygrid && x > Xgrid)
                return -2;
            if (x < Xgrid || y < Ygrid ||
                x >= (Xgrid + gridsize*16) || y >= (Ygrid + gridsize*16))
                return -1;

            return ord ?
                   ((x-Xgrid)/gridsize)*16 + (((y-Ygrid)/gridsize)) :
                   ((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);
            if (thischar != null) {
                thischar.setFont(sCurFont, fCurStyle);
                thischar.repaint();
            }
        }
    }


    /*
     * The charDetails class is a Swing component which displays details
     * of a single charactor
     */
    public class charDetails extends JComponent implements ActionListener {
        private int     fCh;
        private Font    fFont;
        private int     fSize;
        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  uType;
        private String  fDescription;
        private String  fDirection;
        private byte [] fCodepoint;
        private String  fsCodepoint;
        private String  fEncoding;
        private Font    fSansSerif;
        private int     BigFontSize = 200;
        private JFrame  thisFrame;
        private JButton bDetach;
        private JButton bClose;
        private int     ypos;
        private int     xname;
        private int     xequal;
        private int     xdata;
        private int     linesz;
        private boolean isDetached = false;
        private boolean antiAlias;
        private int     drawMode;
        private BufferedImage img;
        private Graphics2D    g2img;
        private final int imgSize = 64;
        private final int imgOffset = 16;


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

            thisFrame = f;
            JPanel p = new JPanel();

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

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

            /* Add the char display and button panels to the frame */
            Container cp = thisFrame.getContentPane();
            cp.setLayout(new BorderLayout());
            cp.add(p, BorderLayout.SOUTH);
            cp.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;

            antiAlias = cAntiA.getState();
            drawMode = fDetailDraw;
        }


        /*
         * 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);
            fSize     = fCurSize;
            fUnicode  = uData.getUnicodeInfo(ch);
            fDescription = uData.getUnicodeInfo(fUnicode, 1);/* Current name*/
            uType     = UniTypeName(uData.getUnicodeInfo(fUnicode, 2));
            if (!fDescription.equals("")) {
                if (fDescription.equals("<control>")) {
                    String desc2 = uData.getUnicodeInfo(fUnicode, 10);/* Old name*/
                    if (!desc2.equals(""))
                        fDescription = desc2;
                }
                if (fDescription.charAt(0) != '<')
                    fDescription = fDescription.toLowerCase();
            }
            fDirection = uData.getUnicodeInfo(fUnicode, 4);
            if (fDirection.equals("ON") && uData.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;
                 };
                 isDetached = true;
                 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;
        }

        private Color [] icolors = {
            new Color(0x00606060),
            new Color(0x00787878),
            new Color(0x00949494),
            new Color(0x00b0b0b0),
            new Color(0x00c0c0c0),
            new Color(0x00d0d0d0),
            new Color(0x00e0e0e0),
            new Color(0x00f0f0f0),
            new Color(0x00ffffff)
        };

        private Color [] bcolors = {
            new Color(0x00ffffff),
            new Color(0x00c0c0c0),
            new Color(0x00606060),
            new Color(0x00404040)
        };


        private void showBackPels(Graphics2D g, int cp,
                                  int xpos, int ypos, int pelsize) {
            int pelhalf = pelsize / 2;
            int j, k;
            if (!bf.setCodepoint(cp))
                return;

            int w = bf.getWidth();
            int h = bf.getHeight();
            byte [] data = bf.getData();
            int offset = 0;
            int shift  = 0;
            int dat    = 0;

            for (j=0; j<h; j++ ) {
                for (k=0; k<w; k++) {
                    if (shift == 0) {
                        dat = data[offset++];
                        shift = 8;
                    }
                    shift -= 2;
                    int pel = (dat>>shift)&0x03;
                    if (pel != 0) {
                        g.setColor(bcolors[pel]);
                        g.fillRoundRect(xpos+pelsize*(k+bf.getXpos()),
                                        ypos+pelsize*(j+bf.getYpos()),
                                        pelsize-1, pelsize-1,
                                        pelhalf, pelhalf);
                    }
                }
            }
        }


        private void showPels(Graphics2D g, int xpos, int ypos, int pelsize) {
            int pelhalf = pelsize / 2;
            int xstart = xpos - (imgOffset * pelsize);
            int ystart = ypos - ((imgSize-imgOffset) * pelsize);
            int i, j;
            Color saveColor = g.getColor();
            for (i=0; i<imgSize; i++) {
                for (j=0; j<imgSize; j++) {
                    int pel = img.getRGB(j, i);
                    pel = (pel >> 5) & 0x07;
                    if (pel < 7) {
                        g.setColor(icolors[pel]);
                        g.fillRoundRect(xstart+pelsize*j, ystart+pelsize*i,
                                         pelsize-1, pelsize-1, pelhalf, pelhalf);
                    }
                }
            }
            g.setColor(saveColor);
        }


        /*
         * 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);

            if (!isDetached) {
                antiAlias = cAntiA.getState();
                drawMode = fDetailDraw;
                fSize    = fCurSize;
            }

            /* 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);
            FontMetrics fm = g2.getFontMetrics();
            GlyphVector gv  = fFont.createGlyphVector(g2.getFontRenderContext(), s1);
            int glyph = gv.getGlyphCode(0);
            GlyphMetrics gm = null;
            if (drawMode == DRAWPIXELS && (fUseBkFont || glyph == 0 || glyph == 0xffff)) {
                showBackPels(g2, fCh, 20, -10, 8);
                g2.setColor(Color.black);
                ypos   = BigFontSize*3/2 + 30;
            } else {
                gm = gv.getGlyphMetrics(0);

                if ((fCh<0x590 || fCh>0x10A0) &&
                    fFont.canDisplay((char)fCh) &&
                    gm.getLSB() < ((float)BigFontSize/-10) &&
                    "Mn".equals(uData.getUnicodeInfo(fCh, 2)) ) {
                    s1 = new char[2];
                    s1[0] = 0x25CC;
                    s1[1] = (char)fCh;
                    gv  = fFont.createGlyphVector(g2.getFontRenderContext(), s1);
                }

                     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);

                switch (drawMode) {

                case DRAWPIXELS:
                    if (img == null) {
                        img = new BufferedImage(imgSize, imgSize, BufferedImage.TYPE_INT_RGB);
                        g2img = img.createGraphics();

                    }
                    Font pFont = fFont.deriveFont((float)fSize);
                    g2img.setColor(Color.white);
                    g2img.fillRect(0, 0, imgSize, imgSize);
                    g2img.setFont(pFont);
                    g2img.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                  antiAlias ? RenderingHints.VALUE_ANTIALIAS_ON :
                                              RenderingHints.VALUE_ANTIALIAS_OFF);
                    GlyphVector gvimg = pFont.createGlyphVector(g2img.getFontRenderContext(), s1);
                    g2img.setColor(Color.black);
                    g2img.drawGlyphVector(gvimg, imgOffset, imgSize-imgOffset);
                    showPels(g2, xpos, ypos, BigFontSize/fSize);
                    /* fall thru */

                case DRAWSHAPE:
                    Font myFont = fFont.deriveFont((float)fSize);
                    g2.setFont(myFont);
                    GlyphVector gvx = myFont.createGlyphVector(g2.getFontRenderContext(), s1);
                    Shape shp = gvx.getOutline();

                    float [] txvec = {BigFontSize/fSize, 0, 0, BigFontSize/fSize, xpos, ypos};
                    AffineTransform tx = new AffineTransform(txvec);
                    shp = tx.createTransformedShape(shp);
                    g2.draw(shp);
                    break;

                case DRAWTEXTLAYOUT:
                    TextLayout tl = new TextLayout(new String(s1, 0, s1.length), fFont, g2.getFontRenderContext());
                    tl.draw(g2, xpos, ypos);
                    break;

                default:
                    g2.drawGlyphVector( gv, xpos, ypos);
                }
                ypos   = BigFontSize*3/2;
            }


            /*
             * Print out glyph data
             */
            g.setFont(fSansSerif);
            if (drawMode == DRAWPIXELS &&
                (!fUseBkFont || glyph > 0 || glyph != 0xffff)) {
                putLine(g, "Font", "Unicode glyph");
            } else {
                putLine(g, "Font",  fsFont);
                putLine(g, "Style", fsStyle);
                if (drawMode == DRAWPIXELS)
                    putLine(g, "Size", ""+fSize);
            }
            if (!fEncoding.equals("Unicode")) {
                putLine(g, "Codepoint",  fEncoding + " - " + fsCodepoint);
            }
            putLine(g, "Unicode",  hex4(fCh));
            putLine(g, "Description", fDescription);
            if (fsType.equals(uType)) {
                putLine(g, "Type",     fsType);
            } else {
                putLine(g, "Java Type", fsType);
                putLine(g, "UCD Type",  uType);
            }
            if (!fUseBkFont && glyph > 0 && glyph != 0xffff) {
                /* 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());
                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", uData.getUnicodeInfo(fUnicode, 5));
            if (!fDirection.equals(""))
                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";
        }

        private String UniTypeName(String d) {
            if (d.equals("Lu")) return "Uppercase Letter";
            if (d.equals("Ll")) return "Lowercase Letter";
            if (d.equals("Lt")) return "Titlecase Letter";
            if (d.equals("Lm")) return "Modifier Letter";
            if (d.equals("Lo")) return "Other Letter";
            if (d.equals("Mn")) return "Non Spacing Mark";
            if (d.equals("Mc")) return "Combining Spacing Mark";
            if (d.equals("Me")) return "Enclosing Mark";
            if (d.equals("Nd")) return "Decimal Digit";
            if (d.equals("Nl")) return "Letter Number";
            if (d.equals("No")) return "Other Number";
            if (d.equals("Pc")) return "Connector Punctuation";
            if (d.equals("Pd")) return "Dash Punctuation";
            if (d.equals("Ps")) return "Start Punctuation";
            if (d.equals("Pe")) return "End Punctuation";
            if (d.equals("Pi")) return "Initial Punctuation";
            if (d.equals("Pf")) return "Final Punctuation";
            if (d.equals("Po")) return "Other Punctuation";
            if (d.equals("Sm")) return "Math Symbol";
            if (d.equals("Sc")) return "Currency Symbol";
            if (d.equals("Sk")) return "Modifier Symbol";
            if (d.equals("So")) return "Other Symbol";
            if (d.equals("Zs")) return "Space Separator";
            if (d.equals("Zl")) return "Line Separator";
            if (d.equals("Zp")) return "Paragraph Separator";
            if (d.equals("Cc")) return "Control";
            if (d.equals("Cf")) return "Format";
            if (d.equals("Cs")) return "Surrogate";
            if (d.equals("Co")) return "Private Use";
            return "Unassigned";
        }


        /*
         * 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 JComponent implements ActionListener {

        private int     ypos;
        private int     xname;
        private int     xequal;
        private int     xdata;
        private int     linesz;
        private Font    fSansSerif;
        private JFrame  thisFrame;
        private JButton bClose;
        private JButton 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(JFrame f, KeyListener keylist) {

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

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

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

            /* Add the char display and button panels to the frame */
            Container cp = thisFrame.getContentPane();
            cp.setLayout(new BorderLayout());
            cp.add(p, BorderLayout.SOUTH);
            cp.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 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());
        }
    }

    private static class CJK16 extends DefaultMetalTheme {

        public String getName() { return "CJK 16 point"; }

        private final FontUIResource controlFont = new FontUIResource("Dialog", Font.PLAIN, 16);
        private final FontUIResource systemFont = new FontUIResource("Dialog", Font.PLAIN, 16);
        private final FontUIResource windowTitleFont = new FontUIResource("Dialog", Font.BOLD, 16);
        private final FontUIResource userFont = new FontUIResource("SansSerif", Font.PLAIN, 16);
        private final FontUIResource smallFont = new FontUIResource("Dialog", Font.PLAIN, 12);

        public FontUIResource getControlTextFont() { return controlFont;}
        public FontUIResource getSystemTextFont() { return systemFont;}
        public FontUIResource getUserTextFont() { return userFont;}
        public FontUIResource getMenuTextFont() { return controlFont;}
        public FontUIResource getWindowTitleFont() { return windowTitleFont;}
        public FontUIResource getSubTextFont() { return smallFont;}
    }
}
