/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
  Part of the Processing project - http://processing.org

  Copyright (c) 2004-10 Ben Fry and Casey Reas
  Copyright (c) 2001-04 Massachusetts Institute of Technology

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package processing.app.tools;

import processing.app.*;
import processing.core.*;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.io.*;
import java.util.*;

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;


/**
 * GUI tool for font creation heaven/hell.
 */
public class CreateFont extends JFrame implements Tool {
  Editor editor;
  //Sketch sketch;

//  Dimension windowSize;

  JList fontSelector;
  JTextField sizeSelector;
  JButton charsetButton;
  JCheckBox smoothBox;
  JComponent sample;
  JButton okButton;
  JTextField filenameField;

  HashMap<String,Font> table;
  boolean smooth = true;

  Font font;

  String[] list;
  int selection = -1;

  CharacterSelector charSelector;


  public CreateFont() {
    super("Create Font");
  }


  public String getMenuTitle() {
    return "Create Font...";
  }


  public void init(Editor editor) {
    this.editor = editor;

    Container paine = getContentPane();
    paine.setLayout(new BorderLayout()); //10, 10));

    JPanel pain = new JPanel();
    pain.setBorder(new EmptyBorder(13, 13, 13, 13));
    paine.add(pain, BorderLayout.CENTER);

    pain.setLayout(new BoxLayout(pain, BoxLayout.Y_AXIS));

    String labelText =
      "Use this tool to create bitmap fonts for your program.\n" +
      "Select a font and size, and click 'OK' to generate the font.\n" +
      "It will be added to the data folder of the current sketch.";

    JTextArea textarea = new JTextArea(labelText);
    textarea.setBorder(new EmptyBorder(10, 10, 20, 10));
    textarea.setBackground(null);
    textarea.setEditable(false);
    textarea.setHighlighter(null);
    textarea.setFont(new Font("Dialog", Font.PLAIN, 12));
    pain.add(textarea);

    // don't care about families starting with . or #
    // also ignore dialog, dialoginput, monospaced, serif, sansserif

    // getFontList is deprecated in 1.4, so this has to be used
    GraphicsEnvironment ge =
      GraphicsEnvironment.getLocalGraphicsEnvironment();
    Font fonts[] = ge.getAllFonts();
    
    if (false) {
      ArrayList<Font> fontList = new ArrayList<Font>();
      File folderList = new File("/Users/fry/coconut/sys/fonts/web");
      for (File folder : folderList.listFiles()) {
        if (folder.isDirectory()) {
          File[] items = folder.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
              if (name.charAt(0) == '.') return false;
              return (name.toLowerCase().endsWith(".ttf") ||
                      name.toLowerCase().endsWith(".otf"));
            }
          });
          for (File fontFile : items) {
            try {
              FileInputStream fis = new FileInputStream(fontFile);
              BufferedInputStream input = new BufferedInputStream(fis);
              Font font = Font.createFont(Font.TRUETYPE_FONT, input);
              input.close();
              fontList.add(font);
              
            } catch (Exception e) {
              System.out.println("Ignoring " + fontFile.getName());
            }
          }
        }
      }
