1/*******************************************************************************
2 * Copyright 2011-2015 See AUTHORS file.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *   http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 ******************************************************************************/
16
17package com.badlogic.gdx.tools.hiero;
18
19import static org.lwjgl.opengl.GL11.*;
20
21import java.awt.BorderLayout;
22import java.awt.Component;
23import java.awt.Container;
24import java.awt.Dimension;
25import java.awt.EventQueue;
26import java.awt.FileDialog;
27import java.awt.FlowLayout;
28import java.awt.Font;
29import java.awt.Frame;
30import java.awt.GraphicsEnvironment;
31import java.awt.GridBagConstraints;
32import java.awt.GridBagLayout;
33import java.awt.Insets;
34import java.awt.LayoutManager;
35import java.awt.event.ActionEvent;
36import java.awt.event.ActionListener;
37import java.awt.event.KeyAdapter;
38import java.awt.event.KeyEvent;
39import java.awt.event.MouseAdapter;
40import java.awt.event.MouseEvent;
41import java.awt.event.WindowAdapter;
42import java.awt.event.WindowEvent;
43import java.awt.image.BufferedImage;
44import java.io.File;
45import java.io.IOException;
46import java.util.ArrayList;
47import java.util.Iterator;
48import java.util.List;
49import java.util.prefs.Preferences;
50
51import javax.swing.BorderFactory;
52import javax.swing.ButtonGroup;
53import javax.swing.DefaultComboBoxModel;
54import javax.swing.Icon;
55import javax.swing.ImageIcon;
56import javax.swing.JButton;
57import javax.swing.JCheckBox;
58import javax.swing.JColorChooser;
59import javax.swing.JComboBox;
60import javax.swing.JFormattedTextField;
61import javax.swing.JFrame;
62import javax.swing.JLabel;
63import javax.swing.JList;
64import javax.swing.JMenu;
65import javax.swing.JMenuBar;
66import javax.swing.JMenuItem;
67import javax.swing.JPanel;
68import javax.swing.JRadioButton;
69import javax.swing.JScrollPane;
70import javax.swing.JSpinner;
71import javax.swing.JTextField;
72import javax.swing.JTextPane;
73import javax.swing.JWindow;
74import javax.swing.KeyStroke;
75import javax.swing.ScrollPaneConstants;
76import javax.swing.SpinnerModel;
77import javax.swing.SpinnerNumberModel;
78import javax.swing.SwingUtilities;
79import javax.swing.border.EmptyBorder;
80import javax.swing.event.ChangeEvent;
81import javax.swing.event.ChangeListener;
82import javax.swing.event.DocumentEvent;
83import javax.swing.event.DocumentListener;
84import javax.swing.event.ListSelectionEvent;
85import javax.swing.event.ListSelectionListener;
86
87import org.lwjgl.opengl.GL11;
88
89import com.badlogic.gdx.ApplicationAdapter;
90import com.badlogic.gdx.Gdx;
91import com.badlogic.gdx.backends.lwjgl.LwjglCanvas;
92import com.badlogic.gdx.graphics.Color;
93import com.badlogic.gdx.graphics.Texture;
94import com.badlogic.gdx.graphics.g2d.BitmapFont;
95import com.badlogic.gdx.graphics.g2d.SpriteBatch;
96import com.badlogic.gdx.tools.hiero.unicodefont.GlyphPage;
97import com.badlogic.gdx.tools.hiero.unicodefont.UnicodeFont;
98import com.badlogic.gdx.tools.hiero.unicodefont.UnicodeFont.RenderType;
99import com.badlogic.gdx.tools.hiero.unicodefont.effects.ColorEffect;
100import com.badlogic.gdx.tools.hiero.unicodefont.effects.ConfigurableEffect;
101import com.badlogic.gdx.tools.hiero.unicodefont.effects.ConfigurableEffect.Value;
102import com.badlogic.gdx.utils.StringBuilder;
103import com.badlogic.gdx.tools.hiero.unicodefont.effects.DistanceFieldEffect;
104import com.badlogic.gdx.tools.hiero.unicodefont.effects.EffectUtil;
105import com.badlogic.gdx.tools.hiero.unicodefont.effects.GradientEffect;
106import com.badlogic.gdx.tools.hiero.unicodefont.effects.OutlineEffect;
107import com.badlogic.gdx.tools.hiero.unicodefont.effects.OutlineWobbleEffect;
108import com.badlogic.gdx.tools.hiero.unicodefont.effects.OutlineZigzagEffect;
109import com.badlogic.gdx.tools.hiero.unicodefont.effects.ShadowEffect;
110
111/** A tool to visualize settings for {@link UnicodeFont} and to export BMFont files for use with {@link BitmapFont}.
112 * <p>
113 * This tool is quite old. It was originally written for Slick, then ported to libgdx. It has had a few different contributors
114 * over the years, the FreeType rendering was tacked on and in general the code isn't great. It does its job though!
115 * @author Nathan Sweet */
116public class Hiero extends JFrame {
117	UnicodeFont unicodeFont;
118	Color renderingBackgroundColor = Color.BLACK;
119	List<EffectPanel> effectPanels = new ArrayList<EffectPanel>();
120	Preferences prefs;
121	ColorEffect colorEffect;
122	boolean batchMode = false;
123
124	JScrollPane appliedEffectsScroll;
125	JPanel appliedEffectsPanel;
126	JButton addEffectButton;
127	JTextPane sampleTextPane;
128	JSpinner padAdvanceXSpinner;
129	JList effectsList;
130	LwjglCanvas rendererCanvas;
131	JPanel gamePanel;
132	JTextField fontFileText;
133	JRadioButton fontFileRadio;
134	JRadioButton systemFontRadio;
135	JSpinner padBottomSpinner;
136	JSpinner padLeftSpinner;
137	JSpinner padRightSpinner;
138	JSpinner padTopSpinner;
139	JList fontList;
140	JSpinner fontSizeSpinner;
141	JSpinner gammaSpinner;
142	DefaultComboBoxModel fontListModel;
143	JLabel backgroundColorLabel;
144	JButton browseButton;
145	JSpinner padAdvanceYSpinner;
146	JCheckBox italicCheckBox;
147	JCheckBox boldCheckBox;
148	JCheckBox monoCheckBox;
149	JRadioButton javaRadio;
150	JRadioButton nativeRadio;
151	JRadioButton freeTypeRadio;
152	JLabel glyphsTotalLabel;
153	JLabel glyphPagesTotalLabel;
154	JComboBox glyphPageHeightCombo;
155	JComboBox glyphPageWidthCombo;
156	JComboBox glyphPageCombo;
157	JPanel glyphCachePanel;
158	JRadioButton glyphCacheRadio;
159	JRadioButton sampleTextRadio;
160	DefaultComboBoxModel glyphPageComboModel;
161	JButton resetCacheButton;
162	JButton sampleAsciiButton;
163	JButton sampleNeheButton;
164	JButton sampleExtendedButton;
165	DefaultComboBoxModel effectsListModel;
166	JMenuItem openMenuItem;
167	JMenuItem saveMenuItem;
168	JMenuItem exitMenuItem;
169	JMenuItem saveBMFontMenuItem;
170	File saveBmFontFile;
171	String lastSaveFilename = "", lastSaveBMFilename = "", lastOpenFilename = "";
172	JPanel effectsPanel;
173	JScrollPane effectsScroll;
174	JPanel unicodePanel, bitmapPanel;
175
176	public Hiero (String[] args) {
177		super("Hiero v5 - Bitmap Font Tool");
178		Splash splash = new Splash(this, "/splash.jpg", 2000);
179		initialize();
180		splash.close();
181
182		rendererCanvas = new LwjglCanvas(new Renderer());
183		gamePanel.add(rendererCanvas.getCanvas());
184
185		prefs = Preferences.userNodeForPackage(Hiero.class);
186		java.awt.Color backgroundColor = EffectUtil.fromString(prefs.get("background", "000000"));
187		backgroundColorLabel.setIcon(getColorIcon(backgroundColor));
188		renderingBackgroundColor = new Color(backgroundColor.getRed() / 255f, backgroundColor.getGreen() / 255f,
189			backgroundColor.getBlue() / 255f, 1);
190		fontList.setSelectedValue(prefs.get("system.font", "Arial"), true);
191		fontFileText.setText(prefs.get("font.file", ""));
192
193		java.awt.Color foregroundColor = EffectUtil.fromString(prefs.get("foreground", "ffffff"));
194		colorEffect = new ColorEffect();
195		colorEffect.setColor(foregroundColor);
196		effectsListModel.addElement(colorEffect);
197		effectsListModel.addElement(new GradientEffect());
198		effectsListModel.addElement(new OutlineEffect());
199		effectsListModel.addElement(new OutlineWobbleEffect());
200		effectsListModel.addElement(new OutlineZigzagEffect());
201		effectsListModel.addElement(new ShadowEffect());
202		effectsListModel.addElement(new DistanceFieldEffect());
203		new EffectPanel(colorEffect);
204
205		parseArgs(args);
206
207		addWindowListener(new WindowAdapter() {
208			public void windowClosed (WindowEvent event) {
209				System.exit(0);
210				// Gdx.app.quit();
211			}
212		});
213
214		updateFontSelector();
215		setVisible(true);
216	}
217
218	void initialize () {
219		initializeComponents();
220		initializeMenus();
221		initializeEvents();
222
223		setSize(1024, 600);
224		setLocationRelativeTo(null);
225		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
226	}
227
228	private void parseArgs (String[] args) {
229		float scale = 1f;
230
231		for (int i = 0; i < args.length; i++) {
232			final String param = args[i];
233			final boolean more = i < args.length - 1;
234
235			if (param.equals("-b") || param.equals("--batch")) {
236				batchMode = true;
237			} else if (more && (param.equals("-s") || param.equals("--scale"))) {
238				scale = Float.parseFloat(args[++i]);
239			} else if (more && (param.equals("-i") || param.equals("--input"))) {
240				File f = new File(args[++i]);
241				open(f);
242				fontFileRadio.setText("");
243				updateFont();
244			} else if (more && (param.equals("-o") || param.equals("--output"))) {
245				File f = new File(args[++i]);
246				saveBm(f);
247			} else {
248				System.err.println("Unknown parameter: " + param);
249				exit(3);
250			}
251		}
252
253		fontSizeSpinner.setValue((int)(0.5f + Math.max(4, scale * ((Integer)fontSizeSpinner.getValue()))));
254	}
255
256	void updateFontSelector () {
257		final boolean useFile = fontFileRadio.isSelected();
258		fontList.setEnabled(!useFile);
259		fontFileText.setEnabled(useFile);
260		browseButton.setEnabled(useFile);
261	}
262
263	void updateFont () {
264		int fontSize = ((Integer)fontSizeSpinner.getValue()).intValue();
265
266		File file = null;
267		if (fontFileRadio.isSelected()) {
268			file = new File(fontFileText.getText());
269			if (!file.exists() || !file.isFile()) file = null;
270		}
271
272		boolean isFreeType = freeTypeRadio.isSelected();
273		boolean isNative = nativeRadio.isSelected();
274		boolean isJava = javaRadio.isSelected();
275		addEffectButton.setVisible(isJava);
276		effectsScroll.setVisible(isJava);
277		appliedEffectsScroll.setVisible(isJava);
278		boldCheckBox.setEnabled(!isFreeType);
279		italicCheckBox.setEnabled(!isFreeType);
280		bitmapPanel.setVisible(isFreeType);
281		unicodePanel.setVisible(!isFreeType);
282		updateFontSelector();
283
284		UnicodeFont unicodeFont = null;
285		if (file != null) {
286			// Load from file.
287			try {
288				unicodeFont = new UnicodeFont(fontFileText.getText(), fontSize, boldCheckBox.isSelected(),
289					italicCheckBox.isSelected());
290			} catch (Throwable ex) {
291				ex.printStackTrace();
292				fontFileRadio.setSelected(false);
293			}
294		}
295
296		if (unicodeFont == null) {
297			// Load from java.awt.Font (kerning not available!).
298			unicodeFont = new UnicodeFont(Font.decode((String)fontList.getSelectedValue()), fontSize, boldCheckBox.isSelected(),
299				italicCheckBox.isSelected());
300		}
301
302		unicodeFont.setMono(monoCheckBox.isSelected());
303		unicodeFont.setGamma(((Number)gammaSpinner.getValue()).floatValue());
304		unicodeFont.setPaddingTop(((Number)padTopSpinner.getValue()).intValue());
305		unicodeFont.setPaddingRight(((Number)padRightSpinner.getValue()).intValue());
306		unicodeFont.setPaddingBottom(((Number)padBottomSpinner.getValue()).intValue());
307		unicodeFont.setPaddingLeft(((Number)padLeftSpinner.getValue()).intValue());
308		unicodeFont.setPaddingAdvanceX(((Number)padAdvanceXSpinner.getValue()).intValue());
309		unicodeFont.setPaddingAdvanceY(((Number)padAdvanceYSpinner.getValue()).intValue());
310		unicodeFont.setGlyphPageWidth(((Number)glyphPageWidthCombo.getSelectedItem()).intValue());
311		unicodeFont.setGlyphPageHeight(((Number)glyphPageHeightCombo.getSelectedItem()).intValue());
312		if (nativeRadio.isSelected())
313			unicodeFont.setRenderType(RenderType.Native);
314		else if (freeTypeRadio.isSelected())
315			unicodeFont.setRenderType(RenderType.FreeType);
316		else
317			unicodeFont.setRenderType(RenderType.Java);
318
319		for (Iterator iter = effectPanels.iterator(); iter.hasNext();) {
320			EffectPanel panel = (EffectPanel)iter.next();
321			unicodeFont.getEffects().add(panel.getEffect());
322		}
323
324		int size = sampleTextPane.getFont().getSize();
325		if (size < 14) size = 14;
326		sampleTextPane.setFont(unicodeFont.getFont().deriveFont((float)size));
327
328		if (this.unicodeFont != null) this.unicodeFont.dispose();
329		this.unicodeFont = unicodeFont;
330
331		updateFontSelector();
332	}
333
334	void saveBm (File file) {
335		saveBmFontFile = file;
336	}
337
338	void save (File file) throws IOException {
339		HieroSettings settings = new HieroSettings();
340		settings.setFontName((String)fontList.getSelectedValue());
341		settings.setFontSize(((Integer)fontSizeSpinner.getValue()).intValue());
342		settings.setFont2File(fontFileText.getText());
343		settings.setFont2Active(fontFileRadio.isSelected());
344		settings.setBold(boldCheckBox.isSelected());
345		settings.setItalic(italicCheckBox.isSelected());
346		settings.setMono(monoCheckBox.isSelected());
347		settings.setGamma(((Number)gammaSpinner.getValue()).floatValue());
348		settings.setPaddingTop(((Number)padTopSpinner.getValue()).intValue());
349		settings.setPaddingRight(((Number)padRightSpinner.getValue()).intValue());
350		settings.setPaddingBottom(((Number)padBottomSpinner.getValue()).intValue());
351		settings.setPaddingLeft(((Number)padLeftSpinner.getValue()).intValue());
352		settings.setPaddingAdvanceX(((Number)padAdvanceXSpinner.getValue()).intValue());
353		settings.setPaddingAdvanceY(((Number)padAdvanceYSpinner.getValue()).intValue());
354		settings.setGlyphPageWidth(((Number)glyphPageWidthCombo.getSelectedItem()).intValue());
355		settings.setGlyphPageHeight(((Number)glyphPageHeightCombo.getSelectedItem()).intValue());
356		settings.setGlyphText(sampleTextPane.getText());
357		for (Iterator iter = effectPanels.iterator(); iter.hasNext();) {
358			EffectPanel panel = (EffectPanel)iter.next();
359			settings.getEffects().add(panel.getEffect());
360		}
361		settings.save(file);
362	}
363
364	void open (File file) {
365		EffectPanel[] panels = (EffectPanel[])effectPanels.toArray(new EffectPanel[effectPanels.size()]);
366		for (int i = 0; i < panels.length; i++)
367			panels[i].remove();
368
369		HieroSettings settings = new HieroSettings(file.getAbsolutePath());
370		fontList.setSelectedValue(settings.getFontName(), true);
371		fontSizeSpinner.setValue(new Integer(settings.getFontSize()));
372		boldCheckBox.setSelected(settings.isBold());
373		italicCheckBox.setSelected(settings.isItalic());
374		monoCheckBox.setSelected(settings.isMono());
375		gammaSpinner.setValue(new Float(settings.getGamma()));
376		padTopSpinner.setValue(new Integer(settings.getPaddingTop()));
377		padRightSpinner.setValue(new Integer(settings.getPaddingRight()));
378		padBottomSpinner.setValue(new Integer(settings.getPaddingBottom()));
379		padLeftSpinner.setValue(new Integer(settings.getPaddingLeft()));
380		padAdvanceXSpinner.setValue(new Integer(settings.getPaddingAdvanceX()));
381		padAdvanceYSpinner.setValue(new Integer(settings.getPaddingAdvanceY()));
382		glyphPageWidthCombo.setSelectedItem(new Integer(settings.getGlyphPageWidth()));
383		glyphPageHeightCombo.setSelectedItem(new Integer(settings.getGlyphPageHeight()));
384		String gt = settings.getGlyphText();
385		if (gt.length() > 0) {
386			sampleTextPane.setText(settings.getGlyphText());
387		}
388
389		final String font2 = settings.getFont2File();
390		if (font2.length() > 0)
391			fontFileText.setText(font2);
392		else
393			fontFileText.setText(prefs.get("font.file", ""));
394
395		fontFileRadio.setSelected(settings.isFont2Active());
396		systemFontRadio.setSelected(!settings.isFont2Active());
397
398		for (Iterator iter = settings.getEffects().iterator(); iter.hasNext();) {
399			ConfigurableEffect settingsEffect = (ConfigurableEffect)iter.next();
400			for (int i = 0, n = effectsListModel.getSize(); i < n; i++) {
401				ConfigurableEffect effect = (ConfigurableEffect)effectsListModel.getElementAt(i);
402				if (effect.getClass() == settingsEffect.getClass()) {
403					effect.setValues(settingsEffect.getValues());
404					new EffectPanel(effect);
405					break;
406				}
407			}
408		}
409
410		updateFont();
411	}
412
413	void exit (final int exitCode) {
414		rendererCanvas.stop();
415		EventQueue.invokeLater(new Runnable() {
416			public void run () {
417				System.exit(exitCode);
418			}
419		});
420	}
421
422	private void initializeEvents () {
423		fontList.addListSelectionListener(new ListSelectionListener() {
424			public void valueChanged (ListSelectionEvent evt) {
425				if (evt.getValueIsAdjusting()) return;
426				prefs.put("system.font", (String)fontList.getSelectedValue());
427				updateFont();
428			}
429		});
430
431		class FontUpdateListener implements ChangeListener, ActionListener {
432			public void stateChanged (ChangeEvent evt) {
433				updateFont();
434			}
435
436			public void actionPerformed (ActionEvent evt) {
437				updateFont();
438			}
439
440			public void addSpinners (JSpinner[] spinners) {
441				for (int i = 0; i < spinners.length; i++) {
442					final JSpinner spinner = spinners[i];
443					spinner.addChangeListener(this);
444					((JSpinner.DefaultEditor)spinner.getEditor()).getTextField().addKeyListener(new KeyAdapter() {
445						String lastText;
446
447						public void keyReleased (KeyEvent evt) {
448							JFormattedTextField textField = ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField();
449							String text = textField.getText();
450							if (text.length() == 0) return;
451							if (text.equals(lastText)) return;
452							lastText = text;
453							int caretPosition = textField.getCaretPosition();
454							try {
455								spinner.setValue(Integer.valueOf(text));
456							} catch (NumberFormatException ex) {
457							}
458							textField.setCaretPosition(caretPosition);
459						}
460					});
461				}
462			}
463		}
464		FontUpdateListener listener = new FontUpdateListener();
465
466		listener.addSpinners(new JSpinner[] {padTopSpinner, padRightSpinner, padBottomSpinner, padLeftSpinner, padAdvanceXSpinner,
467			padAdvanceYSpinner});
468		fontSizeSpinner.addChangeListener(listener);
469		gammaSpinner.addChangeListener(listener);
470
471		glyphPageWidthCombo.addActionListener(listener);
472		glyphPageHeightCombo.addActionListener(listener);
473		boldCheckBox.addActionListener(listener);
474		italicCheckBox.addActionListener(listener);
475		monoCheckBox.addActionListener(listener);
476		resetCacheButton.addActionListener(listener);
477		javaRadio.addActionListener(listener);
478		nativeRadio.addActionListener(listener);
479		freeTypeRadio.addActionListener(listener);
480
481		sampleTextRadio.addActionListener(new ActionListener() {
482			public void actionPerformed (ActionEvent evt) {
483				glyphCachePanel.setVisible(false);
484			}
485		});
486		glyphCacheRadio.addActionListener(new ActionListener() {
487			public void actionPerformed (ActionEvent evt) {
488				glyphCachePanel.setVisible(true);
489			}
490		});
491
492		fontFileText.getDocument().addDocumentListener(new DocumentListener() {
493			public void removeUpdate (DocumentEvent evt) {
494				changed();
495			}
496
497			public void insertUpdate (DocumentEvent evt) {
498				changed();
499			}
500
501			public void changedUpdate (DocumentEvent evt) {
502				changed();
503			}
504
505			private void changed () {
506				File file = new File(fontFileText.getText());
507				if (fontList.isEnabled() && (!file.exists() || !file.isFile())) return;
508				prefs.put("font.file", fontFileText.getText());
509				updateFont();
510			}
511		});
512
513		final ActionListener al = new ActionListener() {
514			public void actionPerformed (ActionEvent evt) {
515				updateFontSelector();
516				updateFont();
517			}
518		};
519
520		systemFontRadio.addActionListener(al);
521		fontFileRadio.addActionListener(al);
522
523		browseButton.addActionListener(new ActionListener() {
524			public void actionPerformed (ActionEvent evt) {
525				FileDialog dialog = new FileDialog(Hiero.this, "Choose TrueType font file", FileDialog.LOAD);
526				dialog.setLocationRelativeTo(null);
527				dialog.setFile("*.ttf");
528				dialog.setDirectory(prefs.get("dir.font", ""));
529				dialog.setVisible(true);
530				if (dialog.getDirectory() != null) {
531					prefs.put("dir.font", dialog.getDirectory());
532				}
533
534				String fileName = dialog.getFile();
535				if (fileName == null) return;
536				fontFileText.setText(new File(dialog.getDirectory(), fileName).getAbsolutePath());
537			}
538		});
539
540		backgroundColorLabel.addMouseListener(new MouseAdapter() {
541			public void mouseClicked (MouseEvent evt) {
542				java.awt.Color color = JColorChooser.showDialog(null, "Choose a background color",
543					EffectUtil.fromString(prefs.get("background", "000000")));
544				if (color == null) return;
545				renderingBackgroundColor = new Color(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, 1);
546				backgroundColorLabel.setIcon(getColorIcon(color));
547				prefs.put("background", EffectUtil.toString(color));
548			}
549		});
550
551		effectsList.addListSelectionListener(new ListSelectionListener() {
552			public void valueChanged (ListSelectionEvent evt) {
553				ConfigurableEffect selectedEffect = (ConfigurableEffect)effectsList.getSelectedValue();
554				boolean enabled = selectedEffect != null;
555				for (Iterator iter = effectPanels.iterator(); iter.hasNext();) {
556					ConfigurableEffect effect = ((EffectPanel)iter.next()).getEffect();
557					if (effect == selectedEffect) {
558						enabled = false;
559						break;
560					}
561				}
562				addEffectButton.setEnabled(enabled);
563			}
564		});
565
566		effectsList.addMouseListener(new MouseAdapter() {
567			public void mouseClicked (MouseEvent evt) {
568				if (evt.getClickCount() == 2 && addEffectButton.isEnabled()) addEffectButton.doClick();
569			}
570		});
571
572		addEffectButton.addActionListener(new ActionListener() {
573			public void actionPerformed (ActionEvent evt) {
574				new EffectPanel((ConfigurableEffect)effectsList.getSelectedValue());
575			}
576		});
577
578		openMenuItem.addActionListener(new ActionListener() {
579			public void actionPerformed (ActionEvent evt) {
580				FileDialog dialog = new FileDialog(Hiero.this, "Open Hiero settings file", FileDialog.LOAD);
581				dialog.setLocationRelativeTo(null);
582				dialog.setFile("*.hiero");
583				dialog.setDirectory(prefs.get("dir.open", ""));
584				dialog.setVisible(true);
585				if (dialog.getDirectory() != null) {
586					prefs.put("dir.open", dialog.getDirectory());
587				}
588
589				String fileName = dialog.getFile();
590				if (fileName == null) return;
591				lastOpenFilename = fileName;
592				open(new File(dialog.getDirectory(), fileName));
593			}
594		});
595
596		saveMenuItem.addActionListener(new ActionListener() {
597			public void actionPerformed (ActionEvent evt) {
598				FileDialog dialog = new FileDialog(Hiero.this, "Save Hiero settings file", FileDialog.SAVE);
599				dialog.setLocationRelativeTo(null);
600				dialog.setFile("*.hiero");
601				dialog.setDirectory(prefs.get("dir.save", ""));
602
603				if (lastSaveFilename.length() > 0) {
604					dialog.setFile(lastSaveFilename);
605				} else if (lastOpenFilename.length() > 0) {
606					dialog.setFile(lastOpenFilename);
607				}
608
609				dialog.setVisible(true);
610
611				if (dialog.getDirectory() != null) {
612					prefs.put("dir.save", dialog.getDirectory());
613				}
614
615				String fileName = dialog.getFile();
616				if (fileName == null) return;
617				if (!fileName.endsWith(".hiero")) fileName += ".hiero";
618				lastSaveFilename = fileName;
619				File file = new File(dialog.getDirectory(), fileName);
620				try {
621					save(file);
622				} catch (IOException ex) {
623					throw new RuntimeException("Error saving Hiero settings file: " + file.getAbsolutePath(), ex);
624				}
625			}
626		});
627
628		saveBMFontMenuItem.addActionListener(new ActionListener() {
629			public void actionPerformed (ActionEvent evt) {
630				FileDialog dialog = new FileDialog(Hiero.this, "Save BMFont files", FileDialog.SAVE);
631				dialog.setLocationRelativeTo(null);
632				dialog.setFile("*.fnt");
633				dialog.setDirectory(prefs.get("dir.savebm", ""));
634
635				if (lastSaveBMFilename.length() > 0) {
636					dialog.setFile(lastSaveBMFilename);
637				} else if (lastOpenFilename.length() > 0) {
638					dialog.setFile(lastOpenFilename.replace(".hiero", ".fnt"));
639				}
640
641				dialog.setVisible(true);
642				if (dialog.getDirectory() != null) {
643					prefs.put("dir.savebm", dialog.getDirectory());
644				}
645
646				String fileName = dialog.getFile();
647				if (fileName == null) return;
648				lastSaveBMFilename = fileName;
649				saveBm(new File(dialog.getDirectory(), fileName));
650			}
651		});
652
653		exitMenuItem.addActionListener(new ActionListener() {
654			public void actionPerformed (ActionEvent evt) {
655				dispose();
656			}
657		});
658
659		sampleNeheButton.addActionListener(new ActionListener() {
660			public void actionPerformed (ActionEvent evt) {
661				sampleTextPane.setText(NEHE_CHARS);
662				resetCacheButton.doClick();
663			}
664		});
665
666		sampleAsciiButton.addActionListener(new ActionListener() {
667			public void actionPerformed (ActionEvent evt) {
668				StringBuilder buffer = new StringBuilder();
669				buffer.append(NEHE_CHARS);
670				buffer.append('\n');
671				int count = 0;
672				for (int i = 33; i <= 255; i++) {
673					if (buffer.indexOf(Character.toString((char)i)) != -1) continue;
674					buffer.append((char)i);
675					if (++count % 30 == 0) buffer.append('\n');
676				}
677				sampleTextPane.setText(buffer.toString());
678				resetCacheButton.doClick();
679			}
680		});
681
682		sampleExtendedButton.addActionListener(new ActionListener() {
683			public void actionPerformed (ActionEvent evt) {
684				sampleTextPane.setText(EXTENDED_CHARS);
685				resetCacheButton.doClick();
686			}
687		});
688	}
689
690	private void initializeComponents () {
691		getContentPane().setLayout(new GridBagLayout());
692		JPanel leftSidePanel = new JPanel();
693		leftSidePanel.setLayout(new GridBagLayout());
694		getContentPane().add(leftSidePanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
695			GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
696		{
697			JPanel fontPanel = new JPanel();
698			leftSidePanel.add(fontPanel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
699				GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0));
700			fontPanel.setLayout(new GridBagLayout());
701			fontPanel.setBorder(BorderFactory.createTitledBorder("Font"));
702			{
703				fontSizeSpinner = new JSpinner(new SpinnerNumberModel(32, 0, 256, 1));
704				fontPanel.add(fontSizeSpinner, new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
705					GridBagConstraints.NONE, new Insets(0, 0, 5, 10), 0, 0));
706				((JSpinner.DefaultEditor)fontSizeSpinner.getEditor()).getTextField().setColumns(2);
707			}
708			{
709				JScrollPane fontScroll = new JScrollPane();
710				fontPanel.add(fontScroll, new GridBagConstraints(1, 1, 3, 1, 1.0, 1.0, GridBagConstraints.CENTER,
711					GridBagConstraints.BOTH, new Insets(0, 0, 5, 5), 0, 0));
712				{
713					fontListModel = new DefaultComboBoxModel(
714						GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames());
715					fontList = new JList();
716					fontScroll.setViewportView(fontList);
717					fontList.setModel(fontListModel);
718					fontList.setVisibleRowCount(6);
719					fontList.setSelectedIndex(0);
720					fontScroll.setMinimumSize(new Dimension(220, fontList.getPreferredScrollableViewportSize().height));
721				}
722			}
723			{
724				systemFontRadio = new JRadioButton("System:", true);
725				fontPanel.add(systemFontRadio, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST,
726					GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0));
727				systemFontRadio.setMargin(new Insets(0, 0, 0, 0));
728			}
729			{
730				fontFileRadio = new JRadioButton("File:");
731				fontPanel.add(fontFileRadio, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
732					GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));
733				fontFileRadio.setMargin(new Insets(0, 0, 0, 0));
734			}
735			{
736				fontFileText = new JTextField();
737				fontPanel.add(fontFileText, new GridBagConstraints(1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER,
738					GridBagConstraints.HORIZONTAL, new Insets(0, 0, 5, 0), 0, 0));
739			}
740			{
741				fontPanel.add(new JLabel("Size:"), new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
742					GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
743			}
744
745			{
746				unicodePanel = new JPanel(new GridBagLayout());
747				fontPanel.add(unicodePanel, new GridBagConstraints(2, 3, 2, 1, 0.0, 0.0, GridBagConstraints.EAST,
748					GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 5), 0, 0));
749				{
750					boldCheckBox = new JCheckBox("Bold");
751					unicodePanel.add(boldCheckBox, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
752						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
753				}
754				{
755					italicCheckBox = new JCheckBox("Italic");
756					unicodePanel.add(italicCheckBox, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST,
757						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
758				}
759			}
760			{
761				bitmapPanel = new JPanel(new GridBagLayout());
762				fontPanel.add(bitmapPanel, new GridBagConstraints(2, 3, 2, 1, 1.0, 0.0, GridBagConstraints.WEST,
763					GridBagConstraints.NONE, new Insets(0, 0, 0, 5), 0, 0));
764				{
765					bitmapPanel.add(new JLabel("Gamma:"), new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
766						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
767				}
768				{
769					gammaSpinner = new JSpinner(new SpinnerNumberModel(1.8f, 0, 30, 0.01));
770					((JSpinner.DefaultEditor)gammaSpinner.getEditor()).getTextField().setColumns(2);
771					bitmapPanel.add(gammaSpinner, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
772						GridBagConstraints.NONE, new Insets(0, 0, 5, 10), 0, 0));
773				}
774				{
775					monoCheckBox = new JCheckBox("Mono");
776					bitmapPanel.add(monoCheckBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
777						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
778				}
779			}
780			{
781				browseButton = new JButton("...");
782				fontPanel.add(browseButton, new GridBagConstraints(3, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
783					GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
784				browseButton.setMargin(new Insets(0, 0, 0, 0));
785			}
786			{
787				fontPanel.add(new JLabel("Rendering:"), new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST,
788					GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
789			}
790			{
791				JPanel renderingPanel = new JPanel(new GridBagLayout());
792				fontPanel.add(renderingPanel, new GridBagConstraints(1, 4, 3, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST,
793					GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
794				{
795					freeTypeRadio = new JRadioButton("FreeType");
796					renderingPanel.add(freeTypeRadio, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
797						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
798				}
799				{
800					javaRadio = new JRadioButton("Java");
801					renderingPanel.add(javaRadio, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
802						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
803				}
804				{
805					nativeRadio = new JRadioButton("Native");
806					renderingPanel.add(nativeRadio, new GridBagConstraints(2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST,
807						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
808				}
809			}
810			ButtonGroup buttonGroup = new ButtonGroup();
811			buttonGroup.add(systemFontRadio);
812			buttonGroup.add(fontFileRadio);
813			buttonGroup = new ButtonGroup();
814			buttonGroup.add(freeTypeRadio);
815			buttonGroup.add(javaRadio);
816			buttonGroup.add(nativeRadio);
817			freeTypeRadio.setSelected(true);
818		}
819		{
820			JPanel samplePanel = new JPanel();
821			leftSidePanel.add(samplePanel, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,
822				GridBagConstraints.BOTH, new Insets(5, 0, 5, 5), 0, 0));
823			samplePanel.setLayout(new GridBagLayout());
824			samplePanel.setBorder(BorderFactory.createTitledBorder("Sample Text"));
825			{
826				JScrollPane textScroll = new JScrollPane();
827				samplePanel.add(textScroll, new GridBagConstraints(0, 0, 4, 1, 1.0, 1.0, GridBagConstraints.CENTER,
828					GridBagConstraints.BOTH, new Insets(0, 5, 5, 5), 0, 0));
829				{
830					sampleTextPane = new JTextPane();
831					textScroll.setViewportView(sampleTextPane);
832				}
833			}
834			{
835				sampleNeheButton = new JButton();
836				sampleNeheButton.setText("NEHE");
837				samplePanel.add(sampleNeheButton, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
838					GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
839			}
840			{
841				sampleAsciiButton = new JButton();
842				sampleAsciiButton.setText("ASCII");
843				samplePanel.add(sampleAsciiButton, new GridBagConstraints(3, 1, 1, 1, 0, 0.0, GridBagConstraints.EAST,
844					GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
845			}
846			{
847				sampleExtendedButton = new JButton();
848				sampleExtendedButton.setText("Extended");
849				samplePanel.add(sampleExtendedButton, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.EAST,
850					GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
851			}
852		}
853		{
854			JPanel renderingPanel = new JPanel();
855			leftSidePanel.add(renderingPanel, new GridBagConstraints(0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER,
856				GridBagConstraints.BOTH, new Insets(0, 5, 5, 5), 0, 0));
857			renderingPanel.setBorder(BorderFactory.createTitledBorder("Rendering"));
858			renderingPanel.setLayout(new GridBagLayout());
859			{
860				JPanel wrapperPanel = new JPanel();
861				renderingPanel.add(wrapperPanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
862					GridBagConstraints.BOTH, new Insets(0, 5, 5, 5), 0, 0));
863				wrapperPanel.setLayout(new BorderLayout());
864				wrapperPanel.setBackground(java.awt.Color.white);
865				{
866					gamePanel = new JPanel();
867					wrapperPanel.add(gamePanel);
868					gamePanel.setLayout(new BorderLayout());
869					gamePanel.setBackground(java.awt.Color.white);
870				}
871			}
872			{
873				glyphCachePanel = new JPanel() {
874					private int maxWidth;
875
876					public Dimension getPreferredSize () {
877						// Keep glyphCachePanel width from ever going down so the CanvasGameContainer doesn't change sizes and flicker.
878						Dimension size = super.getPreferredSize();
879						maxWidth = Math.max(maxWidth, size.width);
880						size.width = maxWidth;
881						return size;
882					}
883				};
884				glyphCachePanel.setVisible(false);
885				renderingPanel.add(glyphCachePanel, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTH,
886					GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
887				glyphCachePanel.setLayout(new GridBagLayout());
888				{
889					glyphCachePanel.add(new JLabel("Glyphs:"), new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
890						GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));
891				}
892				{
893					glyphCachePanel.add(new JLabel("Pages:"), new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
894						GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));
895				}
896				{
897					glyphCachePanel.add(new JLabel("Page width:"), new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
898						GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));
899				}
900				{
901					glyphCachePanel.add(new JLabel("Page height:"), new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0,
902						GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));
903				}
904				{
905					glyphPageWidthCombo = new JComboBox(new DefaultComboBoxModel(new Integer[] {new Integer(32), new Integer(64),
906						new Integer(128), new Integer(256), new Integer(512), new Integer(1024), new Integer(2048)}));
907					glyphCachePanel.add(glyphPageWidthCombo, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
908						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
909					glyphPageWidthCombo.setSelectedIndex(4);
910				}
911				{
912					glyphPageHeightCombo = new JComboBox(new DefaultComboBoxModel(new Integer[] {new Integer(32), new Integer(64),
913						new Integer(128), new Integer(256), new Integer(512), new Integer(1024), new Integer(2048)}));
914					glyphCachePanel.add(glyphPageHeightCombo, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
915						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
916					glyphPageHeightCombo.setSelectedIndex(4);
917				}
918				{
919					resetCacheButton = new JButton("Reset Cache");
920					glyphCachePanel.add(resetCacheButton, new GridBagConstraints(0, 6, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER,
921						GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));
922				}
923				{
924					glyphPagesTotalLabel = new JLabel("1");
925					glyphCachePanel.add(glyphPagesTotalLabel, new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
926						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
927				}
928				{
929					glyphsTotalLabel = new JLabel("0");
930					glyphCachePanel.add(glyphsTotalLabel, new GridBagConstraints(1, 4, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
931						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
932				}
933				{
934					glyphPageComboModel = new DefaultComboBoxModel();
935					glyphPageCombo = new JComboBox();
936					glyphCachePanel.add(glyphPageCombo, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
937						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
938					glyphPageCombo.setModel(glyphPageComboModel);
939				}
940				{
941					glyphCachePanel.add(new JLabel("View:"), new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
942						GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));
943				}
944			}
945			{
946				JPanel radioButtonsPanel = new JPanel();
947				renderingPanel.add(radioButtonsPanel, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.CENTER,
948					GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
949				radioButtonsPanel.setLayout(new GridBagLayout());
950				{
951					sampleTextRadio = new JRadioButton("Sample text");
952					radioButtonsPanel.add(sampleTextRadio, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
953						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
954					sampleTextRadio.setSelected(true);
955				}
956				{
957					glyphCacheRadio = new JRadioButton("Glyph cache");
958					radioButtonsPanel.add(glyphCacheRadio, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
959						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
960				}
961				{
962					radioButtonsPanel.add(new JLabel("Background:"), new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
963						GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));
964				}
965				{
966					backgroundColorLabel = new JLabel();
967					radioButtonsPanel.add(backgroundColorLabel, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST,
968						GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));
969				}
970				ButtonGroup buttonGroup = new ButtonGroup();
971				buttonGroup.add(glyphCacheRadio);
972				buttonGroup.add(sampleTextRadio);
973			}
974		}
975		JPanel rightSidePanel = new JPanel();
976		rightSidePanel.setLayout(new GridBagLayout());
977		getContentPane().add(rightSidePanel, new GridBagConstraints(1, 0, 1, 2, 0.0, 0.0, GridBagConstraints.CENTER,
978			GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
979		{
980			JPanel paddingPanel = new JPanel();
981			paddingPanel.setLayout(new GridBagLayout());
982			rightSidePanel.add(paddingPanel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
983				GridBagConstraints.BOTH, new Insets(0, 0, 5, 5), 0, 0));
984			paddingPanel.setBorder(BorderFactory.createTitledBorder("Padding"));
985			{
986				padTopSpinner = new JSpinner(new SpinnerNumberModel(1, 0, 999, 1));
987				paddingPanel.add(padTopSpinner, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
988					GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
989				((JSpinner.DefaultEditor)padTopSpinner.getEditor()).getTextField().setColumns(2);
990			}
991			{
992				padRightSpinner = new JSpinner(new SpinnerNumberModel(1, 0, 999, 1));
993				paddingPanel.add(padRightSpinner, new GridBagConstraints(2, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST,
994					GridBagConstraints.NONE, new Insets(0, 0, 0, 5), 0, 0));
995				((JSpinner.DefaultEditor)padRightSpinner.getEditor()).getTextField().setColumns(2);
996			}
997			{
998				padLeftSpinner = new JSpinner(new SpinnerNumberModel(1, 0, 999, 1));
999				paddingPanel.add(padLeftSpinner, new GridBagConstraints(0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.EAST,
1000					GridBagConstraints.NONE, new Insets(0, 5, 0, 0), 0, 0));
1001				((JSpinner.DefaultEditor)padLeftSpinner.getEditor()).getTextField().setColumns(2);
1002			}
1003			{
1004				padBottomSpinner = new JSpinner(new SpinnerNumberModel(1, 0, 999, 1));
1005				paddingPanel.add(padBottomSpinner, new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
1006					GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
1007				((JSpinner.DefaultEditor)padBottomSpinner.getEditor()).getTextField().setColumns(2);
1008			}
1009			{
1010				JPanel advancePanel = new JPanel();
1011				FlowLayout advancePanelLayout = new FlowLayout();
1012				advancePanel.setLayout(advancePanelLayout);
1013				paddingPanel.add(advancePanel, new GridBagConstraints(0, 4, 3, 1, 1.0, 0.0, GridBagConstraints.CENTER,
1014					GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
1015				{
1016					advancePanel.add(new JLabel("X:"));
1017				}
1018				{
1019					padAdvanceXSpinner = new JSpinner(new SpinnerNumberModel(-2, -999, 999, 1));
1020					advancePanel.add(padAdvanceXSpinner);
1021					((JSpinner.DefaultEditor)padAdvanceXSpinner.getEditor()).getTextField().setColumns(2);
1022				}
1023				{
1024					advancePanel.add(new JLabel("Y:"));
1025				}
1026				{
1027					padAdvanceYSpinner = new JSpinner(new SpinnerNumberModel(-2, -999, 999, 1));
1028					advancePanel.add(padAdvanceYSpinner);
1029					((JSpinner.DefaultEditor)padAdvanceYSpinner.getEditor()).getTextField().setColumns(2);
1030				}
1031			}
1032		}
1033		{
1034			effectsPanel = new JPanel();
1035			effectsPanel.setLayout(new GridBagLayout());
1036			rightSidePanel.add(effectsPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
1037				GridBagConstraints.BOTH, new Insets(5, 0, 5, 5), 0, 0));
1038			effectsPanel.setBorder(BorderFactory.createTitledBorder("Effects"));
1039			effectsPanel.setMinimumSize(new Dimension(210, 1));
1040			{
1041				effectsScroll = new JScrollPane();
1042				effectsPanel.add(effectsScroll, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.NORTH,
1043					GridBagConstraints.HORIZONTAL, new Insets(0, 5, 5, 5), 0, 0));
1044				{
1045					effectsListModel = new DefaultComboBoxModel();
1046					effectsList = new JList();
1047					effectsScroll.setViewportView(effectsList);
1048					effectsList.setModel(effectsListModel);
1049					effectsList.setVisibleRowCount(7);
1050					effectsScroll.setMinimumSize(effectsList.getPreferredScrollableViewportSize());
1051				}
1052			}
1053			{
1054				addEffectButton = new JButton("Add");
1055				effectsPanel.add(addEffectButton, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
1056					GridBagConstraints.NONE, new Insets(0, 5, 6, 5), 0, 0));
1057				addEffectButton.setEnabled(false);
1058			}
1059			{
1060				appliedEffectsScroll = new JScrollPane();
1061				effectsPanel.add(appliedEffectsScroll, new GridBagConstraints(1, 3, 1, 1, 1.0, 1.0, GridBagConstraints.NORTH,
1062					GridBagConstraints.BOTH, new Insets(0, 0, 5, 0), 0, 0));
1063				appliedEffectsScroll.setBorder(new EmptyBorder(0, 0, 0, 0));
1064				appliedEffectsScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
1065				{
1066					JPanel panel = new JPanel();
1067					panel.setLayout(new GridBagLayout());
1068					appliedEffectsScroll.setViewportView(panel);
1069					{
1070						appliedEffectsPanel = new JPanel();
1071						appliedEffectsPanel.setLayout(new GridBagLayout());
1072						panel.add(appliedEffectsPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.NORTH,
1073							GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
1074						appliedEffectsPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, java.awt.Color.black));
1075					}
1076				}
1077			}
1078		}
1079	}
1080
1081	private void initializeMenus () {
1082		{
1083			JMenuBar menuBar = new JMenuBar();
1084			setJMenuBar(menuBar);
1085			{
1086				JMenu fileMenu = new JMenu();
1087				menuBar.add(fileMenu);
1088				fileMenu.setText("File");
1089				fileMenu.setMnemonic(KeyEvent.VK_F);
1090				{
1091					openMenuItem = new JMenuItem("Open Hiero settings file...");
1092					openMenuItem.setMnemonic(KeyEvent.VK_O);
1093					openMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK));
1094					fileMenu.add(openMenuItem);
1095				}
1096				{
1097					saveMenuItem = new JMenuItem("Save Hiero settings file...");
1098					saveMenuItem.setMnemonic(KeyEvent.VK_S);
1099					saveMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK));
1100					fileMenu.add(saveMenuItem);
1101				}
1102				fileMenu.addSeparator();
1103				{
1104					saveBMFontMenuItem = new JMenuItem("Save BMFont files (text)...");
1105					saveBMFontMenuItem.setMnemonic(KeyEvent.VK_B);
1106					saveBMFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.CTRL_MASK));
1107					fileMenu.add(saveBMFontMenuItem);
1108				}
1109				fileMenu.addSeparator();
1110				{
1111					exitMenuItem = new JMenuItem("Exit");
1112					exitMenuItem.setMnemonic(KeyEvent.VK_X);
1113					fileMenu.add(exitMenuItem);
1114				}
1115			}
1116		}
1117	}
1118
1119	static Icon getColorIcon (java.awt.Color color) {
1120		BufferedImage image = new BufferedImage(32, 16, BufferedImage.TYPE_INT_RGB);
1121		java.awt.Graphics g = image.getGraphics();
1122		g.setColor(color);
1123		g.fillRect(1, 1, 30, 14);
1124		g.setColor(java.awt.Color.black);
1125		g.drawRect(0, 0, 31, 15);
1126		return new ImageIcon(image);
1127	}
1128
1129	private class EffectPanel extends JPanel {
1130		final java.awt.Color selectedColor = new java.awt.Color(0xb1d2e9);
1131
1132		final ConfigurableEffect effect;
1133		List values;
1134
1135		JButton upButton;
1136		JButton downButton;
1137		JButton deleteButton;
1138		private JPanel valuesPanel;
1139		JLabel nameLabel;
1140
1141		GridBagConstraints constrains = new GridBagConstraints(0, -1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,
1142			GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0);
1143
1144		EffectPanel (final ConfigurableEffect effect) {
1145			this.effect = effect;
1146			effectPanels.add(this);
1147			effectsList.getListSelectionListeners()[0].valueChanged(null);
1148
1149			setLayout(new GridBagLayout());
1150			setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, java.awt.Color.black));
1151			appliedEffectsPanel.add(this, constrains);
1152			{
1153				JPanel titlePanel = new JPanel();
1154				titlePanel.setLayout(new LayoutManager() {
1155					public void removeLayoutComponent (Component comp) {
1156					}
1157
1158					public Dimension preferredLayoutSize (Container parent) {
1159						return null;
1160					}
1161
1162					public Dimension minimumLayoutSize (Container parent) {
1163						return null;
1164					}
1165
1166					public void layoutContainer (Container parent) {
1167						Dimension buttonSize = upButton.getPreferredSize();
1168						int upButtonX = getWidth() - buttonSize.width * 3 - 6 - 5;
1169						upButton.setBounds(upButtonX, 0, buttonSize.width, buttonSize.height);
1170						downButton.setBounds(getWidth() - buttonSize.width * 2 - 3 - 5, 0, buttonSize.width, buttonSize.height);
1171						deleteButton.setBounds(getWidth() - buttonSize.width - 5, 0, buttonSize.width, buttonSize.height);
1172
1173						Dimension labelSize = nameLabel.getPreferredSize();
1174						nameLabel.setBounds(5, buttonSize.height / 2 - labelSize.height / 2, getWidth() - 5, labelSize.height);
1175					}
1176
1177					public void addLayoutComponent (String name, Component comp) {
1178					}
1179				});
1180				{
1181					upButton = new JButton();
1182					titlePanel.add(upButton);
1183					upButton.setText("Up");
1184					upButton.setMargin(new Insets(0, 0, 0, 0));
1185					Font font = upButton.getFont();
1186					upButton.setFont(new Font(font.getName(), font.getStyle(), font.getSize() - 2));
1187				}
1188				{
1189					downButton = new JButton();
1190					titlePanel.add(downButton);
1191					downButton.setText("Down");
1192					downButton.setMargin(new Insets(0, 0, 0, 0));
1193					Font font = downButton.getFont();
1194					downButton.setFont(new Font(font.getName(), font.getStyle(), font.getSize() - 2));
1195				}
1196				{
1197					deleteButton = new JButton();
1198					titlePanel.add(deleteButton);
1199					deleteButton.setText("X");
1200					deleteButton.setMargin(new Insets(0, 0, 0, 0));
1201					Font font = deleteButton.getFont();
1202					deleteButton.setFont(new Font(font.getName(), font.getStyle(), font.getSize() - 2));
1203				}
1204				{
1205					nameLabel = new JLabel(effect.toString());
1206					titlePanel.add(nameLabel);
1207					Font font = nameLabel.getFont();
1208					nameLabel.setFont(new Font(font.getName(), Font.BOLD, font.getSize()));
1209				}
1210				titlePanel.setPreferredSize(
1211					new Dimension(0, Math.max(nameLabel.getPreferredSize().height, deleteButton.getPreferredSize().height)));
1212				add(titlePanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
1213					new Insets(5, 0, 0, 5), 0, 0));
1214				titlePanel.setOpaque(false);
1215			}
1216			{
1217				valuesPanel = new JPanel();
1218				valuesPanel.setOpaque(false);
1219				valuesPanel.setLayout(new GridBagLayout());
1220				add(valuesPanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,
1221					GridBagConstraints.HORIZONTAL, new Insets(0, 10, 5, 0), 0, 0));
1222			}
1223
1224			upButton.addActionListener(new ActionListener() {
1225				public void actionPerformed (ActionEvent evt) {
1226					int currentIndex = effectPanels.indexOf(EffectPanel.this);
1227					if (currentIndex > 0) {
1228						moveEffect(currentIndex - 1);
1229						updateFont();
1230						updateUpDownButtons();
1231					}
1232				}
1233			});
1234
1235			downButton.addActionListener(new ActionListener() {
1236				public void actionPerformed (ActionEvent evt) {
1237					int currentIndex = effectPanels.indexOf(EffectPanel.this);
1238					if (currentIndex < effectPanels.size() - 1) {
1239						moveEffect(currentIndex + 1);
1240						updateFont();
1241						updateUpDownButtons();
1242					}
1243				}
1244			});
1245
1246			deleteButton.addActionListener(new ActionListener() {
1247				public void actionPerformed (ActionEvent evt) {
1248					remove();
1249					updateFont();
1250					updateUpDownButtons();
1251				}
1252			});
1253
1254			updateValues();
1255			updateFont();
1256			updateUpDownButtons();
1257		}
1258
1259		public void remove () {
1260			effectPanels.remove(this);
1261			appliedEffectsPanel.remove(EffectPanel.this);
1262			getContentPane().validate();
1263			effectsList.getListSelectionListeners()[0].valueChanged(null);
1264		}
1265
1266		public void updateValues () {
1267			prefs.put("foreground", EffectUtil.toString(colorEffect.getColor()));
1268			valuesPanel.removeAll();
1269			values = effect.getValues();
1270			for (Iterator iter = values.iterator(); iter.hasNext();)
1271				addValue((Value)iter.next());
1272		}
1273
1274		public void updateUpDownButtons () {
1275			for (int index = 0; index < effectPanels.size(); index++) {
1276				EffectPanel effectPanel = effectPanels.get(index);
1277				if (index == 0) {
1278					effectPanel.upButton.setEnabled(false);
1279				} else {
1280					effectPanel.upButton.setEnabled(true);
1281				}
1282
1283				if (index == effectPanels.size() - 1) {
1284					effectPanel.downButton.setEnabled(false);
1285				} else {
1286					effectPanel.downButton.setEnabled(true);
1287				}
1288			}
1289		}
1290
1291		public void moveEffect (int newIndex) {
1292			appliedEffectsPanel.remove(this);
1293			effectPanels.remove(this);
1294			appliedEffectsPanel.add(this, constrains, newIndex);
1295			effectPanels.add(newIndex, this);
1296		}
1297
1298		public void addValue (final Value value) {
1299			JLabel valueNameLabel = new JLabel(value.getName() + ":");
1300			valuesPanel.add(valueNameLabel, new GridBagConstraints(0, -1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
1301				GridBagConstraints.NONE, new Insets(0, 0, 0, 5), 0, 0));
1302
1303			final JLabel valueValueLabel = new JLabel();
1304			valuesPanel.add(valueValueLabel, new GridBagConstraints(1, -1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST,
1305				GridBagConstraints.BOTH, new Insets(0, 0, 0, 5), 0, 0));
1306			valueValueLabel.setOpaque(true);
1307			if (value.getObject() instanceof java.awt.Color)
1308				valueValueLabel.setIcon(getColorIcon((java.awt.Color)value.getObject()));
1309			else
1310				valueValueLabel.setText(value.toString());
1311
1312			valueValueLabel.addMouseListener(new MouseAdapter() {
1313				public void mouseEntered (MouseEvent evt) {
1314					valueValueLabel.setBackground(selectedColor);
1315				}
1316
1317				public void mouseExited (MouseEvent evt) {
1318					valueValueLabel.setBackground(null);
1319				}
1320
1321				public void mouseClicked (MouseEvent evt) {
1322					Object oldObject = value.getObject();
1323					value.showDialog();
1324					if (!value.getObject().equals(oldObject)) {
1325						effect.setValues(values);
1326						updateValues();
1327						updateFont();
1328					}
1329				}
1330			});
1331		}
1332
1333		public ConfigurableEffect getEffect () {
1334			return effect;
1335		}
1336
1337		public boolean equals (Object obj) {
1338			if (this == obj) return true;
1339			if (obj == null) return false;
1340			if (getClass() != obj.getClass()) return false;
1341			final EffectPanel other = (EffectPanel)obj;
1342			if (effect == null) {
1343				if (other.effect != null) return false;
1344			} else if (!effect.equals(other.effect)) return false;
1345			return true;
1346		}
1347
1348	}
1349
1350	static private class Splash extends JWindow {
1351		final int minMillis;
1352		final long startTime;
1353
1354		public Splash (Frame frame, String imageFile, int minMillis) {
1355			super(frame);
1356			this.minMillis = minMillis;
1357			getContentPane().add(new JLabel(new ImageIcon(Splash.class.getResource(imageFile))), BorderLayout.CENTER);
1358			pack();
1359			setLocationRelativeTo(null);
1360			setVisible(true);
1361			startTime = System.currentTimeMillis();
1362		}
1363
1364		public void close () {
1365			final long endTime = System.currentTimeMillis();
1366			new Thread(new Runnable() {
1367				public void run () {
1368					if (endTime - startTime < minMillis) {
1369						addMouseListener(new MouseAdapter() {
1370							public void mousePressed (MouseEvent evt) {
1371								dispose();
1372							}
1373						});
1374						try {
1375							Thread.sleep(minMillis - (endTime - startTime));
1376						} catch (InterruptedException ignored) {
1377						}
1378					}
1379					EventQueue.invokeLater(new Runnable() {
1380						public void run () {
1381							dispose();
1382						}
1383					});
1384				}
1385			}, "Splash").start();
1386		}
1387	}
1388
1389	class Renderer extends ApplicationAdapter {
1390		SpriteBatch batch;
1391		int width, height;
1392
1393		public void create () {
1394			glClearColor(0, 0, 0, 0);
1395			glClearDepth(1);
1396			glDisable(GL_LIGHTING);
1397
1398			batch = new SpriteBatch();
1399
1400			sampleNeheButton.doClick();
1401		}
1402
1403		public void resize (int width, int height) {
1404			this.width = width;
1405			this.height = height;
1406			batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height);
1407		}
1408
1409		public void render () {
1410			int viewWidth = Gdx.graphics.getWidth();
1411			int viewHeight = Gdx.graphics.getHeight();
1412
1413			if (sampleTextRadio.isSelected()) {
1414				GL11.glClearColor(renderingBackgroundColor.r, renderingBackgroundColor.g, renderingBackgroundColor.b,
1415					renderingBackgroundColor.a);
1416				GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
1417			} else {
1418				GL11.glClearColor(1, 1, 1, 1);
1419				GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
1420			}
1421
1422			String sampleText = sampleTextPane.getText();
1423
1424			glEnable(GL_TEXTURE_2D);
1425			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
1426			glEnableClientState(GL_VERTEX_ARRAY);
1427
1428			glEnable(GL_BLEND);
1429			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1430
1431			glViewport(0, 0, width, height);
1432			glScissor(0, 0, width, height);
1433
1434			glMatrixMode(GL_PROJECTION);
1435			glLoadIdentity();
1436			glOrtho(0, width, height, 0, 1, -1);
1437			glMatrixMode(GL_MODELVIEW);
1438			glLoadIdentity();
1439
1440			unicodeFont.addGlyphs(sampleText);
1441
1442			if (!unicodeFont.getEffects().isEmpty() && unicodeFont.loadGlyphs(64)) {
1443				glyphPageComboModel.removeAllElements();
1444				int pageCount = unicodeFont.getGlyphPages().size();
1445				int glyphCount = 0;
1446				for (int i = 0; i < pageCount; i++) {
1447					glyphPageComboModel.addElement("Page " + (i + 1));
1448					glyphCount += ((GlyphPage)unicodeFont.getGlyphPages().get(i)).getGlyphs().size();
1449				}
1450				glyphPagesTotalLabel.setText(String.valueOf(pageCount));
1451				glyphsTotalLabel.setText(String.valueOf(glyphCount));
1452			}
1453
1454			if (sampleTextRadio.isSelected()) {
1455				int offset = unicodeFont.getYOffset(sampleText);
1456				if (offset > 0) offset = 0;
1457				unicodeFont.drawString(10, 12, sampleText, Color.WHITE, 0, sampleText.length());
1458			} else {
1459				// GL11.glColor4f(renderingBackgroundColor.r, renderingBackgroundColor.g, renderingBackgroundColor.b,
1460				// renderingBackgroundColor.a);
1461				// fillRect(0, 0, unicodeFont.getGlyphPageWidth() + 2, unicodeFont.getGlyphPageHeight() + 2);
1462				int index = glyphPageCombo.getSelectedIndex();
1463				List pages = unicodeFont.getGlyphPages();
1464				if (index >= 0 && index < pages.size()) {
1465					Texture texture = ((GlyphPage)pages.get(glyphPageCombo.getSelectedIndex())).getTexture();
1466
1467					glDisable(GL_TEXTURE_2D);
1468					glColor4f(renderingBackgroundColor.r, renderingBackgroundColor.g, renderingBackgroundColor.b,
1469						renderingBackgroundColor.a);
1470					glBegin(GL_QUADS);
1471					glVertex3f(0, 0, 0);
1472					glVertex3f(0, texture.getHeight(), 0);
1473					glVertex3f(texture.getWidth(), texture.getHeight(), 0);
1474					glVertex3f(texture.getWidth(), 0, 0);
1475					glEnd();
1476					glEnable(GL_TEXTURE_2D);
1477
1478					texture.bind();
1479					glColor4f(1, 1, 1, 1);
1480					glBegin(GL_QUADS);
1481					glTexCoord2f(0, 0);
1482					glVertex3f(0, 0, 0);
1483
1484					glTexCoord2f(0, 1);
1485					glVertex3f(0, texture.getHeight(), 0);
1486
1487					glTexCoord2f(1, 1);
1488					glVertex3f(texture.getWidth(), texture.getHeight(), 0);
1489
1490					glTexCoord2f(1, 0);
1491					glVertex3f(texture.getWidth(), 0, 0);
1492					glEnd();
1493				}
1494			}
1495
1496			glDisable(GL_TEXTURE_2D);
1497			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
1498			glDisableClientState(GL_VERTEX_ARRAY);
1499
1500			if (saveBmFontFile != null) {
1501				try {
1502					BMFontUtil bmFont = new BMFontUtil(unicodeFont);
1503					bmFont.save(saveBmFontFile);
1504
1505					if (batchMode) {
1506						exit(0);
1507					}
1508				} catch (Throwable ex) {
1509					System.out.println("Error saving BMFont files: " + saveBmFontFile.getAbsolutePath());
1510					ex.printStackTrace();
1511				} finally {
1512					saveBmFontFile = null;
1513				}
1514			}
1515		}
1516
1517	}
1518
1519	static final String NEHE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n" //
1520		+ "abcdefghijklmnopqrstuvwxyz\n1234567890 \n" //
1521		+ "\"!`?'.,;:()[]{}<>|/@\\^$-%+=#_&~*\u0000\u007F";
1522	static public final String EXTENDED_CHARS;
1523
1524	static {
1525		StringBuilder buffer = new StringBuilder();
1526		int i = 0;
1527		for (int c : new int[] {0, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
1528			57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,
1529			87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
1530			114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170,
1531			171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
1532			195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218,
1533			219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242,
1534			243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266,
1535			267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290,
1536			291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314,
1537			315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338,
1538			339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362,
1539			363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 884, 885, 890,
1540			891, 892, 893, 894, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921,
1541			922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946,
1542			947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970,
1543			971, 972, 973, 974, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995,
1544			996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016,
1545			1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036,
1546			1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056,
1547			1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076,
1548			1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096,
1549			1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116,
1550			1117, 1118, 1119, 1120, 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1136,
1551			1137, 1138, 1139, 1140, 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1148, 1149, 1150, 1151, 1152, 1153, 1154, 1155, 1156,
1552			1157, 1158, 1159, 1160, 1161, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1176,
1553			1177, 1178, 1179, 1180, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189, 1190, 1191, 1192, 1193, 1194, 1195, 1196,
1554			1197, 1198, 1199, 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212, 1213, 1214, 1215, 1216,
1555			1217, 1218, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226, 1227, 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1236,
1556			1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246, 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256,
1557			1257, 1258, 1259, 1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268, 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276,
1558			1277, 1278, 1279, 1280, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296,
1559			1297, 1298, 1299, 1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316,
1560			1317, 1318, 1319, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8203, 8204, 8205, 8206, 8207, 8210,
1561			8211, 8212, 8213, 8214, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8223, 8224, 8225, 8226, 8230, 8234, 8235, 8236,
1562			8237, 8238, 8239, 8240, 8242, 8243, 8244, 8249, 8250, 8252, 8254, 8260, 8286, 8298, 8299, 8300, 8301, 8302, 8303, 8352,
1563			8353, 8354, 8355, 8356, 8357, 8358, 8359, 8360, 8361, 8363, 8364, 8365, 8366, 8367, 8368, 8369, 8370, 8371, 8372, 8373,
1564			8377, 8378, 11360, 11361, 11362, 11363, 11364, 11365, 11366, 11367, 11368, 11369, 11370, 11371, 11372, 11373, 11377,
1565			11378, 11379, 11380, 11381, 11382, 11383}) {
1566			i++;
1567			if (i > 26) {
1568				i = 0;
1569				buffer.append("\r\n");
1570			}
1571			buffer.append((char)c);
1572		}
1573		EXTENDED_CHARS = buffer.toString();
1574	}
1575
1576	public static void main (final String[] args) throws Exception {
1577		SwingUtilities.invokeLater(new Runnable() {
1578			public void run () {
1579				new Hiero(args);
1580			}
1581		});
1582	}
1583}
1584