//      fonts = new Font[fontList.size()];
//      fontList.toArray(fonts);
      fonts = fontList.toArray(new Font[fontList.size()]);
    }

    String flist[] = new String[fonts.length];
    table = new HashMap<String,Font>();

    int index = 0;
    for (int i = 0; i < fonts.length; i++) {
      //String psname = fonts[i].getPSName();
      //if (psname == null) System.err.println("ps name is null");

      try {
        flist[index++] = fonts[i].getPSName();
        table.put(fonts[i].getPSName(), fonts[i]);
        // Checking into http://processing.org/bugs/bugzilla/407.html
        // and https://github.com/processing/processing/issues/1727
//        if (fonts[i].getPSName().contains("Helv")) {
//          System.out.println(fonts[i].getPSName() + " -> " + fonts[i]);
//        }
      } catch (Exception e) {
        // Sometimes fonts cause lots of trouble.
        // http://code.google.com/p/processing/issues/detail?id=442
        e.printStackTrace();
      }
    }

    list = new String[index];
    System.arraycopy(flist, 0, list, 0, index);

    fontSelector = new JList(list);
    fontSelector.addListSelectionListener(new ListSelectionListener() {
        public void valueChanged(ListSelectionEvent e) {
          if (e.getValueIsAdjusting() == false) {
            selection = fontSelector.getSelectedIndex();
            okButton.setEnabled(true);
            update();
          }
        }
      });

    fontSelector.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    fontSelector.setVisibleRowCount(12);
    JScrollPane fontScroller = new JScrollPane(fontSelector);
    pain.add(fontScroller);

    Dimension d1 = new Dimension(13, 13);
    pain.add(new Box.Filler(d1, d1, d1));

    sample = new SampleComponent(this);

    // Seems that in some instances, no default font is set
    // http://dev.processing.org/bugs/show_bug.cgi?id=777
    sample.setFont(new Font("Dialog", Font.PLAIN, 12));

    pain.add(sample);

    Dimension d2 = new Dimension(6, 6);
    pain.add(new Box.Filler(d2, d2, d2));

    JPanel panel = new JPanel();
    panel.add(new JLabel("Size:"));
    sizeSelector = new JTextField(" 48 ");
    sizeSelector.getDocument().addDocumentListener(new DocumentListener() {
        public void insertUpdate(DocumentEvent e) { update(); }
        public void removeUpdate(DocumentEvent e) { update(); }
        public void changedUpdate(DocumentEvent e) { }
      });
    panel.add(sizeSelector);

    smoothBox = new JCheckBox("Smooth");
    smoothBox.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          smooth = smoothBox.isSelected();
          update();
        }
      });
    smoothBox.setSelected(smooth);
    panel.add(smoothBox);

//    allBox = new JCheckBox("All Characters");
//    allBox.addActionListener(new ActionListener() {
//        public void actionPerformed(ActionEvent e) {
//          all = allBox.isSelected();
//        }
//      });
//    allBox.setSelected(all);
//    panel.add(allBox);
    charsetButton = new JButton("Characters...");
    charsetButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        //showCharacterList();
        charSelector.setVisible(true);
      }
    });
    panel.add(charsetButton);

    pain.add(panel);

    JPanel filestuff = new JPanel();
    filestuff.add(new JLabel("Filename:"));
    filestuff.add(filenameField = new JTextField(20));
    filestuff.add(new JLabel(".vlw"));
    pain.add(filestuff);

    JPanel buttons = new JPanel();
    JButton cancelButton = new JButton("Cancel");
    cancelButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          setVisible(false);
        }
      });
    okButton = new JButton("OK");
    okButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          build();
        }
      });
    okButton.setEnabled(false);

    buttons.add(cancelButton);
    buttons.add(okButton);
    pain.add(buttons);

    JRootPane root = getRootPane();
    root.setDefaultButton(okButton);
    ActionListener disposer = new ActionListener() {
        public void actionPerformed(ActionEvent actionEvent) {
          setVisible(false);
        }
      };
    Toolkit.registerWindowCloseKeys(root, disposer);
    Toolkit.setIcon(this);

    pack();
    setResizable(false);
    //System.out.println(getPreferredSize());

    // do this after pack so it doesn't affect layout
    sample.setFont(new Font(list[0], Font.PLAIN, 48));

    fontSelector.setSelectedIndex(0);

//    Dimension screen = Toolkit.getScreenSize();
//    windowSize = getSize();
//    setLocation((screen.width - windowSize.width) / 2,
//                (screen.height - windowSize.height) / 2);
    setLocationRelativeTo(null);

    // create this behind the scenes
    charSelector = new CharacterSelector();
  }


  public void run() {
    setVisible(true);
  }


  public void update() {
    int fontsize = 0;
    try {
      fontsize = Integer.parseInt(sizeSelector.getText().trim());
      //System.out.println("'" + sizeSelector.getText() + "'");
    } catch (NumberFormatException e2) { }

    // if a deselect occurred, selection will be -1
    if ((fontsize > 0) && (fontsize < 256) && (selection != -1)) {
      //font = new Font(list[selection], Font.PLAIN, fontsize);
      Font instance = table.get(list[selection]);
//      font = instance.deriveFont(Font.PLAIN, fontsize);
      font = instance.deriveFont((float) fontsize);
      //System.out.println("setting font to " + font);
      sample.setFont(font);

      String filenameSuggestion = list[selection].replace(' ', '_');
      filenameSuggestion += "-" + fontsize;
      filenameField.setText(filenameSuggestion);
    }
  }


  public void build() {
    int fontsize = 0;
    try {
      fontsize = Integer.parseInt(sizeSelector.getText().trim());
    } catch (NumberFormatException e) { }

    if (fontsize <= 0) {
      JOptionPane.showMessageDialog(this, "Bad font size, try again.",
                                    "Badness", JOptionPane.WARNING_MESSAGE);
      return;
    }

    String filename = filenameField.getText().trim();
    if (filename.length() == 0) {
      JOptionPane.showMessageDialog(this, "Enter a file name for the font.",
                                    "Lameness", JOptionPane.WARNING_MESSAGE);
      return;
    }
    if (!filename.endsWith(".vlw")) {
      filename += ".vlw";
    }

    // Please implement me properly. The schematic is below, but not debugged.
    // http://dev.processing.org/bugs/show_bug.cgi?id=1464

//    final String filename2 = filename;
//    final int fontsize2 = fontsize;
//    SwingUtilities.invokeLater(new Runnable() {
//      public void run() {
    try {
      Font instance = table.get(list[selection]);
      font = instance.deriveFont(Font.PLAIN, fontsize);
      //PFont f = new PFont(font, smooth, all ? null : PFont.CHARSET);
      PFont f = new PFont(font, smooth, charSelector.getCharacters());

//      PFont f = new PFont(font, smooth, null);
//      char[] charset = charSelector.getCharacters();
//      ProgressMonitor progressMonitor = new ProgressMonitor(CreateFont.this,
//                                            "Creating font", "", 0, charset.length);
//      progressMonitor.setProgress(0);
//      for (int i = 0; i < charset.length; i++) {
//        System.out.println(charset[i]);
//        f.index(charset[i]);  // load this char
//        progressMonitor.setProgress(i+1);
//      }

      // make sure the 'data' folder exists
      File folder = editor.getSketch().prepareDataFolder();
      f.save(new FileOutputStream(new File(folder, filename)));

    } catch (IOException e) {
      JOptionPane.showMessageDialog(CreateFont.this,
                                    "An error occurred while creating font.",
                                    "No font for you",
                                    JOptionPane.WARNING_MESSAGE);
      e.printStackTrace();
    }
//      }
//    });

    setVisible(false);
  }


//  /**
//   * make the window vertically resizable
//   */
//  public Dimension getMaximumSize() {
//    return new Dimension(windowSize.width, 2000);
//  }
//
//
//  public Dimension getMinimumSize() {
//    return windowSize;
//  }


  /*
  public void show(File targetFolder) {
    this.targetFolder = targetFolder;
    show();
  }
  */
}


/**
 * Component that draws the sample text. This is its own subclassed component
 * because Mac OS X controls seem to reset the RenderingHints for smoothing
 * so that they cannot be overridden properly for JLabel or JTextArea.
 * @author fry
 */
class SampleComponent extends JComponent {
  // see http://rinkworks.com/words/pangrams.shtml
  String text =
    "Forsaking monastic tradition, twelve jovial friars gave up their " +
    "vocation for a questionable existence on the flying trapeze.";
  int high = 80;

  CreateFont parent;

  public SampleComponent(CreateFont p) {
    this.parent = p;

    // and yet, we still need an inner class to handle the basics.
    // or no, maybe i'll refactor this as a separate class!
    // maybe a few getters and setters? mmm?
    addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        String input =
          (String) JOptionPane.showInputDialog(parent,
                                               "Enter new sample text:",
                                               "Sample Text",
                                               JOptionPane.PLAIN_MESSAGE,
                                               null, // icon
                                               null, // choices
                                               text);
        if (input != null) {
          text = input;
          parent.repaint();
        }
      }
    });
  }

  public void paintComponent(Graphics g) {
//    System.out.println("smoothing set to " + smooth);
    Graphics2D g2 = (Graphics2D) g;
    g2.setColor(Color.WHITE);
    Dimension dim = getSize();
    g2.fillRect(0, 0, dim.width, dim.height);
    g2.setColor(Color.BLACK);

    g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                        parent.smooth ?
                        RenderingHints.VALUE_TEXT_ANTIALIAS_ON :
                        RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
    // add this one as well (after 1.0.9)
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        parent.smooth ?
                        RenderingHints.VALUE_ANTIALIAS_ON :
                        RenderingHints.VALUE_ANTIALIAS_OFF);
    //super.paintComponent(g2);
    Font font = getFont();
    int ascent = g2.getFontMetrics().getAscent();
//    System.out.println(f.getName());
    g2.setFont(font);
    g2.drawString(text, 5, dim.height - (dim.height - ascent) / 2);
  }

  public Dimension getPreferredSize() {
    return new Dimension(400, high);
  }

  public Dimension getMaximumSize() {
    return new Dimension(10000, high);
  }

  public Dimension getMinimumSize() {
    return new Dimension(100, high);
  }
}


/**
 * Frame for selecting which characters will be included with the font.
 */
class CharacterSelector extends JFrame {
  JRadioButton defaultCharsButton;
  JRadioButton allCharsButton;
  JRadioButton unicodeCharsButton;
  JScrollPane unicodeBlockScroller;
  JList charsetList;


  public CharacterSelector() {
    super("Character Selector");

    charsetList = new CheckBoxList();
    DefaultListModel model = new DefaultListModel();
    charsetList.setModel(model);
    for (String item : blockNames) {
      model.addElement(new JCheckBox(item));
    }

    unicodeBlockScroller =
      new JScrollPane(charsetList,
                      ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
                      ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

    Container outer = getContentPane();
    outer.setLayout(new BorderLayout());

    JPanel pain = new JPanel();
    pain.setBorder(new EmptyBorder(13, 13, 13, 13));
    outer.add(pain, BorderLayout.CENTER);

    pain.setLayout(new BoxLayout(pain, BoxLayout.Y_AXIS));

    String labelText =
      "Default characters will include most bitmaps for Mac OS\n" +
      "and Windows Latin scripts. Including all characters may\n" +
      "require large amounts of memory for all of the bitmaps.\n" +
      "For greater control, you can select specific Unicode blocks.";
    JTextArea textarea = new JTextArea(labelText);
    textarea.setBorder(new EmptyBorder(13, 8, 13, 8));
    textarea.setBackground(null);
    textarea.setEditable(false);
    textarea.setHighlighter(null);
    textarea.setFont(new Font("Dialog", Font.PLAIN, 12));
    pain.add(textarea);

    ActionListener listener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        //System.out.println("action " + unicodeCharsButton.isSelected());
        //unicodeBlockScroller.setEnabled(unicodeCharsButton.isSelected());
        charsetList.setEnabled(unicodeCharsButton.isSelected());
      }
    };
    defaultCharsButton = new JRadioButton("Default Characters");
    allCharsButton = new JRadioButton("All Characters");
    unicodeCharsButton = new JRadioButton("Specific Unicode Blocks");

    defaultCharsButton.addActionListener(listener);
    allCharsButton.addActionListener(listener);
    unicodeCharsButton.addActionListener(listener);

    ButtonGroup group = new ButtonGroup();
    group.add(defaultCharsButton);
    group.add(allCharsButton);
    group.add(unicodeCharsButton);

    JPanel radioPanel = new JPanel();
    //radioPanel.setBackground(Color.red);
    radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.Y_AXIS));
    radioPanel.add(defaultCharsButton);
    radioPanel.add(allCharsButton);
    radioPanel.add(unicodeCharsButton);

    JPanel rightStuff = new JPanel();
    rightStuff.setLayout(new BoxLayout(rightStuff, BoxLayout.X_AXIS));
    rightStuff.add(radioPanel);
    rightStuff.add(Box.createHorizontalGlue());
    pain.add(rightStuff);
    pain.add(Box.createVerticalStrut(13));

//    pain.add(radioPanel);

//    pain.add(defaultCharsButton);
//    pain.add(allCharsButton);
//    pain.add(unicodeCharsButton);

    defaultCharsButton.setSelected(true);
    charsetList.setEnabled(false);

    //frame.getContentPane().add(scroller);
    pain.add(unicodeBlockScroller);
    pain.add(Box.createVerticalStrut(8));

    JPanel buttons = new JPanel();
    JButton okButton = new JButton("OK");
    okButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          setVisible(false);
        }
      });
    okButton.setEnabled(true);
    buttons.add(okButton);
    pain.add(buttons);

    JRootPane root = getRootPane();
    root.setDefaultButton(okButton);
    ActionListener disposer = new ActionListener() {
        public void actionPerformed(ActionEvent actionEvent) {
          setVisible(false);
        }
      };
    Toolkit.registerWindowCloseKeys(root, disposer);
    Toolkit.setIcon(this);

    pack();

    Dimension screen = Toolkit.getScreenSize();
    Dimension windowSize = getSize();

    setLocation((screen.width - windowSize.width) / 2,
                (screen.height - windowSize.height) / 2);
  }


  protected char[] getCharacters() {
    if (defaultCharsButton.isSelected()) {
      return PFont.CHARSET;
    }

    char[] charset = new char[65536];
    if (allCharsButton.isSelected()) {
      for (int i = 0; i < 0xFFFF; i++) {
        charset[i] = (char) i;
      }
    } else {
      DefaultListModel model = (DefaultListModel) charsetList.getModel();
      int index = 0;
      for (int i = 0; i < BLOCKS.length; i++) {
        if (((JCheckBox) model.get(i)).isSelected()) {
          for (int j = blockStart[i]; j <= blockStop[i]; j++) {
            charset[index++] = (char) j;
          }
        }
      }
      charset = PApplet.subset(charset, 0, index);
    }
    //System.out.println("Creating font with " + charset.length + " characters.");
    return charset;
  }


  // http://www.unicode.org/Public/UNIDATA/Blocks.txt
  static final String[] BLOCKS = {
    "0000..007F; Basic Latin",
    "0080..00FF; Latin-1 Supplement",
    "0100..017F; Latin Extended-A",
    "0180..024F; Latin Extended-B",
    "0250..02AF; IPA Extensions",
    "02B0..02FF; Spacing Modifier Letters",
    "0300..036F; Combining Diacritical Marks",
    "0370..03FF; Greek and Coptic",
    "0400..04FF; Cyrillic",
    "0500..052F; Cyrillic Supplement",
    "0530..058F; Armenian",
    "0590..05FF; Hebrew",
    "0600..06FF; Arabic",
    "0700..074F; Syriac",
    "0750..077F; Arabic Supplement",
    "0780..07BF; Thaana",
    "07C0..07FF; NKo",
    "0800..083F; Samaritan",
    "0900..097F; Devanagari",
    "0980..09FF; Bengali",
    "0A00..0A7F; Gurmukhi",
    "0A80..0AFF; Gujarati",
    "0B00..0B7F; Oriya",
    "0B80..0BFF; Tamil",
    "0C00..0C7F; Telugu",
    "0C80..0CFF; Kannada",
    "0D00..0D7F; Malayalam",
    "0D80..0DFF; Sinhala",
    "0E00..0E7F; Thai",
    "0E80..0EFF; Lao",
    "0F00..0FFF; Tibetan",
    "1000..109F; Myanmar",
    "10A0..10FF; Georgian",
    "1100..11FF; Hangul Jamo",
    "1200..137F; Ethiopic",
    "1380..139F; Ethiopic Supplement",
    "13A0..13FF; Cherokee",
    "1400..167F; Unified Canadian Aboriginal Syllabics",
    "1680..169F; Ogham",
    "16A0..16FF; Runic",
    "1700..171F; Tagalog",
    "1720..173F; Hanunoo",
    "1740..175F; Buhid",
    "1760..177F; Tagbanwa",
    "1780..17FF; Khmer",
    "1800..18AF; Mongolian",
    "18B0..18FF; Unified Canadian Aboriginal Syllabics Extended",
    "1900..194F; Limbu",
    "1950..197F; Tai Le",
    "1980..19DF; New Tai Lue",
    "19E0..19FF; Khmer Symbols",
    "1A00..1A1F; Buginese",
    "1A20..1AAF; Tai Tham",
    "1B00..1B7F; Balinese",
    "1B80..1BBF; Sundanese",
    "1C00..1C4F; Lepcha",
    "1C50..1C7F; Ol Chiki",
    "1CD0..1CFF; Vedic Extensions",
    "1D00..1D7F; Phonetic Extensions",
    "1D80..1DBF; Phonetic Extensions Supplement",
    "1DC0..1DFF; Combining Diacritical Marks Supplement",
    "1E00..1EFF; Latin Extended Additional",
    "1F00..1FFF; Greek Extended",
    "2000..206F; General Punctuation",
    "2070..209F; Superscripts and Subscripts",
    "20A0..20CF; Currency Symbols",
    "20D0..20FF; Combining Diacritical Marks for Symbols",
    "2100..214F; Letterlike Symbols",
    "2150..218F; Number Forms",
    "2190..21FF; Arrows",
    "2200..22FF; Mathematical Operators",
    "2300..23FF; Miscellaneous Technical",
    "2400..243F; Control Pictures",
    "2440..245F; Optical Character Recognition",
    "2460..24FF; Enclosed Alphanumerics",
    "2500..257F; Box Drawing",
    "2580..259F; Block Elements",
    "25A0..25FF; Geometric Shapes",
    "2600..26FF; Miscellaneous Symbols",
    "2700..27BF; Dingbats",
    "27C0..27EF; Miscellaneous Mathematical Symbols-A",
    "27F0..27FF; Supplemental Arrows-A",
    "2800..28FF; Braille Patterns",
    "2900..297F; Supplemental Arrows-B",
    "2980..29FF; Miscellaneous Mathematical Symbols-B",
    "2A00..2AFF; Supplemental Mathematical Operators",
    "2B00..2BFF; Miscellaneous Symbols and Arrows",
    "2C00..2C5F; Glagolitic",
    "2C60..2C7F; Latin Extended-C",
    "2C80..2CFF; Coptic",
    "2D00..2D2F; Georgian Supplement",
    "2D30..2D7F; Tifinagh",
    "2D80..2DDF; Ethiopic Extended",
    "2DE0..2DFF; Cyrillic Extended-A",
    "2E00..2E7F; Supplemental Punctuation",
    "2E80..2EFF; CJK Radicals Supplement",
    "2F00..2FDF; Kangxi Radicals",
    "2FF0..2FFF; Ideographic Description Characters",
    "3000..303F; CJK Symbols and Punctuation",
    "3040..309F; Hiragana",
    "30A0..30FF; Katakana",
    "3100..312F; Bopomofo",
    "3130..318F; Hangul Compatibility Jamo",
    "3190..319F; Kanbun",
    "31A0..31BF; Bopomofo Extended",
    "31C0..31EF; CJK Strokes",
    "31F0..31FF; Katakana Phonetic Extensions",
    "3200..32FF; Enclosed CJK Letters and Months",
    "3300..33FF; CJK Compatibility",
    "3400..4DBF; CJK Unified Ideographs Extension A",
    "4DC0..4DFF; Yijing Hexagram Symbols",
    "4E00..9FFF; CJK Unified Ideographs",
    "A000..A48F; Yi Syllables",
    "A490..A4CF; Yi Radicals",
    "A4D0..A4FF; Lisu",
    "A500..A63F; Vai",
    "A640..A69F; Cyrillic Extended-B",
    "A6A0..A6FF; Bamum",
    "A700..A71F; Modifier Tone Letters",
    "A720..A7FF; Latin Extended-D",
    "A800..A82F; Syloti Nagri",
    "A830..A83F; Common Indic Number Forms",
    "A840..A87F; Phags-pa",
    "A880..A8DF; Saurashtra",
    "A8E0..A8FF; Devanagari Extended",
    "A900..A92F; Kayah Li",
    "A930..A95F; Rejang",
    "A960..A97F; Hangul Jamo Extended-A",
    "A980..A9DF; Javanese",
    "AA00..AA5F; Cham",
    "AA60..AA7F; Myanmar Extended-A",
    "AA80..AADF; Tai Viet",
    "ABC0..ABFF; Meetei Mayek",
    "AC00..D7AF; Hangul Syllables",
    "D7B0..D7FF; Hangul Jamo Extended-B",
    "D800..DB7F; High Surrogates",
    "DB80..DBFF; High Private Use Surrogates",
    "DC00..DFFF; Low Surrogates",
    "E000..F8FF; Private Use Area",
    "F900..FAFF; CJK Compatibility Ideographs",
    "FB00..FB4F; Alphabetic Presentation Forms",
    "FB50..FDFF; Arabic Presentation Forms-A",
    "FE00..FE0F; Variation Selectors",
    "FE10..FE1F; Vertical Forms",
    "FE20..FE2F; Combining Half Marks",
    "FE30..FE4F; CJK Compatibility Forms",
    "FE50..FE6F; Small Form Variants",
    "FE70..FEFF; Arabic Presentation Forms-B",
    "FF00..FFEF; Halfwidth and Fullwidth Forms",
    "FFF0..FFFF; Specials"
  };

  static String[] blockNames;
  static int[] blockStart;
  static int[] blockStop;
  static {
    int count = BLOCKS.length;
    blockNames = new String[count];
    blockStart = new int[count];
    blockStop = new int[count];
    for (int i = 0; i < count; i++) {
      String line = BLOCKS[i];
      blockStart[i] = PApplet.unhex(line.substring(0, 4));
      blockStop[i] = PApplet.unhex(line.substring(6, 10));
      blockNames[i] = line.substring(12);
    }
//    PApplet.println(codePointStop);
//    PApplet.println(codePoints);
  }
}


// Code for this CheckBoxList class found on the net, though I've lost the
// link. If you run across the original version, please let me know so that
// the original author can be credited properly. It was from a snippet
// collection, but it seems to have been picked up so many places with others
// placing their copyright on it, that I haven't been able to determine the
// original author. [fry 20100216]
class CheckBoxList extends JList {
  protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);

  public CheckBoxList() {
    setCellRenderer(new CellRenderer());

    addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        if (isEnabled()) {
          int index = locationToIndex(e.getPoint());

          if (index != -1) {
            JCheckBox checkbox = (JCheckBox)
            getModel().getElementAt(index);
            checkbox.setSelected(!checkbox.isSelected());
            repaint();
          }
        }
      }
    });
    setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  }


  protected class CellRenderer implements ListCellRenderer {
    public Component getListCellRendererComponent(JList list, Object value,
                                                  int index, boolean isSelected,
                                                  boolean cellHasFocus) {
      JCheckBox checkbox = (JCheckBox) value;
      checkbox.setBackground(isSelected ? getSelectionBackground() : getBackground());
      checkbox.setForeground(isSelected ? getSelectionForeground() : getForeground());
      //checkbox.setEnabled(isEnabled());
      checkbox.setEnabled(list.isEnabled());
      checkbox.setFont(getFont());
      checkbox.setFocusPainted(false);
      checkbox.setBorderPainted(true);
      checkbox.setBorder(isSelected ? UIManager.getBorder("List.focusCellHighlightBorder") : noFocusBorder);
      return checkbox;
    }
  }
}