TextureWrap vWrap) {\r
Pixmap pixmap = newPixmap(file);\r
if (!isPowerOfTwo(pixmap.getHeight()) || !isPowerOfTwo(pixmap.getWidth()))\r
- throw new GdxRuntimeException("Texture dimensions must be a power of two");\r
+ throw new GdxRuntimeException("Texture dimensions must be a power of two: " + file);\r
\r
return new LwjglTexture((BufferedImage)pixmap.getNativePixmap(), minFilter, magFilter, uWrap, vWrap, false);\r
}\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+ <classpathentry excluding="**/.svn/*" kind="src" path="src"/>\r
+ <classpathentry excluding="**/.svn/*" kind="src" path="data"/>\r
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>\r
+ <classpathentry combineaccessrules="false" kind="src" path="/gdx"/>\r
+ <classpathentry combineaccessrules="false" kind="src" path="/gdx-backend-lwjgl"/>\r
+ <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+ <name>hiero</name>\r
+ <comment></comment>\r
+ <projects>\r
+ </projects>\r
+ <buildSpec>\r
+ <buildCommand>\r
+ <name>org.eclipse.jdt.core.javabuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ </buildSpec>\r
+ <natures>\r
+ <nature>org.eclipse.jdt.core.javanature</nature>\r
+ </natures>\r
+</projectDescription>\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero;\r
+\r
+import java.awt.Font;\r
+import java.awt.Graphics;\r
+import java.awt.Image;\r
+import java.awt.font.GlyphMetrics;\r
+import java.awt.font.GlyphVector;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.image.AffineTransformOp;\r
+import java.awt.image.BufferedImage;\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.PrintStream;\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+import javax.imageio.ImageIO;\r
+import javax.swing.ImageIcon;\r
+\r
+import com.badlogic.gdx.Files.FileType;\r
+import com.badlogic.gdx.Gdx;\r
+import com.badlogic.gdx.hiero.unicodefont.Glyph;\r
+import com.badlogic.gdx.hiero.unicodefont.GlyphPage;\r
+import com.badlogic.gdx.hiero.unicodefont.UnicodeFont;\r
+\r
+/**\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class BMFontUtil {\r
+ private final UnicodeFont unicodeFont;\r
+\r
+ public BMFontUtil (UnicodeFont unicodeFont) {\r
+ this.unicodeFont = unicodeFont;\r
+ }\r
+\r
+ public void save (File outputBMFontFile) throws IOException {\r
+ File outputDir = outputBMFontFile.getParentFile();\r
+ String outputName = outputBMFontFile.getName();\r
+ if (outputName.endsWith(".fnt")) outputName = outputName.substring(0, outputName.length() - 4);\r
+\r
+ unicodeFont.loadGlyphs();\r
+\r
+ PrintStream out = new PrintStream(new FileOutputStream(new File(outputDir, outputName + ".fnt")));\r
+ Font font = unicodeFont.getFont();\r
+ int pageWidth = unicodeFont.getGlyphPageWidth();\r
+ int pageHeight = unicodeFont.getGlyphPageHeight();\r
+ out.println("info face=\"" + font.getFontName() + "\" size=" + font.getSize() + " bold=" + (font.isBold() ? 1 : 0)\r
+ + " italic=" + (font.isItalic() ? 1 : 0)\r
+ + " charset=\"\" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1");\r
+ out.println("common lineHeight=" + unicodeFont.getLineHeight() + " base=" + unicodeFont.getAscent() + " scaleW="\r
+ + pageWidth + " scaleH=" + pageHeight + " pages=" + unicodeFont.getGlyphPages().size() + " packed=0");\r
+\r
+ int pageIndex = 0, glyphCount = 0;\r
+ for (Iterator pageIter = unicodeFont.getGlyphPages().iterator(); pageIter.hasNext();) {\r
+ GlyphPage page = (GlyphPage)pageIter.next();\r
+ String fileName;\r
+ if (pageIndex == 0 && !pageIter.hasNext())\r
+ fileName = outputName + ".png";\r
+ else\r
+ fileName = outputName + (pageIndex + 1) + ".png";\r
+ out.println("page id=" + pageIndex + " file=\"" + fileName + "\"");\r
+ glyphCount += page.getGlyphs().size();\r
+ pageIndex++;\r
+ }\r
+\r
+ out.println("chars count=" + glyphCount);\r
+\r
+ // Always output space entry (codepoint 32).\r
+ int[] glyphMetrics = getGlyphMetrics(font, 32);\r
+ int xAdvance = glyphMetrics[1];\r
+ out.println("char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=" + unicodeFont.getAscent()\r
+ + " xadvance=" + xAdvance + " page=0 chnl=0 ");\r
+\r
+ pageIndex = 0;\r
+ List allGlyphs = new ArrayList(512);\r
+ for (Iterator pageIter = unicodeFont.getGlyphPages().iterator(); pageIter.hasNext();) {\r
+ GlyphPage page = (GlyphPage)pageIter.next();\r
+ for (Iterator glyphIter = page.getGlyphs().iterator(); glyphIter.hasNext();) {\r
+ Glyph glyph = (Glyph)glyphIter.next();\r
+\r
+ glyphMetrics = getGlyphMetrics(font, glyph.getCodePoint());\r
+ int xOffset = glyphMetrics[0];\r
+ xAdvance = glyphMetrics[1];\r
+\r
+ out.println("char id=" + glyph.getCodePoint() + " " + "x=" + (int)(glyph.getU() * pageWidth) + " y="\r
+ + (int)(glyph.getV() * pageHeight) + " width=" + glyph.getWidth() + " height=" + glyph.getHeight()\r
+ + " xoffset=" + xOffset + " yoffset=" + glyph.getYOffset() + " xadvance=" + xAdvance + " page="\r
+ + pageIndex + " chnl=0 ");\r
+ }\r
+ allGlyphs.addAll(page.getGlyphs());\r
+ pageIndex++;\r
+ }\r
+\r
+ String ttfFileRef = unicodeFont.getFontFile();\r
+ if (ttfFileRef == null)\r
+ System.out.println("Kerning information could not be output because a TTF font file was not specified.");\r
+ else {\r
+ Kerning kerning = new Kerning();\r
+ try {\r
+ kerning.load(Gdx.files.readFile(ttfFileRef, FileType.Internal), font.getSize());\r
+ } catch (IOException ex) {\r
+ System.out.println("Unable to read kerning information from font: " + ttfFileRef);\r
+ }\r
+\r
+ Map glyphCodeToCodePoint = new HashMap();\r
+ for (Iterator iter = allGlyphs.iterator(); iter.hasNext();) {\r
+ Glyph glyph = (Glyph)iter.next();\r
+ glyphCodeToCodePoint.put(new Integer(getGlyphCode(font, glyph.getCodePoint())), new Integer(glyph.getCodePoint()));\r
+ }\r
+\r
+ List kernings = new ArrayList(256);\r
+ class KerningPair {\r
+ public int firstCodePoint, secondCodePoint, offset;\r
+ }\r
+ for (Iterator iter1 = allGlyphs.iterator(); iter1.hasNext();) {\r
+ Glyph firstGlyph = (Glyph)iter1.next();\r
+ int firstGlyphCode = getGlyphCode(font, firstGlyph.getCodePoint());\r
+ int[] values = kerning.getValues(firstGlyphCode);\r
+ if (values == null) continue;\r
+ for (int i = 0; i < values.length; i++) {\r
+ Integer secondCodePoint = (Integer)glyphCodeToCodePoint.get(new Integer(values[i] & 0xffff));\r
+ if (secondCodePoint == null) continue; // We may not be outputting the second character.\r
+ int offset = values[i] >> 16;\r
+ KerningPair pair = new KerningPair();\r
+ pair.firstCodePoint = firstGlyph.getCodePoint();\r
+ pair.secondCodePoint = secondCodePoint.intValue();\r
+ pair.offset = offset;\r
+ kernings.add(pair);\r
+ }\r
+ }\r
+ out.println("kernings count=" + kerning.getCount());\r
+ for (Iterator iter = kernings.iterator(); iter.hasNext();) {\r
+ KerningPair pair = (KerningPair)iter.next();\r
+ out.println("kerning first=" + pair.firstCodePoint + " second=" + pair.secondCodePoint + " amount=" + pair.offset);\r
+ }\r
+ }\r
+ out.close();\r
+\r
+ pageIndex = 0;\r
+ for (Iterator pageIter = unicodeFont.getGlyphPages().iterator(); pageIter.hasNext();) {\r
+ GlyphPage page = (GlyphPage)pageIter.next();\r
+ String fileName;\r
+ if (pageIndex == 0 && !pageIter.hasNext())\r
+ fileName = outputName + ".png";\r
+ else\r
+ fileName = outputName + (pageIndex + 1) + ".png";\r
+ File imageOutputFile = new File(outputDir, fileName);\r
+ FileOutputStream imageOutput = new FileOutputStream(imageOutputFile);\r
+ try {\r
+ // BOZO - Save texture to PNG.\r
+ // saveImage(page.getTexture(), "png", imageOutput, true);\r
+ } finally {\r
+ imageOutput.close();\r
+ }\r
+ // Flip output image.\r
+ Image image = new ImageIcon(imageOutputFile.getAbsolutePath()).getImage();\r
+ BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);\r
+ Graphics g = bufferedImage.getGraphics();\r
+ g.drawImage(image, 0, 0, null);\r
+ AffineTransform tx = AffineTransform.getScaleInstance(1, -1);\r
+ tx.translate(0, -image.getHeight(null));\r
+ AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);\r
+ bufferedImage = op.filter(bufferedImage, null);\r
+ ImageIO.write(bufferedImage, "png", imageOutputFile);\r
+\r
+ pageIndex++;\r
+ }\r
+ }\r
+\r
+ private int getGlyphCode (Font font, int codePoint) {\r
+ char[] chars = Character.toChars(codePoint);\r
+ GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);\r
+ return vector.getGlyphCode(0);\r
+ }\r
+\r
+ private int[] getGlyphMetrics (Font font, int codePoint) {\r
+ // xOffset and xAdvance will be incorrect for unicode characters such as combining marks or non-spacing characters\r
+ // (eg Pnujabi's "\u0A1C\u0A47") that require the context of surrounding glyphs to determine spacing, but thisis the\r
+ // best we can do with the BMFont format.\r
+ char[] chars = Character.toChars(codePoint);\r
+ GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);\r
+ GlyphMetrics metrics = vector.getGlyphMetrics(0);\r
+ int xOffset = vector.getGlyphPixelBounds(0, GlyphPage.renderContext, 0.5f, 0).x - unicodeFont.getPaddingLeft();\r
+ int xAdvance = (int)(metrics.getAdvanceX() + unicodeFont.getPaddingAdvanceX() + unicodeFont.getPaddingLeft() + unicodeFont\r
+ .getPaddingRight());\r
+ return new int[] {xOffset, xAdvance};\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero;\r
+\r
+import static org.lwjgl.opengl.GL11.*;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Canvas;\r
+import java.awt.Component;\r
+import java.awt.Container;\r
+import java.awt.Dimension;\r
+import java.awt.EventQueue;\r
+import java.awt.FileDialog;\r
+import java.awt.FlowLayout;\r
+import java.awt.Font;\r
+import java.awt.Frame;\r
+import java.awt.GraphicsEnvironment;\r
+import java.awt.GridBagConstraints;\r
+import java.awt.GridBagLayout;\r
+import java.awt.Insets;\r
+import java.awt.LayoutManager;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.awt.event.KeyAdapter;\r
+import java.awt.event.KeyEvent;\r
+import java.awt.event.MouseAdapter;\r
+import java.awt.event.MouseEvent;\r
+import java.awt.event.WindowAdapter;\r
+import java.awt.event.WindowEvent;\r
+import java.awt.image.BufferedImage;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.prefs.Preferences;\r
+\r
+import javax.swing.BorderFactory;\r
+import javax.swing.ButtonGroup;\r
+import javax.swing.DefaultComboBoxModel;\r
+import javax.swing.Icon;\r
+import javax.swing.ImageIcon;\r
+import javax.swing.JButton;\r
+import javax.swing.JCheckBox;\r
+import javax.swing.JColorChooser;\r
+import javax.swing.JComboBox;\r
+import javax.swing.JFormattedTextField;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JList;\r
+import javax.swing.JMenu;\r
+import javax.swing.JMenuBar;\r
+import javax.swing.JMenuItem;\r
+import javax.swing.JPanel;\r
+import javax.swing.JRadioButton;\r
+import javax.swing.JScrollPane;\r
+import javax.swing.JSpinner;\r
+import javax.swing.JTextField;\r
+import javax.swing.JTextPane;\r
+import javax.swing.JWindow;\r
+import javax.swing.KeyStroke;\r
+import javax.swing.ScrollPaneConstants;\r
+import javax.swing.SpinnerNumberModel;\r
+import javax.swing.UIManager;\r
+import javax.swing.UIManager.LookAndFeelInfo;\r
+import javax.swing.border.EmptyBorder;\r
+import javax.swing.event.ChangeEvent;\r
+import javax.swing.event.ChangeListener;\r
+import javax.swing.event.DocumentEvent;\r
+import javax.swing.event.DocumentListener;\r
+import javax.swing.event.ListSelectionEvent;\r
+import javax.swing.event.ListSelectionListener;\r
+\r
+import org.lwjgl.LWJGLException;\r
+import org.lwjgl.opengl.Display;\r
+import org.lwjgl.opengl.GL11;\r
+\r
+import sun.rmi.runtime.Log;\r
+\r
+import com.badlogic.gdx.Gdx;\r
+import com.badlogic.gdx.RenderListener;\r
+import com.badlogic.gdx.backends.desktop.LwjglApplication;\r
+import com.badlogic.gdx.graphics.BitmapFont;\r
+import com.badlogic.gdx.graphics.Color;\r
+import com.badlogic.gdx.graphics.Texture;\r
+import com.badlogic.gdx.hiero.unicodefont.GlyphPage;\r
+import com.badlogic.gdx.hiero.unicodefont.HieroSettings;\r
+import com.badlogic.gdx.hiero.unicodefont.UnicodeFont;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.ColorEffect;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.ConfigurableEffect;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.ConfigurableEffect.Value;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.EffectUtil;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.GradientEffect;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.OutlineEffect;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.OutlineWobbleEffect;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.OutlineZigzagEffect;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.ShadowEffect;\r
+import com.badlogic.gdx.utils.GdxRuntimeException;\r
+\r
+/**\r
+ * A tool to visualize settings for {@link UnicodeFont} and to export BMFont files for use with {@link BitmapFont}.\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class Hiero extends JFrame {\r
+ static final String NEHE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n" //\r
+ + "abcdefghijklmnopqrstuvwxyz\n1234567890\n" //\r
+ + "\"!`?'.,;:()[]{}<>|/@\\^$-%+=#_&~*\u007F";\r
+\r
+ LwjglApplication app;\r
+ Canvas glCanvas;\r
+ volatile UnicodeFont newUnicodeFont;\r
+ UnicodeFont unicodeFont;\r
+ Color renderingBackgroundColor = Color.BLACK;\r
+ List effectPanels = new ArrayList();\r
+ Preferences prefs;\r
+ ColorEffect colorEffect;\r
+\r
+ JScrollPane appliedEffectsScroll;\r
+ JPanel appliedEffectsPanel;\r
+ JButton addEffectButton;\r
+ JTextPane sampleTextPane;\r
+ JSpinner padAdvanceXSpinner;\r
+ JList effectsList;\r
+ JPanel gamePanel;\r
+ JTextField fontFileText;\r
+ JRadioButton fontFileRadio;\r
+ JRadioButton systemFontRadio;\r
+ JSpinner padBottomSpinner;\r
+ JSpinner padLeftSpinner;\r
+ JSpinner padRightSpinner;\r
+ JSpinner padTopSpinner;\r
+ JList fontList;\r
+ JSpinner fontSizeSpinner;\r
+ DefaultComboBoxModel fontListModel;\r
+ JLabel backgroundColorLabel;\r
+ JButton browseButton;\r
+ JSpinner padAdvanceYSpinner;\r
+ JCheckBox italicCheckBox;\r
+ JCheckBox boldCheckBox;\r
+ JLabel glyphsTotalLabel;\r
+ JLabel glyphPagesTotalLabel;\r
+ JComboBox glyphPageHeightCombo;\r
+ JComboBox glyphPageWidthCombo;\r
+ JComboBox glyphPageCombo;\r
+ JPanel glyphCachePanel;\r
+ JRadioButton glyphCacheRadio;\r
+ JRadioButton sampleTextRadio;\r
+ DefaultComboBoxModel glyphPageComboModel;\r
+ JButton resetCacheButton;\r
+ JButton sampleAsciiButton;\r
+ JButton sampleNeheButton;\r
+ DefaultComboBoxModel effectsListModel;\r
+ JMenuItem openMenuItem;\r
+ JMenuItem saveMenuItem;\r
+ JMenuItem exitMenuItem;\r
+ JMenuItem saveBMFontMenuItem;\r
+ File saveBmFontFile;\r
+\r
+ public Hiero () {\r
+ super("Hiero v2.0 - Bitmap Font Tool");\r
+ Splash splash = new Splash(this, "/splash.jpg", 2000);\r
+ initialize();\r
+ splash.close();\r
+\r
+ prefs = Preferences.userNodeForPackage(Hiero.class);\r
+ java.awt.Color backgroundColor = EffectUtil.fromString(prefs.get("background", "000000"));\r
+ backgroundColorLabel.setIcon(getColorIcon(backgroundColor));\r
+ renderingBackgroundColor = new Color(backgroundColor.getRed() / 255f, backgroundColor.getGreen() / 255f,\r
+ backgroundColor.getBlue() / 255f, 1);\r
+ fontList.setSelectedValue(prefs.get("system.font", "Arial"), true);\r
+ fontFileText.setText(prefs.get("font.file", ""));\r
+\r
+ java.awt.Color foregroundColor = EffectUtil.fromString(prefs.get("foreground", "ffffff"));\r
+ colorEffect = new ColorEffect();\r
+ colorEffect.setColor(foregroundColor);\r
+ effectsListModel.addElement(colorEffect);\r
+ effectsListModel.addElement(new GradientEffect());\r
+ effectsListModel.addElement(new OutlineEffect());\r
+ effectsListModel.addElement(new OutlineWobbleEffect());\r
+ effectsListModel.addElement(new OutlineZigzagEffect());\r
+ effectsListModel.addElement(new ShadowEffect());\r
+ new EffectPanel(colorEffect);\r
+\r
+ gamePanel.add(glCanvas = new Canvas() {\r
+ private final Dimension minSize = new Dimension();\r
+\r
+ public final void addNotify () {\r
+ super.addNotify();\r
+ app = new LwjglApplication("Hiero", 200, 200, false) {\r
+ protected void setupDisplay () throws LWJGLException {\r
+ try {\r
+ Display.setParent(glCanvas);\r
+ } catch (LWJGLException ex) {\r
+ throw new GdxRuntimeException("Error setting display parent.", ex);\r
+ }\r
+ super.setupDisplay();\r
+ }\r
+ };\r
+ app.getGraphics().setRenderListener(new Renderer());\r
+ addWindowListener(new WindowAdapter() {\r
+ public void windowClosed (WindowEvent event) {\r
+ app.stop();\r
+ }\r
+ });\r
+ }\r
+\r
+ public Dimension getMinimumSize () {\r
+ return minSize;\r
+ }\r
+ });\r
+\r
+ setVisible(true);\r
+ }\r
+\r
+ void initialize () {\r
+ initializeComponents();\r
+ initializeMenus();\r
+ initializeEvents();\r
+\r
+ setSize(800, 600);\r
+ setLocationRelativeTo(null);\r
+ setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);\r
+\r
+ sampleNeheButton.doClick();\r
+ }\r
+\r
+ void updateFont () {\r
+ updateFont(false);\r
+ }\r
+\r
+ private void updateFont (boolean ignoreFileText) {\r
+ UnicodeFont unicodeFont;\r
+\r
+ int fontSize = ((Integer)fontSizeSpinner.getValue()).intValue();\r
+\r
+ File file = new File(fontFileText.getText());\r
+ if (!ignoreFileText && file.exists() && file.isFile()) {\r
+ // Load from file.\r
+ fontFileRadio.setSelected(true);\r
+ fontList.setEnabled(false);\r
+ systemFontRadio.setEnabled(false);\r
+ try {\r
+ unicodeFont = new UnicodeFont(fontFileText.getText(), fontSize, boldCheckBox.isSelected(),\r
+ italicCheckBox.isSelected());\r
+ } catch (Throwable ex) {\r
+ ex.printStackTrace();\r
+ updateFont(true);\r
+ return;\r
+ }\r
+ } else {\r
+ // Load from java.awt.Font (kerning not available!).\r
+ fontList.setEnabled(true);\r
+ systemFontRadio.setEnabled(true);\r
+ systemFontRadio.setSelected(true);\r
+ unicodeFont = new UnicodeFont(Font.decode((String)fontList.getSelectedValue()), fontSize, boldCheckBox.isSelected(),\r
+ italicCheckBox.isSelected());\r
+ }\r
+ unicodeFont.setPaddingTop(((Integer)padTopSpinner.getValue()).intValue());\r
+ unicodeFont.setPaddingRight(((Integer)padRightSpinner.getValue()).intValue());\r
+ unicodeFont.setPaddingBottom(((Integer)padBottomSpinner.getValue()).intValue());\r
+ unicodeFont.setPaddingLeft(((Integer)padLeftSpinner.getValue()).intValue());\r
+ unicodeFont.setPaddingAdvanceX(((Integer)padAdvanceXSpinner.getValue()).intValue());\r
+ unicodeFont.setPaddingAdvanceY(((Integer)padAdvanceYSpinner.getValue()).intValue());\r
+ unicodeFont.setGlyphPageWidth(((Integer)glyphPageWidthCombo.getSelectedItem()).intValue());\r
+ unicodeFont.setGlyphPageHeight(((Integer)glyphPageHeightCombo.getSelectedItem()).intValue());\r
+\r
+ for (Iterator iter = effectPanels.iterator(); iter.hasNext();) {\r
+ EffectPanel panel = (EffectPanel)iter.next();\r
+ unicodeFont.getEffects().add(panel.getEffect());\r
+ }\r
+\r
+ int size = sampleTextPane.getFont().getSize();\r
+ if (size < 14) size = 14;\r
+ sampleTextPane.setFont(unicodeFont.getFont().deriveFont((float)size));\r
+\r
+ this.newUnicodeFont = unicodeFont;\r
+ }\r
+\r
+ void save (File file) throws IOException {\r
+ HieroSettings settings = new HieroSettings();\r
+ settings.setFontSize(((Integer)fontSizeSpinner.getValue()).intValue());\r
+ settings.setBold(boldCheckBox.isSelected());\r
+ settings.setItalic(italicCheckBox.isSelected());\r
+ settings.setPaddingTop(((Integer)padTopSpinner.getValue()).intValue());\r
+ settings.setPaddingRight(((Integer)padRightSpinner.getValue()).intValue());\r
+ settings.setPaddingBottom(((Integer)padBottomSpinner.getValue()).intValue());\r
+ settings.setPaddingLeft(((Integer)padLeftSpinner.getValue()).intValue());\r
+ settings.setPaddingAdvanceX(((Integer)padAdvanceXSpinner.getValue()).intValue());\r
+ settings.setPaddingAdvanceY(((Integer)padAdvanceYSpinner.getValue()).intValue());\r
+ settings.setGlyphPageWidth(((Integer)glyphPageWidthCombo.getSelectedItem()).intValue());\r
+ settings.setGlyphPageHeight(((Integer)glyphPageHeightCombo.getSelectedItem()).intValue());\r
+ for (Iterator iter = effectPanels.iterator(); iter.hasNext();) {\r
+ EffectPanel panel = (EffectPanel)iter.next();\r
+ settings.getEffects().add(panel.getEffect());\r
+ }\r
+ settings.save(file);\r
+ }\r
+\r
+ void open (File file) {\r
+ EffectPanel[] panels = (EffectPanel[])effectPanels.toArray(new EffectPanel[effectPanels.size()]);\r
+ for (int i = 0; i < panels.length; i++)\r
+ panels[i].remove();\r
+\r
+ HieroSettings settings = new HieroSettings(file.getAbsolutePath());\r
+ fontSizeSpinner.setValue(new Integer(settings.getFontSize()));\r
+ boldCheckBox.setSelected(settings.isBold());\r
+ italicCheckBox.setSelected(settings.isItalic());\r
+ padTopSpinner.setValue(new Integer(settings.getPaddingTop()));\r
+ padRightSpinner.setValue(new Integer(settings.getPaddingRight()));\r
+ padBottomSpinner.setValue(new Integer(settings.getPaddingBottom()));\r
+ padLeftSpinner.setValue(new Integer(settings.getPaddingLeft()));\r
+ padAdvanceXSpinner.setValue(new Integer(settings.getPaddingAdvanceX()));\r
+ padAdvanceYSpinner.setValue(new Integer(settings.getPaddingAdvanceY()));\r
+ glyphPageWidthCombo.setSelectedItem(new Integer(settings.getGlyphPageWidth()));\r
+ glyphPageHeightCombo.setSelectedItem(new Integer(settings.getGlyphPageHeight()));\r
+ for (Iterator iter = settings.getEffects().iterator(); iter.hasNext();) {\r
+ ConfigurableEffect settingsEffect = (ConfigurableEffect)iter.next();\r
+ for (int i = 0, n = effectsListModel.getSize(); i < n; i++) {\r
+ ConfigurableEffect effect = (ConfigurableEffect)effectsListModel.getElementAt(i);\r
+ if (effect.getClass() == settingsEffect.getClass()) {\r
+ effect.setValues(settingsEffect.getValues());\r
+ new EffectPanel(effect);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ updateFont();\r
+ }\r
+\r
+ private void initializeEvents () {\r
+ fontList.addListSelectionListener(new ListSelectionListener() {\r
+ public void valueChanged (ListSelectionEvent evt) {\r
+ if (evt.getValueIsAdjusting()) return;\r
+ prefs.put("system.font", (String)fontList.getSelectedValue());\r
+ updateFont();\r
+ }\r
+ });\r
+\r
+ class FontUpdateListener implements ChangeListener, ActionListener {\r
+ public void stateChanged (ChangeEvent evt) {\r
+ updateFont();\r
+ }\r
+\r
+ public void actionPerformed (ActionEvent evt) {\r
+ updateFont();\r
+ }\r
+\r
+ public void addSpinners (JSpinner[] spinners) {\r
+ for (int i = 0; i < spinners.length; i++) {\r
+ final JSpinner spinner = spinners[i];\r
+ spinner.addChangeListener(this);\r
+ ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField().addKeyListener(new KeyAdapter() {\r
+ String lastText;\r
+\r
+ public void keyReleased (KeyEvent evt) {\r
+ JFormattedTextField textField = ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField();\r
+ String text = textField.getText();\r
+ if (text.length() == 0) return;\r
+ if (text.equals(lastText)) return;\r
+ lastText = text;\r
+ int caretPosition = textField.getCaretPosition();\r
+ try {\r
+ spinner.setValue(Integer.valueOf(text));\r
+ textField.setCaretPosition(caretPosition);\r
+ } catch (Throwable ignored) {\r
+ }\r
+ }\r
+ });\r
+ }\r
+ }\r
+ }\r
+ FontUpdateListener listener = new FontUpdateListener();\r
+\r
+ listener.addSpinners(new JSpinner[] {padTopSpinner, padRightSpinner, padBottomSpinner, padLeftSpinner, padAdvanceXSpinner,\r
+ padAdvanceYSpinner});\r
+ fontSizeSpinner.addChangeListener(listener);\r
+\r
+ glyphPageWidthCombo.addActionListener(listener);\r
+ glyphPageHeightCombo.addActionListener(listener);\r
+ boldCheckBox.addActionListener(listener);\r
+ italicCheckBox.addActionListener(listener);\r
+ resetCacheButton.addActionListener(listener);\r
+\r
+ sampleTextRadio.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ glyphCachePanel.setVisible(false);\r
+ }\r
+ });\r
+ glyphCacheRadio.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ glyphCachePanel.setVisible(true);\r
+ }\r
+ });\r
+\r
+ fontFileText.getDocument().addDocumentListener(new DocumentListener() {\r
+ public void removeUpdate (DocumentEvent evt) {\r
+ changed();\r
+ }\r
+\r
+ public void insertUpdate (DocumentEvent evt) {\r
+ changed();\r
+ }\r
+\r
+ public void changedUpdate (DocumentEvent evt) {\r
+ changed();\r
+ }\r
+\r
+ private void changed () {\r
+ File file = new File(fontFileText.getText());\r
+ if (fontList.isEnabled() && (!file.exists() || !file.isFile())) return;\r
+ prefs.put("font.file", fontFileText.getText());\r
+ updateFont();\r
+ }\r
+ });\r
+\r
+ fontFileRadio.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ if (fontList.isEnabled()) systemFontRadio.setSelected(true);\r
+ }\r
+ });\r
+\r
+ browseButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ FileDialog dialog = new FileDialog(Hiero.this, "Choose TrueType font file", FileDialog.LOAD);\r
+ dialog.setLocationRelativeTo(null);\r
+ dialog.setFile("*.ttf");\r
+ dialog.setVisible(true);\r
+ String fileName = dialog.getFile();\r
+ if (fileName == null) return;\r
+ fontFileText.setText(new File(dialog.getDirectory(), fileName).getAbsolutePath());\r
+ }\r
+ });\r
+\r
+ backgroundColorLabel.addMouseListener(new MouseAdapter() {\r
+ public void mouseClicked (MouseEvent evt) {\r
+ java.awt.Color color = JColorChooser.showDialog(null, "Choose a background color",\r
+ EffectUtil.fromString(prefs.get("background", "000000")));\r
+ if (color == null) return;\r
+ renderingBackgroundColor = new Color(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, 1);\r
+ backgroundColorLabel.setIcon(getColorIcon(color));\r
+ prefs.put("background", EffectUtil.toString(color));\r
+ }\r
+ });\r
+\r
+ effectsList.addListSelectionListener(new ListSelectionListener() {\r
+ public void valueChanged (ListSelectionEvent evt) {\r
+ ConfigurableEffect selectedEffect = (ConfigurableEffect)effectsList.getSelectedValue();\r
+ boolean enabled = selectedEffect != null;\r
+ for (Iterator iter = effectPanels.iterator(); iter.hasNext();) {\r
+ ConfigurableEffect effect = ((EffectPanel)iter.next()).getEffect();\r
+ if (effect == selectedEffect) {\r
+ enabled = false;\r
+ break;\r
+ }\r
+ }\r
+ addEffectButton.setEnabled(enabled);\r
+ }\r
+ });\r
+\r
+ effectsList.addMouseListener(new MouseAdapter() {\r
+ public void mouseClicked (MouseEvent evt) {\r
+ if (evt.getClickCount() == 2 && addEffectButton.isEnabled()) addEffectButton.doClick();\r
+ }\r
+ });\r
+\r
+ addEffectButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ new EffectPanel((ConfigurableEffect)effectsList.getSelectedValue());\r
+ }\r
+ });\r
+\r
+ openMenuItem.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ FileDialog dialog = new FileDialog(Hiero.this, "Open Hiero settings file", FileDialog.LOAD);\r
+ dialog.setLocationRelativeTo(null);\r
+ dialog.setFile("*.hiero");\r
+ dialog.setVisible(true);\r
+ String fileName = dialog.getFile();\r
+ if (fileName == null) return;\r
+ open(new File(dialog.getDirectory(), fileName));\r
+ }\r
+ });\r
+\r
+ saveMenuItem.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ FileDialog dialog = new FileDialog(Hiero.this, "Save Hiero settings file", FileDialog.SAVE);\r
+ dialog.setLocationRelativeTo(null);\r
+ dialog.setFile("*.hiero");\r
+ dialog.setVisible(true);\r
+ String fileName = dialog.getFile();\r
+ if (fileName == null) return;\r
+ File file = new File(dialog.getDirectory(), fileName);\r
+ try {\r
+ save(file);\r
+ } catch (IOException ex) {\r
+ throw new RuntimeException("Error saving Hiero settings file: " + file.getAbsolutePath(), ex);\r
+ }\r
+ }\r
+ });\r
+\r
+ saveBMFontMenuItem.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ FileDialog dialog = new FileDialog(Hiero.this, "Save BMFont files", FileDialog.SAVE);\r
+ dialog.setLocationRelativeTo(null);\r
+ dialog.setFile("*.fnt");\r
+ dialog.setVisible(true);\r
+ String fileName = dialog.getFile();\r
+ if (fileName == null) return;\r
+ saveBmFontFile = new File(dialog.getDirectory(), fileName);\r
+ }\r
+ });\r
+\r
+ exitMenuItem.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ dispose();\r
+ }\r
+ });\r
+\r
+ sampleNeheButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ sampleTextPane.setText(NEHE);\r
+ }\r
+ });\r
+\r
+ sampleAsciiButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ StringBuilder buffer = new StringBuilder();\r
+ buffer.append(NEHE);\r
+ buffer.append('\n');\r
+ int count = 0;\r
+ for (int i = 33; i <= 255; i++) {\r
+ if (buffer.indexOf(Character.toString((char)i)) != -1) continue;\r
+ buffer.append((char)i);\r
+ if (++count % 30 == 0) buffer.append('\n');\r
+ }\r
+ sampleTextPane.setText(buffer.toString());\r
+ }\r
+ });\r
+ }\r
+\r
+ private void initializeComponents () {\r
+ getContentPane().setLayout(new GridBagLayout());\r
+ JPanel leftSidePanel = new JPanel();\r
+ leftSidePanel.setLayout(new GridBagLayout());\r
+ getContentPane().add(\r
+ leftSidePanel,\r
+ new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0),\r
+ 0, 0));\r
+ {\r
+ JPanel fontPanel = new JPanel();\r
+ leftSidePanel.add(fontPanel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0));\r
+ fontPanel.setLayout(new GridBagLayout());\r
+ fontPanel.setBorder(BorderFactory.createTitledBorder("Font"));\r
+ {\r
+ fontSizeSpinner = new JSpinner(new SpinnerNumberModel(32, 0, 256, 1));\r
+ fontPanel.add(fontSizeSpinner, new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ ((JSpinner.DefaultEditor)fontSizeSpinner.getEditor()).getTextField().setColumns(2);\r
+ }\r
+ {\r
+ JScrollPane fontScroll = new JScrollPane();\r
+ fontPanel.add(fontScroll, new GridBagConstraints(1, 1, 4, 1, 1.0, 1.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.BOTH, new Insets(0, 0, 5, 5), 0, 0));\r
+ {\r
+ fontListModel = new DefaultComboBoxModel(GraphicsEnvironment.getLocalGraphicsEnvironment()\r
+ .getAvailableFontFamilyNames());\r
+ fontList = new JList();\r
+ fontScroll.setViewportView(fontList);\r
+ fontList.setModel(fontListModel);\r
+ fontList.setVisibleRowCount(6);\r
+ fontList.setSelectedIndex(0);\r
+ fontScroll.setMinimumSize(new Dimension(220, fontList.getPreferredScrollableViewportSize().height));\r
+ }\r
+ }\r
+ {\r
+ systemFontRadio = new JRadioButton("System:", true);\r
+ fontPanel.add(systemFontRadio, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST,\r
+ GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0));\r
+ systemFontRadio.setMargin(new Insets(0, 0, 0, 0));\r
+ }\r
+ {\r
+ fontFileRadio = new JRadioButton("File:");\r
+ fontPanel.add(fontFileRadio, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,\r
+ GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));\r
+ fontFileRadio.setMargin(new Insets(0, 0, 0, 0));\r
+ }\r
+ {\r
+ fontFileText = new JTextField();\r
+ fontPanel.add(fontFileText, new GridBagConstraints(1, 2, 3, 1, 1.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.HORIZONTAL, new Insets(0, 0, 5, 0), 0, 0));\r
+ }\r
+ {\r
+ fontPanel.add(new JLabel("Size:"), new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ boldCheckBox = new JCheckBox("Bold");\r
+ fontPanel.add(boldCheckBox, new GridBagConstraints(2, 3, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ italicCheckBox = new JCheckBox("Italic");\r
+ fontPanel.add(italicCheckBox, new GridBagConstraints(3, 3, 1, 1, 1.0, 0.0, GridBagConstraints.WEST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ browseButton = new JButton("...");\r
+ fontPanel.add(browseButton, new GridBagConstraints(4, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ browseButton.setMargin(new Insets(0, 0, 0, 0));\r
+ }\r
+ ButtonGroup buttonGroup = new ButtonGroup();\r
+ buttonGroup.add(systemFontRadio);\r
+ buttonGroup.add(fontFileRadio);\r
+ }\r
+ {\r
+ JPanel samplePanel = new JPanel();\r
+ leftSidePanel.add(samplePanel, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.BOTH, new Insets(5, 0, 5, 5), 0, 0));\r
+ samplePanel.setLayout(new GridBagLayout());\r
+ samplePanel.setBorder(BorderFactory.createTitledBorder("Sample Text"));\r
+ {\r
+ JScrollPane textScroll = new JScrollPane();\r
+ samplePanel.add(textScroll, new GridBagConstraints(0, 0, 3, 1, 1.0, 1.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.BOTH, new Insets(0, 5, 5, 5), 0, 0));\r
+ {\r
+ sampleTextPane = new JTextPane();\r
+ textScroll.setViewportView(sampleTextPane);\r
+ }\r
+ }\r
+ {\r
+ sampleNeheButton = new JButton();\r
+ sampleNeheButton.setText("NEHE");\r
+ samplePanel.add(sampleNeheButton, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ sampleAsciiButton = new JButton();\r
+ sampleAsciiButton.setText("ASCII");\r
+ samplePanel.add(sampleAsciiButton, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.EAST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ }\r
+ }\r
+ {\r
+ JPanel renderingPanel = new JPanel();\r
+ leftSidePanel.add(renderingPanel, new GridBagConstraints(0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.BOTH, new Insets(0, 5, 5, 5), 0, 0));\r
+ renderingPanel.setBorder(BorderFactory.createTitledBorder("Rendering"));\r
+ renderingPanel.setLayout(new GridBagLayout());\r
+ {\r
+ JPanel wrapperPanel = new JPanel();\r
+ renderingPanel.add(wrapperPanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.BOTH, new Insets(0, 5, 5, 5), 0, 0));\r
+ wrapperPanel.setLayout(new BorderLayout());\r
+ wrapperPanel.setBackground(java.awt.Color.white);\r
+ {\r
+ gamePanel = new JPanel();\r
+ wrapperPanel.add(gamePanel);\r
+ gamePanel.setLayout(new BorderLayout());\r
+ gamePanel.setBackground(java.awt.Color.white);\r
+ }\r
+ }\r
+ {\r
+ glyphCachePanel = new JPanel() {\r
+ private int maxWidth;\r
+\r
+ public Dimension getPreferredSize () {\r
+ // Keep glyphCachePanel width from ever going down so the CanvasGameContainer doesn't change sizes and flicker.\r
+ Dimension size = super.getPreferredSize();\r
+ maxWidth = Math.max(maxWidth, size.width);\r
+ size.width = maxWidth;\r
+ return size;\r
+ }\r
+ };\r
+ glyphCachePanel.setVisible(false);\r
+ renderingPanel.add(glyphCachePanel, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTH,\r
+ GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));\r
+ glyphCachePanel.setLayout(new GridBagLayout());\r
+ {\r
+ glyphCachePanel.add(new JLabel("Glyphs:"), new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,\r
+ GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ glyphCachePanel.add(new JLabel("Pages:"), new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,\r
+ GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ glyphCachePanel.add(new JLabel("Page width:"), new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,\r
+ GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ glyphCachePanel.add(new JLabel("Page height:"), new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0,\r
+ GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ glyphPageWidthCombo = new JComboBox(new DefaultComboBoxModel(new Integer[] {new Integer(256), new Integer(512),\r
+ new Integer(1024), new Integer(2048)}));\r
+ glyphCachePanel.add(glyphPageWidthCombo, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ glyphPageWidthCombo.setSelectedIndex(1);\r
+ }\r
+ {\r
+ glyphPageHeightCombo = new JComboBox(new DefaultComboBoxModel(new Integer[] {new Integer(256), new Integer(512),\r
+ new Integer(1024), new Integer(2048)}));\r
+ glyphCachePanel.add(glyphPageHeightCombo, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ glyphPageHeightCombo.setSelectedIndex(1);\r
+ }\r
+ {\r
+ resetCacheButton = new JButton("Reset Cache");\r
+ glyphCachePanel.add(resetCacheButton, new GridBagConstraints(0, 6, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ glyphPagesTotalLabel = new JLabel("1");\r
+ glyphCachePanel.add(glyphPagesTotalLabel, new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ glyphsTotalLabel = new JLabel("0");\r
+ glyphCachePanel.add(glyphsTotalLabel, new GridBagConstraints(1, 4, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ glyphPageComboModel = new DefaultComboBoxModel();\r
+ glyphPageCombo = new JComboBox();\r
+ glyphCachePanel.add(glyphPageCombo, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ glyphPageCombo.setModel(glyphPageComboModel);\r
+ }\r
+ {\r
+ glyphCachePanel.add(new JLabel("View:"), new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,\r
+ GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));\r
+ }\r
+ }\r
+ {\r
+ JPanel radioButtonsPanel = new JPanel();\r
+ renderingPanel.add(radioButtonsPanel, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));\r
+ radioButtonsPanel.setLayout(new GridBagLayout());\r
+ {\r
+ sampleTextRadio = new JRadioButton("Sample text");\r
+ radioButtonsPanel.add(sampleTextRadio, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ sampleTextRadio.setSelected(true);\r
+ }\r
+ {\r
+ glyphCacheRadio = new JRadioButton("Glyph cache");\r
+ radioButtonsPanel.add(glyphCacheRadio, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ radioButtonsPanel.add(new JLabel("Background:"), new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,\r
+ GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0));\r
+ }\r
+ {\r
+ backgroundColorLabel = new JLabel();\r
+ radioButtonsPanel.add(backgroundColorLabel, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 5, 5), 0, 0));\r
+ }\r
+ ButtonGroup buttonGroup = new ButtonGroup();\r
+ buttonGroup.add(glyphCacheRadio);\r
+ buttonGroup.add(sampleTextRadio);\r
+ }\r
+ }\r
+ JPanel rightSidePanel = new JPanel();\r
+ rightSidePanel.setLayout(new GridBagLayout());\r
+ getContentPane().add(\r
+ rightSidePanel,\r
+ new GridBagConstraints(1, 0, 1, 2, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0),\r
+ 0, 0));\r
+ {\r
+ JPanel paddingPanel = new JPanel();\r
+ paddingPanel.setLayout(new GridBagLayout());\r
+ rightSidePanel.add(paddingPanel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.BOTH, new Insets(0, 0, 5, 5), 0, 0));\r
+ paddingPanel.setBorder(BorderFactory.createTitledBorder("Padding"));\r
+ {\r
+ padTopSpinner = new JSpinner();\r
+ paddingPanel.add(padTopSpinner, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));\r
+ ((JSpinner.DefaultEditor)padTopSpinner.getEditor()).getTextField().setColumns(2);\r
+ }\r
+ {\r
+ padRightSpinner = new JSpinner();\r
+ paddingPanel.add(padRightSpinner, new GridBagConstraints(2, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 0, 5), 0, 0));\r
+ ((JSpinner.DefaultEditor)padRightSpinner.getEditor()).getTextField().setColumns(2);\r
+ }\r
+ {\r
+ padLeftSpinner = new JSpinner();\r
+ paddingPanel.add(padLeftSpinner, new GridBagConstraints(0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.EAST,\r
+ GridBagConstraints.NONE, new Insets(0, 5, 0, 0), 0, 0));\r
+ ((JSpinner.DefaultEditor)padLeftSpinner.getEditor()).getTextField().setColumns(2);\r
+ }\r
+ {\r
+ padBottomSpinner = new JSpinner();\r
+ paddingPanel.add(padBottomSpinner, new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));\r
+ ((JSpinner.DefaultEditor)padBottomSpinner.getEditor()).getTextField().setColumns(2);\r
+ }\r
+ {\r
+ JPanel advancePanel = new JPanel();\r
+ FlowLayout advancePanelLayout = new FlowLayout();\r
+ advancePanel.setLayout(advancePanelLayout);\r
+ paddingPanel.add(advancePanel, new GridBagConstraints(0, 4, 3, 1, 1.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));\r
+ {\r
+ advancePanel.add(new JLabel("X:"));\r
+ }\r
+ {\r
+ padAdvanceXSpinner = new JSpinner();\r
+ advancePanel.add(padAdvanceXSpinner);\r
+ ((JSpinner.DefaultEditor)padAdvanceXSpinner.getEditor()).getTextField().setColumns(2);\r
+ }\r
+ {\r
+ advancePanel.add(new JLabel("Y:"));\r
+ }\r
+ {\r
+ padAdvanceYSpinner = new JSpinner();\r
+ advancePanel.add(padAdvanceYSpinner);\r
+ ((JSpinner.DefaultEditor)padAdvanceYSpinner.getEditor()).getTextField().setColumns(2);\r
+ }\r
+ }\r
+ }\r
+ {\r
+ JPanel effectsPanel = new JPanel();\r
+ effectsPanel.setLayout(new GridBagLayout());\r
+ rightSidePanel.add(effectsPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.BOTH, new Insets(5, 0, 5, 5), 0, 0));\r
+ effectsPanel.setBorder(BorderFactory.createTitledBorder("Effects"));\r
+ effectsPanel.setMinimumSize(new Dimension(210, 1));\r
+ {\r
+ JScrollPane effectsScroll = new JScrollPane();\r
+ effectsPanel.add(effectsScroll, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.NORTH,\r
+ GridBagConstraints.HORIZONTAL, new Insets(0, 5, 5, 5), 0, 0));\r
+ {\r
+ effectsListModel = new DefaultComboBoxModel();\r
+ effectsList = new JList();\r
+ effectsScroll.setViewportView(effectsList);\r
+ effectsList.setModel(effectsListModel);\r
+ effectsList.setVisibleRowCount(6);\r
+ effectsScroll.setMinimumSize(effectsList.getPreferredScrollableViewportSize());\r
+ }\r
+ }\r
+ {\r
+ addEffectButton = new JButton("Add");\r
+ effectsPanel.add(addEffectButton, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,\r
+ GridBagConstraints.NONE, new Insets(0, 5, 6, 5), 0, 0));\r
+ addEffectButton.setEnabled(false);\r
+ }\r
+ {\r
+ appliedEffectsScroll = new JScrollPane();\r
+ effectsPanel.add(appliedEffectsScroll, new GridBagConstraints(1, 3, 1, 1, 1.0, 1.0, GridBagConstraints.NORTH,\r
+ GridBagConstraints.BOTH, new Insets(0, 0, 5, 0), 0, 0));\r
+ appliedEffectsScroll.setBorder(new EmptyBorder(0, 0, 0, 0));\r
+ appliedEffectsScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);\r
+ {\r
+ JPanel panel = new JPanel();\r
+ panel.setLayout(new GridBagLayout());\r
+ appliedEffectsScroll.setViewportView(panel);\r
+ {\r
+ appliedEffectsPanel = new JPanel();\r
+ appliedEffectsPanel.setLayout(new GridBagLayout());\r
+ panel.add(appliedEffectsPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.NORTH,\r
+ GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));\r
+ appliedEffectsPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, java.awt.Color.black));\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ private void initializeMenus () {\r
+ {\r
+ JMenuBar menuBar = new JMenuBar();\r
+ setJMenuBar(menuBar);\r
+ {\r
+ JMenu fileMenu = new JMenu();\r
+ menuBar.add(fileMenu);\r
+ fileMenu.setText("File");\r
+ fileMenu.setMnemonic(KeyEvent.VK_F);\r
+ {\r
+ openMenuItem = new JMenuItem("Open Hiero settings file...");\r
+ openMenuItem.setMnemonic(KeyEvent.VK_O);\r
+ openMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK));\r
+ fileMenu.add(openMenuItem);\r
+ }\r
+ {\r
+ saveMenuItem = new JMenuItem("Save Hiero settings file...");\r
+ saveMenuItem.setMnemonic(KeyEvent.VK_S);\r
+ saveMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK));\r
+ fileMenu.add(saveMenuItem);\r
+ }\r
+ fileMenu.addSeparator();\r
+ {\r
+ saveBMFontMenuItem = new JMenuItem("Save BMFont files (text)...");\r
+ saveBMFontMenuItem.setMnemonic(KeyEvent.VK_B);\r
+ saveBMFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.CTRL_MASK));\r
+ fileMenu.add(saveBMFontMenuItem);\r
+ }\r
+ fileMenu.addSeparator();\r
+ {\r
+ exitMenuItem = new JMenuItem("Exit");\r
+ exitMenuItem.setMnemonic(KeyEvent.VK_X);\r
+ fileMenu.add(exitMenuItem);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ static Icon getColorIcon (java.awt.Color color) {\r
+ BufferedImage image = new BufferedImage(32, 16, BufferedImage.TYPE_INT_RGB);\r
+ java.awt.Graphics g = image.getGraphics();\r
+ g.setColor(color);\r
+ g.fillRect(1, 1, 30, 14);\r
+ g.setColor(java.awt.Color.black);\r
+ g.drawRect(0, 0, 31, 15);\r
+ return new ImageIcon(image);\r
+ }\r
+\r
+ private class EffectPanel extends JPanel {\r
+ final java.awt.Color selectedColor = new java.awt.Color(0xb1d2e9);\r
+\r
+ final ConfigurableEffect effect;\r
+ List values;\r
+\r
+ JButton deleteButton;\r
+ private JPanel valuesPanel;\r
+ JLabel nameLabel;\r
+\r
+ EffectPanel (final ConfigurableEffect effect) {\r
+ this.effect = effect;\r
+ effectPanels.add(this);\r
+ effectsList.getListSelectionListeners()[0].valueChanged(null);\r
+\r
+ setLayout(new GridBagLayout());\r
+ setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, java.awt.Color.black));\r
+ appliedEffectsPanel.add(this, new GridBagConstraints(0, -1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));\r
+ {\r
+ JPanel titlePanel = new JPanel();\r
+ titlePanel.setLayout(new LayoutManager() {\r
+ public void removeLayoutComponent (Component comp) {\r
+ }\r
+\r
+ public Dimension preferredLayoutSize (Container parent) {\r
+ return null;\r
+ }\r
+\r
+ public Dimension minimumLayoutSize (Container parent) {\r
+ return null;\r
+ }\r
+\r
+ public void layoutContainer (Container parent) {\r
+ Dimension buttonSize = deleteButton.getPreferredSize();\r
+ deleteButton.setBounds(getWidth() - buttonSize.width - 5, 0, buttonSize.width, buttonSize.height);\r
+\r
+ Dimension labelSize = nameLabel.getPreferredSize();\r
+ nameLabel.setBounds(5, buttonSize.height / 2 - labelSize.height / 2, getWidth() - buttonSize.width - 5 - 5,\r
+ labelSize.height);\r
+ }\r
+\r
+ public void addLayoutComponent (String name, Component comp) {\r
+ }\r
+ });\r
+ {\r
+ deleteButton = new JButton();\r
+ titlePanel.add(deleteButton);\r
+ deleteButton.setText("X");\r
+ deleteButton.setMargin(new Insets(0, 0, 0, 0));\r
+ Font font = deleteButton.getFont();\r
+ deleteButton.setFont(new Font(font.getName(), font.getStyle(), font.getSize() - 2));\r
+ }\r
+ {\r
+ nameLabel = new JLabel(effect.toString());\r
+ titlePanel.add(nameLabel);\r
+ Font font = nameLabel.getFont();\r
+ nameLabel.setFont(new Font(font.getName(), Font.BOLD, font.getSize()));\r
+ }\r
+ titlePanel.setPreferredSize(new Dimension(0, Math.max(nameLabel.getPreferredSize().height,\r
+ deleteButton.getPreferredSize().height)));\r
+ add(titlePanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,\r
+ new Insets(5, 0, 0, 5), 0, 0));\r
+ titlePanel.setOpaque(false);\r
+ }\r
+ {\r
+ valuesPanel = new JPanel();\r
+ valuesPanel.setOpaque(false);\r
+ valuesPanel.setLayout(new GridBagLayout());\r
+ add(valuesPanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.HORIZONTAL, new Insets(0, 10, 5, 0), 0, 0));\r
+ }\r
+\r
+ deleteButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ remove();\r
+ updateFont();\r
+ }\r
+ });\r
+\r
+ updateValues();\r
+ updateFont();\r
+ }\r
+\r
+ public void remove () {\r
+ effectPanels.remove(this);\r
+ appliedEffectsPanel.remove(EffectPanel.this);\r
+ getContentPane().validate();\r
+ effectsList.getListSelectionListeners()[0].valueChanged(null);\r
+ }\r
+\r
+ public void updateValues () {\r
+ prefs.put("foreground", EffectUtil.toString(colorEffect.getColor()));\r
+ valuesPanel.removeAll();\r
+ values = effect.getValues();\r
+ for (Iterator iter = values.iterator(); iter.hasNext();)\r
+ addValue((Value)iter.next());\r
+ }\r
+\r
+ public void addValue (final Value value) {\r
+ JLabel valueNameLabel = new JLabel(value.getName() + ":");\r
+ valuesPanel.add(valueNameLabel, new GridBagConstraints(0, -1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,\r
+ GridBagConstraints.NONE, new Insets(0, 0, 0, 5), 0, 0));\r
+\r
+ final JLabel valueValueLabel = new JLabel();\r
+ valuesPanel.add(valueValueLabel, new GridBagConstraints(1, -1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST,\r
+ GridBagConstraints.BOTH, new Insets(0, 0, 0, 5), 0, 0));\r
+ valueValueLabel.setOpaque(true);\r
+ if (value.getObject() instanceof java.awt.Color)\r
+ valueValueLabel.setIcon(getColorIcon((java.awt.Color)value.getObject()));\r
+ else\r
+ valueValueLabel.setText(value.toString());\r
+\r
+ valueValueLabel.addMouseListener(new MouseAdapter() {\r
+ public void mouseEntered (MouseEvent evt) {\r
+ valueValueLabel.setBackground(selectedColor);\r
+ }\r
+\r
+ public void mouseExited (MouseEvent evt) {\r
+ valueValueLabel.setBackground(null);\r
+ }\r
+\r
+ public void mouseClicked (MouseEvent evt) {\r
+ Object oldObject = value.getObject();\r
+ value.showDialog();\r
+ if (!value.getObject().equals(oldObject)) {\r
+ effect.setValues(values);\r
+ updateValues();\r
+ updateFont();\r
+ }\r
+ }\r
+ });\r
+ }\r
+\r
+ public ConfigurableEffect getEffect () {\r
+ return effect;\r
+ }\r
+\r
+ public boolean equals (Object obj) {\r
+ if (this == obj) return true;\r
+ if (obj == null) return false;\r
+ if (getClass() != obj.getClass()) return false;\r
+ final EffectPanel other = (EffectPanel)obj;\r
+ if (effect == null) {\r
+ if (other.effect != null) return false;\r
+ } else if (!effect.equals(other.effect)) return false;\r
+ return true;\r
+ }\r
+ }\r
+\r
+ static private class Splash extends JWindow {\r
+ final int minMillis;\r
+ final long startTime;\r
+\r
+ public Splash (Frame frame, String imageFile, int minMillis) {\r
+ super(frame);\r
+ this.minMillis = minMillis;\r
+ getContentPane().add(new JLabel(new ImageIcon(Splash.class.getResource(imageFile))), BorderLayout.CENTER);\r
+ pack();\r
+ setLocationRelativeTo(null);\r
+ setVisible(true);\r
+ startTime = System.currentTimeMillis();\r
+ }\r
+\r
+ public void close () {\r
+ final long endTime = System.currentTimeMillis();\r
+ new Thread(new Runnable() {\r
+ public void run () {\r
+ if (endTime - startTime < minMillis) {\r
+ addMouseListener(new MouseAdapter() {\r
+ public void mousePressed (MouseEvent evt) {\r
+ dispose();\r
+ }\r
+ });\r
+ try {\r
+ Thread.sleep(minMillis - (endTime - startTime));\r
+ } catch (InterruptedException ignored) {\r
+ }\r
+ }\r
+ EventQueue.invokeLater(new Runnable() {\r
+ public void run () {\r
+ dispose();\r
+ }\r
+ });\r
+ }\r
+ }, "Splash").start();\r
+ }\r
+ }\r
+\r
+ class Renderer implements RenderListener {\r
+ private String sampleText;\r
+\r
+ public void surfaceCreated () {\r
+ glEnable(GL_SCISSOR_TEST);\r
+\r
+ glEnable(GL_TEXTURE_2D);\r
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);\r
+ glEnableClientState(GL_VERTEX_ARRAY);\r
+\r
+ glClearColor(0, 0, 0, 0);\r
+ glClearDepth(1);\r
+\r
+ glDisable(GL_LIGHTING);\r
+\r
+ glEnable(GL_BLEND);\r
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\r
+ }\r
+\r
+ public void surfaceChanged (int width, int height) {\r
+ glViewport(0, 0, width, height);\r
+ glScissor(0, 0, width, height);\r
+\r
+ glMatrixMode(GL_PROJECTION);\r
+ glLoadIdentity();\r
+ glOrtho(0, width, height, 0, 1, -1);\r
+ glMatrixMode(GL_MODELVIEW);\r
+ glLoadIdentity();\r
+ }\r
+\r
+ public void render () {\r
+ if (glCanvas == null) return;\r
+ int viewWidth = Gdx.graphics.getWidth();\r
+ int viewHeight = Gdx.graphics.getHeight();\r
+ if (viewWidth != glCanvas.getWidth() || viewHeight != glCanvas.getHeight()) {\r
+ viewWidth = Math.max(1, glCanvas.getWidth());\r
+ viewHeight = Math.max(1, glCanvas.getHeight());\r
+ app.setSize(viewWidth, viewHeight);\r
+ }\r
+\r
+ if (newUnicodeFont != null) {\r
+ if (unicodeFont != null) unicodeFont.destroy();\r
+ unicodeFont = newUnicodeFont;\r
+ newUnicodeFont = null;\r
+ }\r
+\r
+ // BOZO - Fix no effects.\r
+ if (unicodeFont.loadGlyphs(25)) {\r
+ glyphPageComboModel.removeAllElements();\r
+ int pageCount = unicodeFont.getGlyphPages().size();\r
+ int glyphCount = 0;\r
+ for (int i = 0; i < pageCount; i++) {\r
+ glyphPageComboModel.addElement("Page " + (i + 1));\r
+ glyphCount += ((GlyphPage)unicodeFont.getGlyphPages().get(i)).getGlyphs().size();\r
+ }\r
+ glyphPagesTotalLabel.setText(String.valueOf(pageCount));\r
+ glyphsTotalLabel.setText(String.valueOf(glyphCount));\r
+ }\r
+\r
+ if (saveBmFontFile != null) {\r
+ try {\r
+ BMFontUtil bmFont = new BMFontUtil(unicodeFont);\r
+ bmFont.save(saveBmFontFile);\r
+ } catch (Throwable ex) {\r
+ System.out.println("Error saving BMFont files: " + saveBmFontFile.getAbsolutePath());\r
+ ex.printStackTrace();\r
+ } finally {\r
+ saveBmFontFile = null;\r
+ }\r
+ }\r
+\r
+ if (unicodeFont == null) return;\r
+\r
+ try {\r
+ sampleText = sampleTextPane.getText();\r
+ } catch (Exception ex) {\r
+ }\r
+\r
+ if (sampleTextRadio.isSelected()) {\r
+ GL11.glColor4f(renderingBackgroundColor.r, renderingBackgroundColor.g, renderingBackgroundColor.b,\r
+ renderingBackgroundColor.a);\r
+ GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);\r
+ int offset = unicodeFont.getYOffset(sampleText);\r
+ if (offset > 0) offset = 0;\r
+ unicodeFont.drawString(0, -offset, sampleText, Color.WHITE, 0, sampleText.length());\r
+ } else {\r
+ GL11.glColor4f(1, 1, 1, 1);\r
+ GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);\r
+ unicodeFont.addGlyphs(sampleText);\r
+ // GL11.glColor4f(renderingBackgroundColor.r, renderingBackgroundColor.g, renderingBackgroundColor.b,\r
+ // renderingBackgroundColor.a);\r
+ // fillRect(0, 0, unicodeFont.getGlyphPageWidth() + 2, unicodeFont.getGlyphPageHeight() + 2);\r
+ int index = glyphPageCombo.getSelectedIndex();\r
+ List pages = unicodeFont.getGlyphPages();\r
+ if (index >= 0 && index < pages.size()) {\r
+ Texture texture = ((GlyphPage)pages.get(glyphPageCombo.getSelectedIndex())).getTexture();\r
+ GL11.glBegin(GL11.GL_QUADS);\r
+ GL11.glTexCoord2f(0, 0);\r
+ GL11.glVertex3f(0, 0, 0);\r
+ GL11.glTexCoord2f(0, 1);\r
+ GL11.glVertex3f(0, texture.getHeight(), 0);\r
+ GL11.glTexCoord2f(1, 1);\r
+ GL11.glVertex3f(texture.getWidth(), texture.getHeight(), 0);\r
+ GL11.glTexCoord2f(1, 0);\r
+ GL11.glVertex3f(texture.getWidth(), 0, 0);\r
+ GL11.glEnd();\r
+ }\r
+ }\r
+ }\r
+\r
+ public void dispose () {\r
+ }\r
+ }\r
+\r
+ public static void main (String[] args) throws Exception {\r
+ LookAndFeelInfo[] lookAndFeels = UIManager.getInstalledLookAndFeels();\r
+ for (int i = 0, n = lookAndFeels.length; i < n; i++) {\r
+ if ("Nimbus".equals(lookAndFeels[i].getName())) {\r
+ try {\r
+ UIManager.setLookAndFeel(lookAndFeels[i].getClassName());\r
+ } catch (Throwable ignored) {\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ new Hiero();\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero;\r
+\r
+import java.awt.font.GlyphVector;\r
+import java.io.EOFException;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.ListIterator;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+\r
+/**\r
+ * Reads a TTF font file and provides access to kerning information.\r
+ * \r
+ * Thanks to the Apache FOP project for their inspiring work!\r
+ * \r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+class Kerning {\r
+ private Map values = Collections.EMPTY_MAP;\r
+ private int size = -1;\r
+ private int kerningPairCount = -1;\r
+ private float scale;\r
+ private long bytePosition;\r
+ private long headOffset = -1;\r
+ private long kernOffset = -1;\r
+\r
+ /**\r
+ * @param input The data for the TTF font.\r
+ * @param size The font size to use to determine kerning pixel offsets.\r
+ * @throws IOException If the font could not be read.\r
+ */\r
+ public void load (InputStream input, int size) throws IOException {\r
+ this.size = size;\r
+ if (input == null) throw new IllegalArgumentException("input cannot be null.");\r
+ readTableDirectory(input);\r
+ if (headOffset == -1) throw new IOException("HEAD table not found.");\r
+ if (kernOffset == -1) {\r
+ values = Collections.EMPTY_MAP;\r
+ return;\r
+ }\r
+ values = new HashMap(256);\r
+ if (headOffset < kernOffset) {\r
+ readHEAD(input);\r
+ readKERN(input);\r
+ } else {\r
+ readKERN(input);\r
+ readHEAD(input);\r
+ }\r
+ input.close();\r
+\r
+ for (Iterator entryIter = values.entrySet().iterator(); entryIter.hasNext();) {\r
+ Entry entry = (Entry)entryIter.next();\r
+ // Scale the offset values using the font size.\r
+ List valueList = (List)entry.getValue();\r
+ for (ListIterator valueIter = valueList.listIterator(); valueIter.hasNext();) {\r
+ int value = ((Integer)valueIter.next()).intValue();\r
+ int glyphCode = value & 0xffff;\r
+ int offset = value >> 16;\r
+ offset = Math.round(offset * scale);\r
+ if (offset == 0)\r
+ valueIter.remove();\r
+ else\r
+ valueIter.set(new Integer((offset << 16) | glyphCode));\r
+ }\r
+ if (valueList.isEmpty()) {\r
+ entryIter.remove();\r
+ } else {\r
+ // Replace ArrayList with int[].\r
+ int[] valueArray = new int[valueList.size()];\r
+ int i = 0;\r
+ for (Iterator valueIter = valueList.iterator(); valueIter.hasNext(); i++)\r
+ valueArray[i] = ((Integer)valueIter.next()).intValue();\r
+ entry.setValue(valueArray);\r
+ kerningPairCount += valueArray.length;\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns the encoded kerning value for the specified glyph. The glyph code for a Unicode codepoint can be retrieved with\r
+ * {@link GlyphVector#getGlyphCode(int)}.\r
+ */\r
+ public int[] getValues (int firstGlyphCode) {\r
+ return (int[])values.get(new Integer(firstGlyphCode));\r
+ }\r
+\r
+ public int getKerning (int[] values, int otherGlyphCode) {\r
+ int low = 0;\r
+ int high = values.length - 1;\r
+ while (low <= high) {\r
+ int midIndex = (low + high) >>> 1;\r
+ int value = values[midIndex];\r
+ int foundGlyphCode = value & 0xffff;\r
+ if (foundGlyphCode < otherGlyphCode)\r
+ low = midIndex + 1;\r
+ else if (foundGlyphCode > otherGlyphCode)\r
+ high = midIndex - 1;\r
+ else\r
+ return value >> 16;\r
+ }\r
+ return 0;\r
+ }\r
+\r
+ public int getCount () {\r
+ return kerningPairCount;\r
+ }\r
+\r
+ private void readTableDirectory (InputStream input) throws IOException {\r
+ skip(input, 4);\r
+ int tableCount = readUnsignedShort(input);\r
+ skip(input, 6);\r
+\r
+ byte[] tagBytes = new byte[4];\r
+ for (int i = 0; i < tableCount; i++) {\r
+ tagBytes[0] = readByte(input);\r
+ tagBytes[1] = readByte(input);\r
+ tagBytes[2] = readByte(input);\r
+ tagBytes[3] = readByte(input);\r
+ skip(input, 4);\r
+ long offset = readUnsignedLong(input);\r
+ skip(input, 4);\r
+\r
+ String tag = new String(tagBytes, "ISO-8859-1");\r
+ if (tag.equals("head")) {\r
+ headOffset = offset;\r
+ if (kernOffset != -1) break;\r
+ } else if (tag.equals("kern")) {\r
+ kernOffset = offset;\r
+ if (headOffset != -1) break;\r
+ }\r
+ }\r
+ }\r
+\r
+ private void readHEAD (InputStream input) throws IOException {\r
+ seek(input, headOffset + 2 * 4 + 2 * 4 + 2);\r
+ int unitsPerEm = readUnsignedShort(input);\r
+ scale = (float)size / unitsPerEm;\r
+ }\r
+\r
+ private void readKERN (InputStream input) throws IOException {\r
+ seek(input, kernOffset + 2);\r
+ for (int subTableCount = readUnsignedShort(input); subTableCount > 0; subTableCount--) {\r
+ skip(input, 2 * 2);\r
+ int tupleIndex = readUnsignedShort(input);\r
+ if (!((tupleIndex & 1) != 0) || (tupleIndex & 2) != 0 || (tupleIndex & 4) != 0) return;\r
+ if (tupleIndex >> 8 != 0) continue;\r
+\r
+ int kerningCount = readUnsignedShort(input);\r
+ skip(input, 3 * 2);\r
+ while (kerningCount-- > 0) {\r
+ int firstGlyphCode = readUnsignedShort(input);\r
+ int secondGlyphCode = readUnsignedShort(input);\r
+ int offset = readShort(input);\r
+ int value = (offset << 16) | secondGlyphCode;\r
+\r
+ List firstGlyphValues = (List)values.get(new Integer(firstGlyphCode));\r
+ if (firstGlyphValues == null) {\r
+ firstGlyphValues = new ArrayList(256);\r
+ values.put(new Integer(firstGlyphCode), firstGlyphValues);\r
+ }\r
+ firstGlyphValues.add(new Integer(value));\r
+ }\r
+ }\r
+ }\r
+\r
+ private int readUnsignedByte (InputStream input) throws IOException {\r
+ bytePosition++;\r
+ int b = input.read();\r
+ if (b == -1) throw new EOFException("Unexpected end of file.");\r
+ return b;\r
+ }\r
+\r
+ private byte readByte (InputStream input) throws IOException {\r
+ return (byte)readUnsignedByte(input);\r
+ }\r
+\r
+ private int readUnsignedShort (InputStream input) throws IOException {\r
+ return (readUnsignedByte(input) << 8) + readUnsignedByte(input);\r
+ }\r
+\r
+ private short readShort (InputStream input) throws IOException {\r
+ return (short)readUnsignedShort(input);\r
+ }\r
+\r
+ private long readUnsignedLong (InputStream input) throws IOException {\r
+ long value = readUnsignedByte(input);\r
+ value = (value << 8) + readUnsignedByte(input);\r
+ value = (value << 8) + readUnsignedByte(input);\r
+ value = (value << 8) + readUnsignedByte(input);\r
+ return value;\r
+ }\r
+\r
+ private void skip (InputStream input, long skip) throws IOException {\r
+ while (skip > 0) {\r
+ long skipped = input.skip(skip);\r
+ if (skipped <= 0) break;\r
+ bytePosition += skipped;\r
+ skip -= skipped;\r
+ }\r
+ }\r
+\r
+ private void seek (InputStream input, long position) throws IOException {\r
+ skip(input, position - bytePosition);\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont;\r
+\r
+import java.awt.Rectangle;\r
+import java.awt.Shape;\r
+import java.awt.font.GlyphMetrics;\r
+import java.awt.font.GlyphVector;\r
+\r
+import com.badlogic.gdx.graphics.Sprite;\r
+import com.badlogic.gdx.graphics.Texture;\r
+\r
+/**\r
+ * Represents the glyph in a font for a unicode codepoint.\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class Glyph {\r
+ private int codePoint;\r
+ private short width, height;\r
+ private short yOffset;\r
+ private boolean isMissing;\r
+ private Shape shape;\r
+ private float u, v, u2, v2;\r
+ private Texture texture;\r
+\r
+ Glyph (int codePoint, Rectangle bounds, GlyphVector vector, int index, UnicodeFont unicodeFont) {\r
+ this.codePoint = codePoint;\r
+\r
+ GlyphMetrics metrics = vector.getGlyphMetrics(index);\r
+ int lsb = (int)metrics.getLSB();\r
+ if (lsb > 0) lsb = 0;\r
+ int rsb = (int)metrics.getRSB();\r
+ if (rsb > 0) rsb = 0;\r
+\r
+ int glyphWidth = bounds.width - lsb - rsb;\r
+ int glyphHeight = bounds.height;\r
+ if (glyphWidth > 0 && glyphHeight > 0) {\r
+ int padTop = unicodeFont.getPaddingTop();\r
+ int padRight = unicodeFont.getPaddingRight();\r
+ int padBottom = unicodeFont.getPaddingBottom();\r
+ int padLeft = unicodeFont.getPaddingLeft();\r
+ int glyphSpacing = 1; // Needed to prevent filtering problems.\r
+ width = (short)(glyphWidth + padLeft + padRight + glyphSpacing);\r
+ height = (short)(glyphHeight + padTop + padBottom + glyphSpacing);\r
+ yOffset = (short)(unicodeFont.getAscent() + bounds.y - padTop);\r
+ }\r
+\r
+ shape = vector.getGlyphOutline(index, -bounds.x + unicodeFont.getPaddingLeft(), -bounds.y + unicodeFont.getPaddingTop());\r
+\r
+ isMissing = !unicodeFont.getFont().canDisplay((char)codePoint);\r
+ }\r
+\r
+ /**\r
+ * The unicode codepoint the glyph represents.\r
+ */\r
+ public int getCodePoint () {\r
+ return codePoint;\r
+ }\r
+\r
+ /**\r
+ * Returns true if the font does not have a glyph for this codepoint.\r
+ */\r
+ public boolean isMissing () {\r
+ return isMissing;\r
+ }\r
+\r
+ /**\r
+ * The width of the glyph's image.\r
+ */\r
+ public int getWidth () {\r
+ return width;\r
+ }\r
+\r
+ /**\r
+ * The height of the glyph's image.\r
+ */\r
+ public int getHeight () {\r
+ return height;\r
+ }\r
+\r
+ /**\r
+ * The shape to use to draw this glyph. This is set to null after the glyph is stored in a GlyphPage.\r
+ */\r
+ public Shape getShape () {\r
+ return shape;\r
+ }\r
+\r
+ public void setShape (Shape shape) {\r
+ this.shape = shape;\r
+ }\r
+\r
+ public void setTexture (Texture texture, float u, float v, float u2, float v2) {\r
+ this.texture = texture;\r
+ this.u = u;\r
+ this.v = v;\r
+ this.u2 = u2;\r
+ this.v2 = v2;\r
+ }\r
+\r
+ public Texture getTexture () {\r
+ return texture;\r
+ }\r
+\r
+ public float getU () {\r
+ return u;\r
+ }\r
+\r
+ public float getV () {\r
+ return v;\r
+ }\r
+\r
+ public float getU2 () {\r
+ return u2;\r
+ }\r
+\r
+ public float getV2 () {\r
+ return v2;\r
+ }\r
+\r
+ /**\r
+ * The distance from drawing y location to top of this glyph, causing the glyph to sit on the baseline.\r
+ */\r
+ public int getYOffset () {\r
+ return yOffset;\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont;\r
+\r
+import java.awt.AlphaComposite;\r
+import java.awt.Graphics2D;\r
+import java.awt.RenderingHints;\r
+import java.awt.font.FontRenderContext;\r
+import java.awt.image.BufferedImage;\r
+import java.awt.image.WritableRaster;\r
+import java.nio.ByteBuffer;\r
+import java.nio.ByteOrder;\r
+import java.nio.IntBuffer;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.ListIterator;\r
+\r
+import javax.swing.Renderer;\r
+\r
+import org.lwjgl.opengl.GL11;\r
+import org.lwjgl.opengl.GL12;\r
+\r
+import com.badlogic.gdx.Gdx;\r
+import com.badlogic.gdx.graphics.Pixmap;\r
+import com.badlogic.gdx.graphics.Pixmap.Format;\r
+import com.badlogic.gdx.graphics.Sprite;\r
+import com.badlogic.gdx.graphics.Texture;\r
+import com.badlogic.gdx.graphics.Texture.TextureFilter;\r
+import com.badlogic.gdx.graphics.Texture.TextureWrap;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.Effect;\r
+\r
+/**\r
+ * Stores a number of glyphs on a single texture.\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class GlyphPage {\r
+ private final UnicodeFont unicodeFont;\r
+ private final int pageWidth, pageHeight;\r
+ private final Texture texture;\r
+ private int pageX, pageY, rowHeight;\r
+ private boolean orderAscending;\r
+ private final List pageGlyphs = new ArrayList(32);\r
+\r
+ /**\r
+ * @param pageWidth The width of the backing texture.\r
+ * @param pageHeight The height of the backing texture.\r
+ */\r
+ GlyphPage (UnicodeFont unicodeFont, int pageWidth, int pageHeight) {\r
+ this.unicodeFont = unicodeFont;\r
+ this.pageWidth = pageWidth;\r
+ this.pageHeight = pageHeight;\r
+\r
+ texture = Gdx.graphics.newUnmanagedTexture(pageWidth, pageHeight, Format.RGBA8888, TextureFilter.Linear,\r
+ TextureFilter.Linear, TextureWrap.ClampToEdge, TextureWrap.ClampToEdge);\r
+ }\r
+\r
+ /**\r
+ * Loads glyphs to the backing texture and sets the image on each loaded glyph. Loaded glyphs are removed from the list.\r
+ * \r
+ * If this page already has glyphs and maxGlyphsToLoad is -1, then this method will return 0 if all the new glyphs don't fit.\r
+ * This reduces texture binds when drawing since glyphs loaded at once are typically displayed together.\r
+ * @param glyphs The glyphs to load.\r
+ * @param maxGlyphsToLoad This is the maximum number of glyphs to load from the list. Set to -1 to attempt to load all the\r
+ * glyphs.\r
+ * @return The number of glyphs that were actually loaded.\r
+ */\r
+ int loadGlyphs (List glyphs, int maxGlyphsToLoad) {\r
+ if (rowHeight != 0 && maxGlyphsToLoad == -1) {\r
+ // If this page has glyphs and we are not loading incrementally, return zero if any of the glyphs don't fit.\r
+ int testX = pageX;\r
+ int testY = pageY;\r
+ int testRowHeight = rowHeight;\r
+ for (Iterator iter = getIterator(glyphs); iter.hasNext();) {\r
+ Glyph glyph = (Glyph)iter.next();\r
+ int width = glyph.getWidth();\r
+ int height = glyph.getHeight();\r
+ if (testX + width >= pageWidth) {\r
+ testX = 0;\r
+ testY += testRowHeight;\r
+ testRowHeight = height;\r
+ } else if (height > testRowHeight) {\r
+ testRowHeight = height;\r
+ }\r
+ if (testY + testRowHeight >= pageWidth) return 0;\r
+ testX += width;\r
+ }\r
+ }\r
+\r
+ GL11.glColor4f(1, 1, 1, 1);\r
+ texture.bind();\r
+\r
+ int i = 0;\r
+ for (Iterator iter = getIterator(glyphs); iter.hasNext();) {\r
+ Glyph glyph = (Glyph)iter.next();\r
+ int width = Math.min(MAX_GLYPH_SIZE, glyph.getWidth());\r
+ int height = Math.min(MAX_GLYPH_SIZE, glyph.getHeight());\r
+\r
+ if (rowHeight == 0) {\r
+ // The first glyph always fits.\r
+ rowHeight = height;\r
+ } else {\r
+ // Wrap to the next line if needed, or break if no more fit.\r
+ if (pageX + width >= pageWidth) {\r
+ if (pageY + rowHeight + height >= pageHeight) break;\r
+ pageX = 0;\r
+ pageY += rowHeight;\r
+ rowHeight = height;\r
+ } else if (height > rowHeight) {\r
+ if (pageY + height >= pageHeight) break;\r
+ rowHeight = height;\r
+ }\r
+ }\r
+\r
+ renderGlyph(glyph, width, height);\r
+ pageGlyphs.add(glyph);\r
+\r
+ pageX += width;\r
+\r
+ iter.remove();\r
+ i++;\r
+ if (i == maxGlyphsToLoad) {\r
+ // If loading incrementally, flip orderAscending so it won't change, since we'll probably load the rest next time.\r
+ orderAscending = !orderAscending;\r
+ break;\r
+ }\r
+ }\r
+\r
+ // Every other batch of glyphs added to a page are sorted the opposite way to attempt to keep same size glyps together.\r
+ orderAscending = !orderAscending;\r
+\r
+ return i;\r
+ }\r
+\r
+ /**\r
+ * Loads a single glyph to the backing texture, if it fits.\r
+ */\r
+ private void renderGlyph (Glyph glyph, int width, int height) {\r
+ // Draw the glyph to the scratch image using Java2D.\r
+ scratchGraphics.setComposite(AlphaComposite.Clear);\r
+ scratchGraphics.fillRect(0, 0, MAX_GLYPH_SIZE, MAX_GLYPH_SIZE);\r
+ scratchGraphics.setComposite(AlphaComposite.SrcOver);\r
+ scratchGraphics.setColor(java.awt.Color.white);\r
+ for (Iterator iter = unicodeFont.getEffects().iterator(); iter.hasNext();)\r
+ ((Effect)iter.next()).draw(scratchImage, scratchGraphics, unicodeFont, glyph);\r
+ glyph.setShape(null); // The shape will never be needed again.\r
+\r
+ WritableRaster raster = scratchImage.getRaster();\r
+ int[] row = new int[width];\r
+ for (int y = 0; y < height; y++) {\r
+ raster.getDataElements(0, y, width, 1, row);\r
+ scratchIntBuffer.put(row);\r
+ }\r
+ GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, pageX, pageY, width, height, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE,\r
+ scratchByteBuffer);\r
+ scratchIntBuffer.clear();\r
+\r
+ float u = pageX / (float)texture.getWidth();\r
+ float v = pageY / (float)texture.getHeight();\r
+ float u2 = (pageX + width) / (float)texture.getWidth();\r
+ float v2 = (pageY + height) / (float)texture.getHeight();\r
+ glyph.setTexture(texture, u, v, u2, v2);\r
+ }\r
+\r
+ /**\r
+ * Returns an iterator for the specified glyphs, sorted either ascending or descending.\r
+ */\r
+ private Iterator getIterator (List glyphs) {\r
+ if (orderAscending) return glyphs.iterator();\r
+ final ListIterator iter = glyphs.listIterator(glyphs.size());\r
+ return new Iterator() {\r
+ public boolean hasNext () {\r
+ return iter.hasPrevious();\r
+ }\r
+\r
+ public Object next () {\r
+ return iter.previous();\r
+ }\r
+\r
+ public void remove () {\r
+ iter.remove();\r
+ }\r
+ };\r
+ }\r
+\r
+ /**\r
+ * Returns the glyphs stored on this page.\r
+ */\r
+ public List getGlyphs () {\r
+ return pageGlyphs;\r
+ }\r
+\r
+ /**\r
+ * Returns the backing texture for this page.\r
+ */\r
+ public Texture getTexture () {\r
+ return texture;\r
+ }\r
+\r
+ static public final int MAX_GLYPH_SIZE = 256;\r
+\r
+ static private ByteBuffer scratchByteBuffer = ByteBuffer.allocateDirect(MAX_GLYPH_SIZE * MAX_GLYPH_SIZE * 4);\r
+ static {\r
+ scratchByteBuffer.order(ByteOrder.LITTLE_ENDIAN);\r
+ }\r
+ static private IntBuffer scratchIntBuffer = scratchByteBuffer.asIntBuffer();\r
+\r
+ static private BufferedImage scratchImage = new BufferedImage(MAX_GLYPH_SIZE, MAX_GLYPH_SIZE, BufferedImage.TYPE_INT_ARGB);\r
+ static Graphics2D scratchGraphics = (Graphics2D)scratchImage.getGraphics();\r
+ static {\r
+ scratchGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
+ scratchGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);\r
+ }\r
+ static public FontRenderContext renderContext = scratchGraphics.getFontRenderContext();\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStreamReader;\r
+import java.io.PrintStream;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import com.badlogic.gdx.Files.FileType;\r
+import com.badlogic.gdx.Gdx;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.ConfigurableEffect;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.ConfigurableEffect.Value;\r
+import com.badlogic.gdx.utils.GdxRuntimeException;\r
+\r
+/**\r
+ * Holds the settings needed to configure a UnicodeFont.\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class HieroSettings {\r
+ private int fontSize = 12;\r
+ private boolean bold = false, italic = false;\r
+ private int paddingTop, paddingLeft, paddingBottom, paddingRight, paddingAdvanceX, paddingAdvanceY;\r
+ private int glyphPageWidth = 512, glyphPageHeight = 512;\r
+ private final List effects = new ArrayList();\r
+\r
+ public HieroSettings () {\r
+ }\r
+\r
+ /**\r
+ * @param hieroFileRef The file system or classpath location of the Hiero settings file.\r
+ */\r
+ public HieroSettings (String hieroFileRef) {\r
+ try {\r
+ BufferedReader reader = new BufferedReader(new InputStreamReader(Gdx.files.readFile(hieroFileRef, FileType.Absolute)));\r
+ while (true) {\r
+ String line = reader.readLine();\r
+ if (line == null) break;\r
+ line = line.trim();\r
+ if (line.length() == 0) continue;\r
+ String[] pieces = line.split("=", 2);\r
+ String name = pieces[0].trim();\r
+ String value = pieces[1];\r
+ if (name.equals("font.size")) {\r
+ fontSize = Integer.parseInt(value);\r
+ } else if (name.equals("font.bold")) {\r
+ bold = Boolean.parseBoolean(value);\r
+ } else if (name.equals("font.italic")) {\r
+ italic = Boolean.parseBoolean(value);\r
+ } else if (name.equals("pad.top")) {\r
+ paddingTop = Integer.parseInt(value);\r
+ } else if (name.equals("pad.right")) {\r
+ paddingRight = Integer.parseInt(value);\r
+ } else if (name.equals("pad.bottom")) {\r
+ paddingBottom = Integer.parseInt(value);\r
+ } else if (name.equals("pad.left")) {\r
+ paddingLeft = Integer.parseInt(value);\r
+ } else if (name.equals("pad.advance.x")) {\r
+ paddingAdvanceX = Integer.parseInt(value);\r
+ } else if (name.equals("pad.advance.y")) {\r
+ paddingAdvanceY = Integer.parseInt(value);\r
+ } else if (name.equals("glyph.page.width")) {\r
+ glyphPageWidth = Integer.parseInt(value);\r
+ } else if (name.equals("glyph.page.height")) {\r
+ glyphPageHeight = Integer.parseInt(value);\r
+ } else if (name.equals("effect.class")) {\r
+ try {\r
+ effects.add(Class.forName(value).newInstance());\r
+ } catch (Throwable ex) {\r
+ throw new GdxRuntimeException("Unable to create effect instance: " + value, ex);\r
+ }\r
+ } else if (name.startsWith("effect.")) {\r
+ // Set an effect value on the last added effect.\r
+ name = name.substring(7);\r
+ ConfigurableEffect effect = (ConfigurableEffect)effects.get(effects.size() - 1);\r
+ List values = effect.getValues();\r
+ for (Iterator iter = values.iterator(); iter.hasNext();) {\r
+ Value effectValue = (Value)iter.next();\r
+ if (effectValue.getName().equals(name)) {\r
+ effectValue.setString(value);\r
+ break;\r
+ }\r
+ }\r
+ effect.setValues(values);\r
+ }\r
+ }\r
+ reader.close();\r
+ } catch (Throwable ex) {\r
+ throw new GdxRuntimeException("Unable to load Hiero font file: " + hieroFileRef, ex);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#getPaddingTop()\r
+ */\r
+ public int getPaddingTop () {\r
+ return paddingTop;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#setPaddingTop(int)\r
+ */\r
+ public void setPaddingTop (int paddingTop) {\r
+ this.paddingTop = paddingTop;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#getPaddingLeft()\r
+ */\r
+ public int getPaddingLeft () {\r
+ return paddingLeft;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#setPaddingLeft(int)\r
+ */\r
+ public void setPaddingLeft (int paddingLeft) {\r
+ this.paddingLeft = paddingLeft;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#getPaddingBottom()\r
+ */\r
+ public int getPaddingBottom () {\r
+ return paddingBottom;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#setPaddingBottom(int)\r
+ */\r
+ public void setPaddingBottom (int paddingBottom) {\r
+ this.paddingBottom = paddingBottom;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#getPaddingRight()\r
+ */\r
+ public int getPaddingRight () {\r
+ return paddingRight;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#setPaddingRight(int)\r
+ */\r
+ public void setPaddingRight (int paddingRight) {\r
+ this.paddingRight = paddingRight;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#getPaddingAdvanceX()\r
+ */\r
+ public int getPaddingAdvanceX () {\r
+ return paddingAdvanceX;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#setPaddingAdvanceX(int)\r
+ */\r
+ public void setPaddingAdvanceX (int paddingAdvanceX) {\r
+ this.paddingAdvanceX = paddingAdvanceX;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#getPaddingAdvanceY()\r
+ */\r
+ public int getPaddingAdvanceY () {\r
+ return paddingAdvanceY;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#setPaddingAdvanceY(int)\r
+ */\r
+ public void setPaddingAdvanceY (int paddingAdvanceY) {\r
+ this.paddingAdvanceY = paddingAdvanceY;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#getGlyphPageWidth()\r
+ */\r
+ public int getGlyphPageWidth () {\r
+ return glyphPageWidth;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#setGlyphPageWidth(int)\r
+ */\r
+ public void setGlyphPageWidth (int glyphPageWidth) {\r
+ this.glyphPageWidth = glyphPageWidth;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#getGlyphPageHeight()\r
+ */\r
+ public int getGlyphPageHeight () {\r
+ return glyphPageHeight;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#setGlyphPageHeight(int)\r
+ */\r
+ public void setGlyphPageHeight (int glyphPageHeight) {\r
+ this.glyphPageHeight = glyphPageHeight;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)\r
+ * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)\r
+ */\r
+ public int getFontSize () {\r
+ return fontSize;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)\r
+ * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)\r
+ */\r
+ public void setFontSize (int fontSize) {\r
+ this.fontSize = fontSize;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)\r
+ * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)\r
+ */\r
+ public boolean isBold () {\r
+ return bold;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)\r
+ * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)\r
+ */\r
+ public void setBold (boolean bold) {\r
+ this.bold = bold;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)\r
+ * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)\r
+ */\r
+ public boolean isItalic () {\r
+ return italic;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)\r
+ * @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)\r
+ */\r
+ public void setItalic (boolean italic) {\r
+ this.italic = italic;\r
+ }\r
+\r
+ /**\r
+ * @see UnicodeFont#getEffects()\r
+ */\r
+ public List getEffects () {\r
+ return effects;\r
+ }\r
+\r
+ /**\r
+ * Saves the settings to a file.\r
+ * @throws IOException if the file could not be saved.\r
+ */\r
+ public void save (File file) throws IOException {\r
+ PrintStream out = new PrintStream(new FileOutputStream(file));\r
+ out.println("font.size=" + fontSize);\r
+ out.println("font.bold=" + bold);\r
+ out.println("font.italic=" + italic);\r
+ out.println();\r
+ out.println("pad.top=" + paddingTop);\r
+ out.println("pad.right=" + paddingRight);\r
+ out.println("pad.bottom=" + paddingBottom);\r
+ out.println("pad.left=" + paddingLeft);\r
+ out.println("pad.advance.x=" + paddingAdvanceX);\r
+ out.println("pad.advance.y=" + paddingAdvanceY);\r
+ out.println();\r
+ out.println("glyph.page.width=" + glyphPageWidth);\r
+ out.println("glyph.page.height=" + glyphPageHeight);\r
+ out.println();\r
+ for (Iterator iter = effects.iterator(); iter.hasNext();) {\r
+ ConfigurableEffect effect = (ConfigurableEffect)iter.next();\r
+ out.println("effect.class=" + effect.getClass().getName());\r
+ for (Iterator iter2 = effect.getValues().iterator(); iter2.hasNext();) {\r
+ Value value = (Value)iter2.next();\r
+ out.println("effect." + value.getName() + "=" + value.getString());\r
+ }\r
+ out.println();\r
+ }\r
+ out.close();\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont;\r
+\r
+import java.awt.Font;\r
+import java.awt.FontFormatException;\r
+import java.awt.FontMetrics;\r
+import java.awt.Rectangle;\r
+import java.awt.font.GlyphVector;\r
+import java.awt.font.TextAttribute;\r
+import java.io.IOException;\r
+import java.lang.reflect.Field;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.Iterator;\r
+import java.util.LinkedHashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+\r
+import org.lwjgl.opengl.GL11;\r
+\r
+import com.badlogic.gdx.Files.FileType;\r
+import com.badlogic.gdx.Gdx;\r
+import com.badlogic.gdx.graphics.Color;\r
+import com.badlogic.gdx.graphics.Sprite;\r
+import com.badlogic.gdx.graphics.Texture;\r
+import com.badlogic.gdx.utils.GdxRuntimeException;\r
+\r
+/**\r
+ * A Slick bitmap font that can display unicode glyphs from a TrueTypeFont.\r
+ * \r
+ * For efficiency, glyphs are packed on to textures. Glyphs can be loaded to the textures on the fly, when they are first needed\r
+ * for display. However, it is best to load the glyphs that are known to be needed at startup.\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class UnicodeFont {\r
+ static private final int DISPLAY_LIST_CACHE_SIZE = 200;\r
+ static private final int MAX_GLYPH_CODE = 0x10FFFF;\r
+ static private final int PAGE_SIZE = 512;\r
+ static private final int PAGES = MAX_GLYPH_CODE / PAGE_SIZE;\r
+\r
+ private Font font;\r
+ private String ttfFileRef;\r
+ private int ascent, descent, leading, spaceWidth;\r
+ private final Glyph[][] glyphs = new Glyph[PAGES][];\r
+ private final List glyphPages = new ArrayList();\r
+ private final List queuedGlyphs = new ArrayList(256);\r
+ private final List effects = new ArrayList();\r
+ private int paddingTop, paddingLeft, paddingBottom, paddingRight, paddingAdvanceX, paddingAdvanceY;\r
+ private Glyph missingGlyph;\r
+ private int glyphPageWidth = 512, glyphPageHeight = 512;\r
+ private final DisplayList emptyDisplayList = new DisplayList();\r
+\r
+ private boolean displayListCaching = true;\r
+ private int baseDisplayListID = -1;\r
+ int eldestDisplayListID;\r
+ private final LinkedHashMap displayLists = new LinkedHashMap(DISPLAY_LIST_CACHE_SIZE, 1, true) {\r
+ protected boolean removeEldestEntry (Entry eldest) {\r
+ DisplayList displayList = (DisplayList)eldest.getValue();\r
+ if (displayList != null) eldestDisplayListID = displayList.id;\r
+ return size() > DISPLAY_LIST_CACHE_SIZE;\r
+ }\r
+ };\r
+\r
+ /**\r
+ * @param ttfFileRef The file system or classpath location of the TrueTypeFont file.\r
+ * @param hieroFileRef The file system or classpath location of the Hiero settings file.\r
+ */\r
+ public UnicodeFont (String ttfFileRef, String hieroFileRef) {\r
+ this(ttfFileRef, new HieroSettings(hieroFileRef));\r
+ }\r
+\r
+ /**\r
+ * @param ttfFileRef The file system or classpath location of the TrueTypeFont file.\r
+ */\r
+ public UnicodeFont (String ttfFileRef, HieroSettings settings) {\r
+ this.ttfFileRef = ttfFileRef;\r
+ Font font = createFont(ttfFileRef);\r
+ initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic());\r
+ loadSettings(settings);\r
+ }\r
+\r
+ /**\r
+ * @param ttfFileRef The file system or classpath location of the TrueTypeFont file.\r
+ */\r
+ public UnicodeFont (String ttfFileRef, int size, boolean bold, boolean italic) {\r
+ this.ttfFileRef = ttfFileRef;\r
+ initializeFont(createFont(ttfFileRef), size, bold, italic);\r
+ }\r
+\r
+ /**\r
+ * Creates a new UnicodeFont.\r
+ * @param hieroFileRef The file system or classpath location of the Hiero settings file.\r
+ */\r
+ public UnicodeFont (Font font, String hieroFileRef) {\r
+ this(font, new HieroSettings(hieroFileRef));\r
+ }\r
+\r
+ /**\r
+ * Creates a new UnicodeFont.\r
+ */\r
+ public UnicodeFont (Font font, HieroSettings settings) {\r
+ initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic());\r
+ loadSettings(settings);\r
+ }\r
+\r
+ /**\r
+ * Creates a new UnicodeFont.\r
+ */\r
+ public UnicodeFont (Font font) {\r
+ initializeFont(font, font.getSize(), font.isBold(), font.isItalic());\r
+ }\r
+\r
+ /**\r
+ * Creates a new UnicodeFont.\r
+ */\r
+ public UnicodeFont (Font font, int size, boolean bold, boolean italic) {\r
+ initializeFont(font, size, bold, italic);\r
+ }\r
+\r
+ private void initializeFont (Font baseFont, int size, boolean bold, boolean italic) {\r
+ Map attributes = baseFont.getAttributes();\r
+ attributes.put(TextAttribute.SIZE, new Float(size));\r
+ attributes.put(TextAttribute.WEIGHT, bold ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR);\r
+ attributes.put(TextAttribute.POSTURE, italic ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR);\r
+ try {\r
+ attributes.put(TextAttribute.class.getDeclaredField("KERNING").get(null),\r
+ TextAttribute.class.getDeclaredField("KERNING_ON").get(null));\r
+ } catch (Throwable ignored) {\r
+ }\r
+ font = baseFont.deriveFont(attributes);\r
+\r
+ FontMetrics metrics = GlyphPage.scratchGraphics.getFontMetrics(font);\r
+ ascent = metrics.getAscent();\r
+ descent = metrics.getDescent();\r
+ leading = metrics.getLeading();\r
+\r
+ // Determine width of space glyph (getGlyphPixelBounds gives a width of zero).\r
+ char[] chars = " ".toCharArray();\r
+ GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);\r
+ spaceWidth = vector.getGlyphLogicalBounds(0).getBounds().width;\r
+ }\r
+\r
+ private void loadSettings (HieroSettings settings) {\r
+ paddingTop = settings.getPaddingTop();\r
+ paddingLeft = settings.getPaddingLeft();\r
+ paddingBottom = settings.getPaddingBottom();\r
+ paddingRight = settings.getPaddingRight();\r
+ paddingAdvanceX = settings.getPaddingAdvanceX();\r
+ paddingAdvanceY = settings.getPaddingAdvanceY();\r
+ glyphPageWidth = settings.getGlyphPageWidth();\r
+ glyphPageHeight = settings.getGlyphPageHeight();\r
+ effects.addAll(settings.getEffects());\r
+ }\r
+\r
+ /**\r
+ * Queues the glyphs in the specified codepoint range (inclusive) to be loaded. Note that the glyphs are not actually loaded\r
+ * until {@link #loadGlyphs()} is called.\r
+ * \r
+ * Some characters like combining marks and non-spacing marks can only be rendered with the context of other glyphs. In this\r
+ * case, use {@link #addGlyphs(String)}.\r
+ */\r
+ public void addGlyphs (int startCodePoint, int endCodePoint) {\r
+ for (int codePoint = startCodePoint; codePoint <= endCodePoint; codePoint++)\r
+ addGlyphs(new String(Character.toChars(codePoint)));\r
+ }\r
+\r
+ /**\r
+ * Queues the glyphs in the specified text to be loaded. Note that the glyphs are not actually loaded until\r
+ * {@link #loadGlyphs()} is called.\r
+ */\r
+ public void addGlyphs (String text) {\r
+ if (text == null) throw new IllegalArgumentException("text cannot be null.");\r
+\r
+ char[] chars = text.toCharArray();\r
+ GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);\r
+ for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {\r
+ int codePoint = text.codePointAt(vector.getGlyphCharIndex(i));\r
+ Rectangle bounds = getGlyphBounds(vector, i, codePoint);\r
+ getGlyph(vector.getGlyphCode(i), codePoint, bounds, vector, i);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Queues the glyphs in the ASCII character set (codepoints 32 through 255) to be loaded. Note that the glyphs are not actually\r
+ * loaded until {@link #loadGlyphs()} is called.\r
+ */\r
+ public void addAsciiGlyphs () {\r
+ addGlyphs(32, 255);\r
+ }\r
+\r
+ /**\r
+ * Queues the glyphs in the NEHE character set (codepoints 32 through 128) to be loaded. Note that the glyphs are not actually\r
+ * loaded until {@link #loadGlyphs()} is called.\r
+ */\r
+ public void addNeheGlyphs () {\r
+ addGlyphs(32, 32 + 96);\r
+ }\r
+\r
+ /**\r
+ * Loads all queued glyphs to the backing textures. Glyphs that are typically displayed together should be added and loaded at\r
+ * the same time so that they are stored on the same backing texture. This reduces the number of backing texture binds required\r
+ * to draw glyphs.\r
+ */\r
+ public boolean loadGlyphs () {\r
+ return loadGlyphs(-1);\r
+ }\r
+\r
+ /**\r
+ * Loads up to the specified number of queued glyphs to the backing textures. This is typically called from the game loop to\r
+ * load glyphs on the fly that were requested for display but have not yet been loaded.\r
+ */\r
+ public boolean loadGlyphs (int maxGlyphsToLoad) {\r
+ if (queuedGlyphs.isEmpty()) return false;\r
+\r
+ if (effects.isEmpty())\r
+ throw new IllegalStateException("The UnicodeFont must have at least one effect before any glyphs can be loaded.");\r
+\r
+ for (Iterator iter = queuedGlyphs.iterator(); iter.hasNext();) {\r
+ Glyph glyph = (Glyph)iter.next();\r
+ int codePoint = glyph.getCodePoint();\r
+\r
+ // Don't load an image for a glyph with nothing to display.\r
+ if (glyph.getWidth() == 0 || codePoint == ' ') {\r
+ iter.remove();\r
+ continue;\r
+ }\r
+\r
+ // Only load the first missing glyph.\r
+ if (glyph.isMissing()) {\r
+ if (missingGlyph != null) {\r
+ if (glyph != missingGlyph) iter.remove();\r
+ continue;\r
+ }\r
+ missingGlyph = glyph;\r
+ }\r
+ }\r
+\r
+ Collections.sort(queuedGlyphs, heightComparator);\r
+\r
+ // Add to existing pages.\r
+ for (Iterator iter = glyphPages.iterator(); iter.hasNext();) {\r
+ GlyphPage glyphPage = (GlyphPage)iter.next();\r
+ maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad);\r
+ if (maxGlyphsToLoad == 0 || queuedGlyphs.isEmpty()) return true;\r
+ }\r
+\r
+ // Add to new pages.\r
+ while (!queuedGlyphs.isEmpty()) {\r
+ GlyphPage glyphPage = new GlyphPage(this, glyphPageWidth, glyphPageHeight);\r
+ glyphPages.add(glyphPage);\r
+ maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad);\r
+ if (maxGlyphsToLoad == 0) return true;\r
+ }\r
+\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Clears all loaded and queued glyphs.\r
+ */\r
+ public void clearGlyphs () {\r
+ for (int i = 0; i < PAGES; i++)\r
+ glyphs[i] = null;\r
+\r
+ for (Iterator iter = glyphPages.iterator(); iter.hasNext();) {\r
+ GlyphPage page = (GlyphPage)iter.next();\r
+ page.getTexture().dispose();\r
+ }\r
+ glyphPages.clear();\r
+\r
+ if (baseDisplayListID != -1) {\r
+ GL11.glDeleteLists(baseDisplayListID, displayLists.size());\r
+ baseDisplayListID = -1;\r
+ }\r
+\r
+ queuedGlyphs.clear();\r
+ missingGlyph = null;\r
+ }\r
+\r
+ /**\r
+ * Releases all resources used by this UnicodeFont. This method should be called when this UnicodeFont instance is no longer\r
+ * needed.\r
+ */\r
+ public void destroy () {\r
+ // The destroy() method is just to provide a consistent API for releasing resources.\r
+ clearGlyphs();\r
+ }\r
+\r
+ /**\r
+ * Identical to {@link #drawString(float, float, String, Color, int, int)} but returns a DisplayList which provides access to\r
+ * the width and height of the text drawn.\r
+ */\r
+ public DisplayList drawDisplayList (float x, float y, String text, Color color, int startIndex, int endIndex) {\r
+ if (text == null) throw new IllegalArgumentException("text cannot be null.");\r
+ if (text.length() == 0) return emptyDisplayList;\r
+ if (color == null) throw new IllegalArgumentException("color cannot be null.");\r
+\r
+ x -= paddingLeft;\r
+ y -= paddingTop;\r
+\r
+ String displayListKey = text.substring(startIndex, endIndex);\r
+\r
+ GL11.glColor4f(color.r, color.g, color.b, color.a);\r
+\r
+ DisplayList displayList = null;\r
+ if (displayListCaching && queuedGlyphs.isEmpty()) {\r
+ if (baseDisplayListID == -1) {\r
+ baseDisplayListID = GL11.glGenLists(DISPLAY_LIST_CACHE_SIZE);\r
+ if (baseDisplayListID == 0) {\r
+ baseDisplayListID = -1;\r
+ displayListCaching = false;\r
+ return new DisplayList();\r
+ }\r
+ }\r
+ // Try to use a display list compiled for this text.\r
+ displayList = (DisplayList)displayLists.get(displayListKey);\r
+ if (displayList != null) {\r
+ if (displayList.invalid)\r
+ displayList.invalid = false;\r
+ else {\r
+ GL11.glTranslatef(x, y, 0);\r
+ GL11.glCallList(displayList.id);\r
+ GL11.glTranslatef(-x, -y, 0);\r
+ return displayList;\r
+ }\r
+ } else if (displayList == null) {\r
+ // Compile a new display list.\r
+ displayList = new DisplayList();\r
+ int displayListCount = displayLists.size();\r
+ displayLists.put(displayListKey, displayList);\r
+ if (displayListCount < DISPLAY_LIST_CACHE_SIZE)\r
+ displayList.id = baseDisplayListID + displayListCount;\r
+ else\r
+ displayList.id = eldestDisplayListID;\r
+ }\r
+ }\r
+\r
+ GL11.glTranslatef(x, y, 0);\r
+\r
+ if (displayList != null) GL11.glNewList(displayList.id, GL11.GL_COMPILE_AND_EXECUTE);\r
+\r
+ char[] chars = text.substring(0, endIndex).toCharArray();\r
+ GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);\r
+\r
+ int maxWidth = 0, totalHeight = 0, lines = 0;\r
+ int extraX = 0, extraY = ascent;\r
+ boolean startNewLine = false;\r
+ Texture lastBind = null;\r
+ for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {\r
+ int charIndex = vector.getGlyphCharIndex(glyphIndex);\r
+ if (charIndex < startIndex) continue;\r
+ if (charIndex > endIndex) break;\r
+\r
+ int codePoint = text.codePointAt(charIndex);\r
+\r
+ Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint);\r
+ Glyph glyph = getGlyph(vector.getGlyphCode(glyphIndex), codePoint, bounds, vector, glyphIndex);\r
+\r
+ if (startNewLine && codePoint != '\n') {\r
+ extraX = -bounds.x;\r
+ startNewLine = false;\r
+ }\r
+\r
+ if (glyph.getTexture() == null && missingGlyph != null && glyph.isMissing()) glyph = missingGlyph;\r
+ if (glyph.getTexture() != null) {\r
+ // Draw glyph, only binding a new glyph page texture when necessary.\r
+ Texture texture = glyph.getTexture();\r
+ if (lastBind != null && lastBind != texture) {\r
+ GL11.glEnd();\r
+ lastBind = null;\r
+ }\r
+ if (lastBind == null) {\r
+ texture.bind();\r
+ GL11.glBegin(GL11.GL_QUADS);\r
+ lastBind = texture;\r
+ }\r
+ int glyphX = bounds.x + extraX;\r
+ int glyphY = bounds.y + extraY;\r
+ GL11.glTexCoord2f(glyph.getU(), glyph.getV());\r
+ GL11.glVertex3f(glyphX, glyphY, 0);\r
+ GL11.glTexCoord2f(glyph.getU(), glyph.getV2());\r
+ GL11.glVertex3f(glyphX, glyphY + glyph.getHeight(), 0);\r
+ GL11.glTexCoord2f(glyph.getU2(), glyph.getV2());\r
+ GL11.glVertex3f(glyphX + glyph.getWidth(), glyphY + glyph.getHeight(), 0);\r
+ GL11.glTexCoord2f(glyph.getU2(), glyph.getV());\r
+ GL11.glVertex3f(glyphX + glyph.getWidth(), glyphY, 0);\r
+ }\r
+\r
+ if (glyphIndex > 0) extraX += paddingRight + paddingLeft + paddingAdvanceX;\r
+ maxWidth = Math.max(maxWidth, bounds.x + extraX + bounds.width);\r
+ totalHeight = Math.max(totalHeight, ascent + bounds.y + bounds.height);\r
+\r
+ if (codePoint == '\n') {\r
+ startNewLine = true; // Mac gives -1 for bounds.x of '\n', so use the bounds.x of the next glyph.\r
+ extraY += getLineHeight();\r
+ lines++;\r
+ totalHeight = 0;\r
+ }\r
+ }\r
+ if (lastBind != null) GL11.glEnd();\r
+\r
+ if (displayList != null) {\r
+ GL11.glEndList();\r
+ // Invalidate the display list if it had glyphs that need to be loaded.\r
+ if (!queuedGlyphs.isEmpty()) displayList.invalid = true;\r
+ }\r
+\r
+ GL11.glTranslatef(-x, -y, 0);\r
+\r
+ if (displayList == null) displayList = new DisplayList();\r
+ displayList.width = (short)maxWidth;\r
+ displayList.height = (short)(lines * getLineHeight() + totalHeight);\r
+ return displayList;\r
+ }\r
+\r
+ public void drawString (float x, float y, String text, Color color, int startIndex, int endIndex) {\r
+ drawDisplayList(x, y, text, color, startIndex, endIndex);\r
+ }\r
+\r
+ public void drawString (float x, float y, String text) {\r
+ drawString(x, y, text, Color.WHITE);\r
+ }\r
+\r
+ public void drawString (float x, float y, String text, Color col) {\r
+ drawString(x, y, text, col, 0, text.length());\r
+ }\r
+\r
+ /**\r
+ * Returns the glyph for the specified codePoint. If the glyph does not exist yet, it is created and queued to be loaded.\r
+ */\r
+ private Glyph getGlyph (int glyphCode, int codePoint, Rectangle bounds, GlyphVector vector, int index) {\r
+ if (glyphCode < 0 || glyphCode >= MAX_GLYPH_CODE) {\r
+ // GlyphVector#getGlyphCode sometimes returns negative numbers on OS X!?\r
+ return new Glyph(codePoint, bounds, vector, index, this) {\r
+ public boolean isMissing () {\r
+ return true;\r
+ }\r
+ };\r
+ }\r
+ int pageIndex = glyphCode / PAGE_SIZE;\r
+ int glyphIndex = glyphCode & (PAGE_SIZE - 1);\r
+ Glyph glyph = null;\r
+ Glyph[] page = glyphs[pageIndex];\r
+ if (page != null) {\r
+ glyph = page[glyphIndex];\r
+ if (glyph != null) return glyph;\r
+ } else\r
+ page = glyphs[pageIndex] = new Glyph[PAGE_SIZE];\r
+ // Add glyph so size information is available and queue it so its image can be loaded later.\r
+ glyph = page[glyphIndex] = new Glyph(codePoint, bounds, vector, index, this);\r
+ queuedGlyphs.add(glyph);\r
+ return glyph;\r
+ }\r
+\r
+ private Rectangle getGlyphBounds (GlyphVector vector, int index, int codePoint) {\r
+ Rectangle bounds = vector.getGlyphPixelBounds(index, GlyphPage.renderContext, 0, 0);\r
+ if (codePoint == ' ') bounds.width = spaceWidth;\r
+ return bounds;\r
+ }\r
+\r
+ public int getSpaceWidth () {\r
+ return spaceWidth;\r
+ }\r
+\r
+ public int getWidth (String text) {\r
+ if (text == null) throw new IllegalArgumentException("text cannot be null.");\r
+ if (text.length() == 0) return 0;\r
+\r
+ if (displayListCaching) {\r
+ DisplayList displayList = (DisplayList)displayLists.get(text);\r
+ if (displayList != null) return displayList.width;\r
+ }\r
+\r
+ char[] chars = text.toCharArray();\r
+ GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);\r
+\r
+ int width = 0;\r
+ int extraX = 0;\r
+ boolean startNewLine = false;\r
+ for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {\r
+ int charIndex = vector.getGlyphCharIndex(glyphIndex);\r
+ int codePoint = text.codePointAt(charIndex);\r
+ Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint);\r
+\r
+ if (startNewLine && codePoint != '\n') extraX = -bounds.x;\r
+\r
+ if (glyphIndex > 0) extraX += paddingLeft + paddingRight + paddingAdvanceX;\r
+ width = Math.max(width, bounds.x + extraX + bounds.width);\r
+\r
+ if (codePoint == '\n') startNewLine = true;\r
+ }\r
+\r
+ return width;\r
+ }\r
+\r
+ public int getHeight (String text) {\r
+ if (text == null) throw new IllegalArgumentException("text cannot be null.");\r
+ if (text.length() == 0) return 0;\r
+\r
+ if (displayListCaching) {\r
+ DisplayList displayList = (DisplayList)displayLists.get(text);\r
+ if (displayList != null) return displayList.height;\r
+ }\r
+\r
+ char[] chars = text.toCharArray();\r
+ GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);\r
+\r
+ int lines = 0, height = 0;\r
+ for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {\r
+ int charIndex = vector.getGlyphCharIndex(i);\r
+ int codePoint = text.codePointAt(charIndex);\r
+ if (codePoint == ' ') continue;\r
+ Rectangle bounds = getGlyphBounds(vector, i, codePoint);\r
+\r
+ height = Math.max(height, ascent + bounds.y + bounds.height);\r
+\r
+ if (codePoint == '\n') {\r
+ lines++;\r
+ height = 0;\r
+ }\r
+ }\r
+ return lines * getLineHeight() + height;\r
+ }\r
+\r
+ /**\r
+ * Returns the distance from the y drawing location to the top most pixel of the specified text.\r
+ */\r
+ public int getYOffset (String text) {\r
+ if (text == null) throw new IllegalArgumentException("text cannot be null.");\r
+\r
+ DisplayList displayList = null;\r
+ if (displayListCaching) {\r
+ displayList = (DisplayList)displayLists.get(text);\r
+ if (displayList != null && displayList.yOffset != null) return displayList.yOffset.intValue();\r
+ }\r
+\r
+ int index = text.indexOf('\n');\r
+ if (index != -1) text = text.substring(0, index);\r
+ char[] chars = text.toCharArray();\r
+ GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);\r
+ int yOffset = ascent + vector.getPixelBounds(null, 0, 0).y;\r
+\r
+ if (displayList != null) displayList.yOffset = new Short((short)yOffset);\r
+\r
+ return yOffset;\r
+ }\r
+\r
+ /**\r
+ * Returns the TrueTypeFont for this UnicodeFont.\r
+ */\r
+ public Font getFont () {\r
+ return font;\r
+ }\r
+\r
+ /**\r
+ * Returns the padding above a glyph on the GlyphPage to allow for effects to be drawn.\r
+ */\r
+ public int getPaddingTop () {\r
+ return paddingTop;\r
+ }\r
+\r
+ /**\r
+ * Sets the padding above a glyph on the GlyphPage to allow for effects to be drawn.\r
+ */\r
+ public void setPaddingTop (int paddingTop) {\r
+ this.paddingTop = paddingTop;\r
+ }\r
+\r
+ /**\r
+ * Returns the padding to the left of a glyph on the GlyphPage to allow for effects to be drawn.\r
+ */\r
+ public int getPaddingLeft () {\r
+ return paddingLeft;\r
+ }\r
+\r
+ /**\r
+ * Sets the padding to the left of a glyph on the GlyphPage to allow for effects to be drawn.\r
+ */\r
+ public void setPaddingLeft (int paddingLeft) {\r
+ this.paddingLeft = paddingLeft;\r
+ }\r
+\r
+ /**\r
+ * Returns the padding below a glyph on the GlyphPage to allow for effects to be drawn.\r
+ */\r
+ public int getPaddingBottom () {\r
+ return paddingBottom;\r
+ }\r
+\r
+ /**\r
+ * Sets the padding below a glyph on the GlyphPage to allow for effects to be drawn.\r
+ */\r
+ public void setPaddingBottom (int paddingBottom) {\r
+ this.paddingBottom = paddingBottom;\r
+ }\r
+\r
+ /**\r
+ * Returns the padding to the right of a glyph on the GlyphPage to allow for effects to be drawn.\r
+ */\r
+ public int getPaddingRight () {\r
+ return paddingRight;\r
+ }\r
+\r
+ /**\r
+ * Sets the padding to the right of a glyph on the GlyphPage to allow for effects to be drawn.\r
+ */\r
+ public void setPaddingRight (int paddingRight) {\r
+ this.paddingRight = paddingRight;\r
+ }\r
+\r
+ /**\r
+ * Gets the additional amount to offset glyphs on the x axis.\r
+ */\r
+ public int getPaddingAdvanceX () {\r
+ return paddingAdvanceX;\r
+ }\r
+\r
+ /**\r
+ * Sets the additional amount to offset glyphs on the x axis. This is typically set to a negative number when left or right\r
+ * padding is used so that glyphs are not spaced too far apart.\r
+ */\r
+ public void setPaddingAdvanceX (int paddingAdvanceX) {\r
+ this.paddingAdvanceX = paddingAdvanceX;\r
+ }\r
+\r
+ /**\r
+ * Gets the additional amount to offset a line of text on the y axis.\r
+ */\r
+ public int getPaddingAdvanceY () {\r
+ return paddingAdvanceY;\r
+ }\r
+\r
+ /**\r
+ * Sets the additional amount to offset a line of text on the y axis. This is typically set to a negative number when top or\r
+ * bottom padding is used so that lines of text are not spaced too far apart.\r
+ */\r
+ public void setPaddingAdvanceY (int paddingAdvanceY) {\r
+ this.paddingAdvanceY = paddingAdvanceY;\r
+ }\r
+\r
+ /**\r
+ * Returns the distance from one line of text to the next. This is the sum of the descent, ascent, leading, padding top,\r
+ * padding bottom, and padding advance y. To change the line height, use {@link #setPaddingAdvanceY(int)}.\r
+ */\r
+ public int getLineHeight () {\r
+ return descent + ascent + leading + paddingTop + paddingBottom + paddingAdvanceY;\r
+ }\r
+\r
+ /**\r
+ * Gets the distance from the baseline to the y drawing location.\r
+ */\r
+ public int getAscent () {\r
+ return ascent;\r
+ }\r
+\r
+ /**\r
+ * Gets the distance from the baseline to the bottom of most alphanumeric characters with descenders.\r
+ */\r
+ public int getDescent () {\r
+ return descent;\r
+ }\r
+\r
+ /**\r
+ * Gets the extra distance between the descent of one line of text to the ascent of the next.\r
+ */\r
+ public int getLeading () {\r
+ return leading;\r
+ }\r
+\r
+ /**\r
+ * Returns the width of the backing textures.\r
+ */\r
+ public int getGlyphPageWidth () {\r
+ return glyphPageWidth;\r
+ }\r
+\r
+ /**\r
+ * Sets the width of the backing textures. Default is 512.\r
+ */\r
+ public void setGlyphPageWidth (int glyphPageWidth) {\r
+ this.glyphPageWidth = glyphPageWidth;\r
+ }\r
+\r
+ /**\r
+ * Returns the height of the backing textures.\r
+ */\r
+ public int getGlyphPageHeight () {\r
+ return glyphPageHeight;\r
+ }\r
+\r
+ /**\r
+ * Sets the height of the backing textures. Default is 512.\r
+ */\r
+ public void setGlyphPageHeight (int glyphPageHeight) {\r
+ this.glyphPageHeight = glyphPageHeight;\r
+ }\r
+\r
+ /**\r
+ * Returns the GlyphPages for this UnicodeFont.\r
+ */\r
+ public List getGlyphPages () {\r
+ return glyphPages;\r
+ }\r
+\r
+ /**\r
+ * Returns a list of {@link com.badlogic.gdx.hiero.unicodefont.effects.Effect}s that will be applied to the glyphs.\r
+ */\r
+ public List getEffects () {\r
+ return effects;\r
+ }\r
+\r
+ /**\r
+ * Returns true if this UnicodeFont caches the glyph drawing instructions to improve performance.\r
+ */\r
+ public boolean isCaching () {\r
+ return displayListCaching;\r
+ }\r
+\r
+ /**\r
+ * Sets if this UnicodeFont caches the glyph drawing instructions to improve performance. Default is true. Text rendering is\r
+ * very slow without display list caching.\r
+ */\r
+ public void setDisplayListCaching (boolean displayListCaching) {\r
+ this.displayListCaching = displayListCaching;\r
+ }\r
+\r
+ /**\r
+ * Returns the path to the TTF file for this UnicodeFont, or null. If this UnicodeFont was created without specifying the TTF\r
+ * file, it will try to determine the path using Sun classes. If this fails, null is returned.\r
+ */\r
+ public String getFontFile () {\r
+ if (ttfFileRef == null) {\r
+ // Worst case if this UnicodeFont was loaded without a ttfFileRef, try to get the font file from Sun's classes.\r
+ try {\r
+ Object font2D = Class.forName("sun.font.FontManager").getDeclaredMethod("getFont2D", new Class[] {Font.class})\r
+ .invoke(null, new Object[] {font});\r
+ Field platNameField = Class.forName("sun.font.PhysicalFont").getDeclaredField("platName");\r
+ platNameField.setAccessible(true);\r
+ ttfFileRef = (String)platNameField.get(font2D);\r
+ } catch (Throwable ignored) {\r
+ }\r
+ if (ttfFileRef == null) ttfFileRef = "";\r
+ }\r
+ if (ttfFileRef.length() == 0) return null;\r
+ return ttfFileRef;\r
+ }\r
+\r
+ /**\r
+ * @param ttfFileRef The file system or classpath location of the TrueTypeFont file.\r
+ */\r
+ static private Font createFont (String ttfFileRef) {\r
+ try {\r
+ return Font.createFont(Font.TRUETYPE_FONT, Gdx.files.readFile(ttfFileRef, FileType.Absolute));\r
+ } catch (FontFormatException ex) {\r
+ throw new GdxRuntimeException("Invalid font: " + ttfFileRef, ex);\r
+ } catch (IOException ex) {\r
+ throw new GdxRuntimeException("Error reading font: " + ttfFileRef, ex);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Sorts glyphs by height, tallest first.\r
+ */\r
+ static private final Comparator heightComparator = new Comparator() {\r
+ public int compare (Object o1, Object o2) {\r
+ return ((Glyph)o1).getHeight() - ((Glyph)o2).getHeight();\r
+ }\r
+ };\r
+\r
+ public class DisplayList {\r
+ boolean invalid;\r
+ int id;\r
+ Short yOffset;\r
+\r
+ public short width, height;\r
+ public Object userData;\r
+\r
+ DisplayList () {\r
+ }\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont;\r
+\r
+import static org.lwjgl.opengl.GL11.*;\r
+\r
+import org.lwjgl.opengl.GL11;\r
+\r
+import com.badlogic.gdx.Gdx;\r
+import com.badlogic.gdx.RenderListener;\r
+import com.badlogic.gdx.backends.desktop.LwjglApplication;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.ColorEffect;\r
+\r
+public class UnicodeFontTest implements RenderListener {\r
+ private UnicodeFont unicodeFont;\r
+\r
+ public void surfaceCreated () {\r
+ unicodeFont = new UnicodeFont("c:/windows/fonts/arial.ttf", 48, false, false);\r
+ unicodeFont.getEffects().add(new ColorEffect(java.awt.Color.white));\r
+ // unicodeFont.addAsciiGlyphs();\r
+ // unicodeFont.loadGlyphs();\r
+ }\r
+\r
+ public void surfaceChanged (int width, int height) {\r
+ glViewport(0, 0, width, height);\r
+ glScissor(0, 0, width, height);\r
+ glEnable(GL_SCISSOR_TEST);\r
+\r
+ glMatrixMode(GL_PROJECTION);\r
+ glLoadIdentity();\r
+ glOrtho(0, width, height, 0, 1, -1);\r
+ glMatrixMode(GL_MODELVIEW);\r
+ glLoadIdentity();\r
+\r
+ glEnable(GL_TEXTURE_2D);\r
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);\r
+ glEnableClientState(GL_VERTEX_ARRAY);\r
+\r
+ glClearColor(0, 0, 0, 0);\r
+ glClearDepth(1);\r
+\r
+ glDisable(GL_LIGHTING);\r
+\r
+ glEnable(GL_BLEND);\r
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\r
+ }\r
+\r
+ public void render () {\r
+ GL11.glClear(GL_COLOR_BUFFER_BIT);\r
+\r
+ unicodeFont.loadGlyphs(1);\r
+\r
+ String text = "This is UnicodeFont!\nIt rockz. Kerning: T,";\r
+ unicodeFont.setDisplayListCaching(false);\r
+ unicodeFont.drawString(10, 33, text);\r
+ unicodeFont.drawString(10, 330, text);\r
+\r
+ unicodeFont.addGlyphs("~!@!#!#$%___--");\r
+ // Cypriot Syllabary glyphs (Everson Mono font): \uD802\uDC02\uD802\uDC03\uD802\uDC12 == 0x10802, 0x10803, s0x10812\r
+ }\r
+\r
+ public void dispose () {\r
+ }\r
+\r
+ public static void main (String[] args) {\r
+ LwjglApplication app = new LwjglApplication("UnicodeFont Test", 800, 600, false);\r
+ app.getGraphics().setRenderListener(new UnicodeFontTest());\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont.effects;\r
+\r
+import java.awt.Color;\r
+import java.awt.Graphics2D;\r
+import java.awt.image.BufferedImage;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import com.badlogic.gdx.hiero.unicodefont.Glyph;\r
+import com.badlogic.gdx.hiero.unicodefont.UnicodeFont;\r
+\r
+/**\r
+ * Makes glyphs a solid color.\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class ColorEffect implements ConfigurableEffect {\r
+ private Color color = Color.white;\r
+\r
+ public ColorEffect () {\r
+ }\r
+\r
+ public ColorEffect (Color color) {\r
+ this.color = color;\r
+ }\r
+\r
+ public void draw (BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {\r
+ g.setColor(color);\r
+ g.fill(glyph.getShape());\r
+ }\r
+\r
+ public Color getColor () {\r
+ return color;\r
+ }\r
+\r
+ public void setColor (Color color) {\r
+ if (color == null) throw new IllegalArgumentException("color cannot be null.");\r
+ this.color = color;\r
+ }\r
+\r
+ public String toString () {\r
+ return "Color";\r
+ }\r
+\r
+ public List getValues () {\r
+ List values = new ArrayList();\r
+ values.add(EffectUtil.colorValue("Color", color));\r
+ return values;\r
+ }\r
+\r
+ public void setValues (List values) {\r
+ for (Iterator iter = values.iterator(); iter.hasNext();) {\r
+ Value value = (Value)iter.next();\r
+ if (value.getName().equals("Color")) {\r
+ setColor((Color)value.getObject());\r
+ }\r
+ }\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont.effects;\r
+\r
+import java.util.List;\r
+\r
+/**\r
+ * An effect that has a number of configuration values. This allows the effect to be configured in the Hiero GUI and to be saved\r
+ * and loaded to and from a file.\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public interface ConfigurableEffect extends Effect {\r
+ /**\r
+ * Returns the list of {@link Value}s for this effect. This list is not typically backed by the effect, so changes to the\r
+ * values will not take affect until {@link #setValues(List)} is called.\r
+ */\r
+ public List getValues ();\r
+\r
+ /**\r
+ * Sets the list of {@link Value}s for this effect.\r
+ */\r
+ public void setValues (List values);\r
+\r
+ /**\r
+ * Represents a configurable value for an effect.\r
+ */\r
+ static public interface Value {\r
+ /**\r
+ * Returns the name of the value.\r
+ */\r
+ public String getName ();\r
+\r
+ /**\r
+ * Sets the string representation of the value.\r
+ */\r
+ public void setString (String value);\r
+\r
+ /**\r
+ * Gets the string representation of the value.\r
+ */\r
+ public String getString ();\r
+\r
+ /**\r
+ * Gets the object representation of the value.\r
+ */\r
+ public Object getObject ();\r
+\r
+ /**\r
+ * Shows a dialog allowing a user to configure this value.\r
+ */\r
+ public void showDialog ();\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont.effects;\r
+\r
+import java.awt.Graphics2D;\r
+import java.awt.image.BufferedImage;\r
+\r
+import com.badlogic.gdx.hiero.unicodefont.Glyph;\r
+import com.badlogic.gdx.hiero.unicodefont.UnicodeFont;\r
+\r
+/**\r
+ * A graphical effect that is applied to glyphs in a {@link UnicodeFont}.\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public interface Effect {\r
+ /**\r
+ * Called to draw the effect.\r
+ */\r
+ public void draw (BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph);\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont.effects;\r
+\r
+import java.awt.AlphaComposite;\r
+import java.awt.Color;\r
+import java.awt.Dimension;\r
+import java.awt.EventQueue;\r
+import java.awt.Graphics2D;\r
+import java.awt.GridBagConstraints;\r
+import java.awt.GridBagLayout;\r
+import java.awt.Insets;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.awt.image.BufferedImage;\r
+\r
+import javax.swing.BorderFactory;\r
+import javax.swing.DefaultComboBoxModel;\r
+import javax.swing.JButton;\r
+import javax.swing.JCheckBox;\r
+import javax.swing.JColorChooser;\r
+import javax.swing.JComboBox;\r
+import javax.swing.JComponent;\r
+import javax.swing.JDialog;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JSpinner;\r
+import javax.swing.JTextArea;\r
+import javax.swing.SpinnerNumberModel;\r
+\r
+import com.badlogic.gdx.hiero.unicodefont.GlyphPage;\r
+import com.badlogic.gdx.hiero.unicodefont.effects.ConfigurableEffect.Value;\r
+\r
+/**\r
+ * Provides utility methods for effects.\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class EffectUtil {\r
+ static private BufferedImage scratchImage = new BufferedImage(GlyphPage.MAX_GLYPH_SIZE, GlyphPage.MAX_GLYPH_SIZE,\r
+ BufferedImage.TYPE_INT_ARGB);\r
+\r
+ /**\r
+ * Returns an image that can be used by effects as a temp image.\r
+ */\r
+ static public BufferedImage getScratchImage () {\r
+ Graphics2D g = (Graphics2D)scratchImage.getGraphics();\r
+ g.setComposite(AlphaComposite.Clear);\r
+ g.fillRect(0, 0, GlyphPage.MAX_GLYPH_SIZE, GlyphPage.MAX_GLYPH_SIZE);\r
+ g.setComposite(AlphaComposite.SrcOver);\r
+ g.setColor(java.awt.Color.white);\r
+ return scratchImage;\r
+ }\r
+\r
+ /**\r
+ * Returns a value that represents a color.\r
+ */\r
+ static public Value colorValue (String name, Color currentValue) {\r
+ return new DefaultValue(name, EffectUtil.toString(currentValue)) {\r
+ public void showDialog () {\r
+ Color newColor = JColorChooser.showDialog(null, "Choose a color", EffectUtil.fromString(value));\r
+ if (newColor != null) value = EffectUtil.toString(newColor);\r
+ }\r
+\r
+ public Object getObject () {\r
+ return EffectUtil.fromString(value);\r
+ }\r
+ };\r
+ }\r
+\r
+ /**\r
+ * Returns a value that represents an int.\r
+ */\r
+ static public Value intValue (String name, final int currentValue, final String description) {\r
+ return new DefaultValue(name, String.valueOf(currentValue)) {\r
+ public void showDialog () {\r
+ JSpinner spinner = new JSpinner(new SpinnerNumberModel(currentValue, Short.MIN_VALUE, Short.MAX_VALUE, 1));\r
+ if (showValueDialog(spinner, description)) value = String.valueOf(spinner.getValue());\r
+ }\r
+\r
+ public Object getObject () {\r
+ return Integer.valueOf(value);\r
+ }\r
+ };\r
+ }\r
+\r
+ /**\r
+ * Returns a value that represents a float, from 0 to 1 (inclusive).\r
+ */\r
+ static public Value floatValue (String name, final float currentValue, final float min, final float max,\r
+ final String description) {\r
+ return new DefaultValue(name, String.valueOf(currentValue)) {\r
+ public void showDialog () {\r
+ JSpinner spinner = new JSpinner(new SpinnerNumberModel(currentValue, min, max, 0.1f));\r
+ if (showValueDialog(spinner, description)) value = String.valueOf(((Double)spinner.getValue()).floatValue());\r
+ }\r
+\r
+ public Object getObject () {\r
+ return Float.valueOf(value);\r
+ }\r
+ };\r
+ }\r
+\r
+ /**\r
+ * Returns a value that represents a boolean.\r
+ */\r
+ static public Value booleanValue (String name, final boolean currentValue, final String description) {\r
+ return new DefaultValue(name, String.valueOf(currentValue)) {\r
+ public void showDialog () {\r
+ JCheckBox checkBox = new JCheckBox();\r
+ checkBox.setSelected(currentValue);\r
+ if (showValueDialog(checkBox, description)) value = String.valueOf(checkBox.isSelected());\r
+ }\r
+\r
+ public Object getObject () {\r
+ return Boolean.valueOf(value);\r
+ }\r
+ };\r
+ }\r
+\r
+ /**\r
+ * Returns a value that represents a fixed number of options. All options are strings.\r
+ * @param options The first array has an entry for each option. Each entry is either a String[1] that is both the display value\r
+ * and actual value, or a String[2] whose first element is the display value and second element is the actual value.\r
+ */\r
+ static public Value optionValue (String name, final String currentValue, final String[][] options, final String description) {\r
+ return new DefaultValue(name, currentValue.toString()) {\r
+ public void showDialog () {\r
+ int selectedIndex = -1;\r
+ DefaultComboBoxModel model = new DefaultComboBoxModel();\r
+ for (int i = 0; i < options.length; i++) {\r
+ model.addElement(options[i][0]);\r
+ if (getValue(i).equals(currentValue)) selectedIndex = i;\r
+ }\r
+ JComboBox comboBox = new JComboBox(model);\r
+ comboBox.setSelectedIndex(selectedIndex);\r
+ if (showValueDialog(comboBox, description)) value = getValue(comboBox.getSelectedIndex());\r
+ }\r
+\r
+ private String getValue (int i) {\r
+ if (options[i].length == 1) return options[i][0];\r
+ return options[i][1];\r
+ }\r
+\r
+ public String toString () {\r
+ for (int i = 0; i < options.length; i++)\r
+ if (getValue(i).equals(value)) return options[i][0].toString();\r
+ return "";\r
+ }\r
+\r
+ public Object getObject () {\r
+ return value;\r
+ }\r
+ };\r
+ }\r
+\r
+ /**\r
+ * Convers a color to a string.\r
+ */\r
+ static public String toString (Color color) {\r
+ if (color == null) throw new IllegalArgumentException("color cannot be null.");\r
+ String r = Integer.toHexString(color.getRed());\r
+ if (r.length() == 1) r = "0" + r;\r
+ String g = Integer.toHexString(color.getGreen());\r
+ if (g.length() == 1) g = "0" + g;\r
+ String b = Integer.toHexString(color.getBlue());\r
+ if (b.length() == 1) b = "0" + b;\r
+ return r + g + b;\r
+ }\r
+\r
+ /**\r
+ * Converts a string to a color.\r
+ */\r
+ static public Color fromString (String rgb) {\r
+ if (rgb == null || rgb.length() != 6) return Color.white;\r
+ return new Color(Integer.parseInt(rgb.substring(0, 2), 16), Integer.parseInt(rgb.substring(2, 4), 16), Integer.parseInt(rgb\r
+ .substring(4, 6), 16));\r
+ }\r
+\r
+ /**\r
+ * Provides generic functionality for an effect's configurable value.\r
+ */\r
+ static private abstract class DefaultValue implements Value {\r
+ String value;\r
+ String name;\r
+\r
+ public DefaultValue (String name, String value) {\r
+ this.value = value;\r
+ this.name = name;\r
+ }\r
+\r
+ public void setString (String value) {\r
+ this.value = value;\r
+ }\r
+\r
+ public String getString () {\r
+ return value;\r
+ }\r
+\r
+ public String getName () {\r
+ return name;\r
+ }\r
+\r
+ public String toString () {\r
+ if (value == null) return "";\r
+ return value.toString();\r
+ }\r
+\r
+ public boolean showValueDialog (final JComponent component, String description) {\r
+ ValueDialog dialog = new ValueDialog(component, name, description);\r
+ dialog.setTitle(name);\r
+ dialog.setLocationRelativeTo(null);\r
+ EventQueue.invokeLater(new Runnable() {\r
+ public void run () {\r
+ JComponent focusComponent = component;\r
+ if (focusComponent instanceof JSpinner)\r
+ focusComponent = ((JSpinner.DefaultEditor)((JSpinner)component).getEditor()).getTextField();\r
+ focusComponent.requestFocusInWindow();\r
+ }\r
+ });\r
+ dialog.setVisible(true);\r
+ return dialog.okPressed;\r
+ }\r
+ };\r
+\r
+ /**\r
+ * Provides generic functionality for a dialog to configure a value.\r
+ */\r
+ static private class ValueDialog extends JDialog {\r
+ public boolean okPressed = false;\r
+\r
+ public ValueDialog (JComponent component, String name, String description) {\r
+ setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);\r
+ setLayout(new GridBagLayout());\r
+ setModal(true);\r
+\r
+ if (component instanceof JSpinner)\r
+ ((JSpinner.DefaultEditor)((JSpinner)component).getEditor()).getTextField().setColumns(4);\r
+\r
+ JPanel descriptionPanel = new JPanel();\r
+ descriptionPanel.setLayout(new GridBagLayout());\r
+ getContentPane().add(\r
+ descriptionPanel,\r
+ new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0,\r
+ 0), 0, 0));\r
+ descriptionPanel.setBackground(Color.white);\r
+ descriptionPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.black));\r
+ {\r
+ JTextArea descriptionText = new JTextArea(description);\r
+ descriptionPanel.add(descriptionText, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,\r
+ GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0));\r
+ descriptionText.setWrapStyleWord(true);\r
+ descriptionText.setLineWrap(true);\r
+ descriptionText.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));\r
+ descriptionText.setEditable(false);\r
+ }\r
+\r
+ JPanel panel = new JPanel();\r
+ getContentPane().add(\r
+ panel,\r
+ new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 0,\r
+ 5), 0, 0));\r
+ panel.add(new JLabel(name + ":"));\r
+ panel.add(component);\r
+\r
+ JPanel buttonPanel = new JPanel();\r
+ getContentPane().add(\r
+ buttonPanel,\r
+ new GridBagConstraints(0, 2, 2, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE,\r
+ new Insets(0, 0, 0, 0), 0, 0));\r
+ {\r
+ JButton okButton = new JButton("OK");\r
+ buttonPanel.add(okButton);\r
+ okButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ okPressed = true;\r
+ setVisible(false);\r
+ }\r
+ });\r
+ }\r
+ {\r
+ JButton cancelButton = new JButton("Cancel");\r
+ buttonPanel.add(cancelButton);\r
+ cancelButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed (ActionEvent evt) {\r
+ setVisible(false);\r
+ }\r
+ });\r
+ }\r
+\r
+ setSize(new Dimension(320, 175));\r
+ }\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont.effects;\r
+\r
+import java.awt.Graphics2D;\r
+import java.awt.image.BufferedImage;\r
+import java.awt.image.BufferedImageOp;\r
+\r
+import com.badlogic.gdx.hiero.unicodefont.Glyph;\r
+import com.badlogic.gdx.hiero.unicodefont.UnicodeFont;\r
+\r
+/**\r
+ * Applys a {@link BufferedImageOp} filter to glyphs. Many filters can be fond here: http://www.jhlabs.com/ip/filters/index.html\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class FilterEffect implements Effect {\r
+ private BufferedImageOp filter;\r
+\r
+ public FilterEffect () {\r
+ }\r
+\r
+ public FilterEffect (BufferedImageOp filter) {\r
+ this.filter = filter;\r
+ }\r
+\r
+ public void draw (BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {\r
+ BufferedImage scratchImage = EffectUtil.getScratchImage();\r
+ filter.filter(image, scratchImage);\r
+ image.getGraphics().drawImage(scratchImage, 0, 0, null);\r
+ }\r
+\r
+ public BufferedImageOp getFilter () {\r
+ return filter;\r
+ }\r
+\r
+ public void setFilter (BufferedImageOp filter) {\r
+ this.filter = filter;\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont.effects;\r
+\r
+import java.awt.Color;\r
+import java.awt.GradientPaint;\r
+import java.awt.Graphics2D;\r
+import java.awt.image.BufferedImage;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import com.badlogic.gdx.hiero.unicodefont.Glyph;\r
+import com.badlogic.gdx.hiero.unicodefont.UnicodeFont;\r
+\r
+/**\r
+ * Paints glyphs with a gradient fill.\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class GradientEffect implements ConfigurableEffect {\r
+ private Color topColor = Color.cyan, bottomColor = Color.blue;\r
+ private int offset = 0;\r
+ private float scale = 1;\r
+ private boolean cyclic;\r
+\r
+ public GradientEffect () {\r
+ }\r
+\r
+ public GradientEffect (Color topColor, Color bottomColor, float scale) {\r
+ this.topColor = topColor;\r
+ this.bottomColor = bottomColor;\r
+ this.scale = scale;\r
+ }\r
+\r
+ public void draw (BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {\r
+ int ascent = unicodeFont.getAscent();\r
+ float height = (ascent) * scale;\r
+ float top = -glyph.getYOffset() + unicodeFont.getDescent() + offset + ascent / 2 - height / 2;\r
+ g.setPaint(new GradientPaint(0, top, topColor, 0, top + height, bottomColor, cyclic));\r
+ g.fill(glyph.getShape());\r
+ }\r
+\r
+ public Color getTopColor () {\r
+ return topColor;\r
+ }\r
+\r
+ public void setTopColor (Color topColor) {\r
+ this.topColor = topColor;\r
+ }\r
+\r
+ public Color getBottomColor () {\r
+ return bottomColor;\r
+ }\r
+\r
+ public void setBottomColor (Color bottomColor) {\r
+ this.bottomColor = bottomColor;\r
+ }\r
+\r
+ public int getOffset () {\r
+ return offset;\r
+ }\r
+\r
+ /**\r
+ * Sets the pixel offset to move the gradient up or down. The gradient is normally centered on the glyph.\r
+ */\r
+ public void setOffset (int offset) {\r
+ this.offset = offset;\r
+ }\r
+\r
+ public float getScale () {\r
+ return scale;\r
+ }\r
+\r
+ /**\r
+ * Changes the height of the gradient by a percentage. The gradient is normally the height of most glyphs in the font.\r
+ */\r
+ public void setScale (float scale) {\r
+ this.scale = scale;\r
+ }\r
+\r
+ public boolean isCyclic () {\r
+ return cyclic;\r
+ }\r
+\r
+ /**\r
+ * If set to true, the gradient will repeat.\r
+ */\r
+ public void setCyclic (boolean cyclic) {\r
+ this.cyclic = cyclic;\r
+ }\r
+\r
+ public String toString () {\r
+ return "Gradient";\r
+ }\r
+\r
+ public List getValues () {\r
+ List values = new ArrayList();\r
+ values.add(EffectUtil.colorValue("Top color", topColor));\r
+ values.add(EffectUtil.colorValue("Bottom color", bottomColor));\r
+ values.add(EffectUtil.intValue("Offset", offset,\r
+ "This setting allows you to move the gradient up or down. The gradient is normally centered on the glyph."));\r
+ values.add(EffectUtil.floatValue("Scale", scale, 0, 10, "This setting allows you to change the height of the gradient by a"\r
+ + "percentage. The gradient is normally the height of most glyphs in the font."));\r
+ values.add(EffectUtil.booleanValue("Cyclic", cyclic, "If this setting is checked, the gradient will repeat."));\r
+ return values;\r
+ }\r
+\r
+ public void setValues (List values) {\r
+ for (Iterator iter = values.iterator(); iter.hasNext();) {\r
+ Value value = (Value)iter.next();\r
+ if (value.getName().equals("Top color")) {\r
+ topColor = (Color)value.getObject();\r
+ } else if (value.getName().equals("Bottom color")) {\r
+ bottomColor = (Color)value.getObject();\r
+ } else if (value.getName().equals("Offset")) {\r
+ offset = ((Integer)value.getObject()).intValue();\r
+ } else if (value.getName().equals("Scale")) {\r
+ scale = ((Float)value.getObject()).floatValue();\r
+ } else if (value.getName().equals("Cyclic")) {\r
+ cyclic = ((Boolean)value.getObject()).booleanValue();\r
+ }\r
+ }\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont.effects;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Graphics2D;\r
+import java.awt.Stroke;\r
+import java.awt.image.BufferedImage;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import com.badlogic.gdx.hiero.unicodefont.Glyph;\r
+import com.badlogic.gdx.hiero.unicodefont.UnicodeFont;\r
+\r
+/**\r
+ * Strokes glyphs with an outline.\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class OutlineEffect implements ConfigurableEffect {\r
+ private float width = 2;\r
+ private Color color = Color.black;\r
+ private int join = BasicStroke.JOIN_BEVEL;\r
+ private Stroke stroke;\r
+\r
+ public OutlineEffect () {\r
+ }\r
+\r
+ public OutlineEffect (int width, Color color) {\r
+ this.width = width;\r
+ this.color = color;\r
+ }\r
+\r
+ public void draw (BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {\r
+ g = (Graphics2D)g.create();\r
+ if (stroke != null)\r
+ g.setStroke(stroke);\r
+ else\r
+ g.setStroke(getStroke());\r
+ g.setColor(color);\r
+ g.draw(glyph.getShape());\r
+ g.dispose();\r
+ }\r
+\r
+ public float getWidth () {\r
+ return width;\r
+ }\r
+\r
+ /**\r
+ * Sets the width of the outline. The glyphs will need padding so the outline doesn't get clipped.\r
+ */\r
+ public void setWidth (int width) {\r
+ this.width = width;\r
+ }\r
+\r
+ public Color getColor () {\r
+ return color;\r
+ }\r
+\r
+ public void setColor (Color color) {\r
+ this.color = color;\r
+ }\r
+\r
+ public int getJoin () {\r
+ return join;\r
+ }\r
+\r
+ public Stroke getStroke () {\r
+ if (stroke == null) return new BasicStroke(width, BasicStroke.CAP_SQUARE, join);\r
+ return stroke;\r
+ }\r
+\r
+ /**\r
+ * Sets the stroke to use for the outline. If this is set, the other outline settings are ignored.\r
+ */\r
+ public void setStroke (Stroke stroke) {\r
+ this.stroke = stroke;\r
+ }\r
+\r
+ /**\r
+ * Sets how the corners of the outline are drawn. This is usually only noticeable at large outline widths.\r
+ * @param join One of: {@link BasicStroke#JOIN_BEVEL}, {@link BasicStroke#JOIN_MITER}, {@link BasicStroke#JOIN_ROUND}\r
+ */\r
+ public void setJoin (int join) {\r
+ this.join = join;\r
+ }\r
+\r
+ public String toString () {\r
+ return "Outline";\r
+ }\r
+\r
+ public List getValues () {\r
+ List values = new ArrayList();\r
+ values.add(EffectUtil.colorValue("Color", color));\r
+ values.add(EffectUtil.floatValue("Width", width, 0.1f, 999, "This setting controls the width of the outline. "\r
+ + "The glyphs will need padding so the outline doesn't get clipped."));\r
+ values.add(EffectUtil.optionValue("Join", String.valueOf(join), new String[][] { {"Bevel", BasicStroke.JOIN_BEVEL + ""},\r
+ {"Miter", BasicStroke.JOIN_MITER + ""}, {"Round", BasicStroke.JOIN_ROUND + ""}},\r
+ "This setting defines how the corners of the outline are drawn. "\r
+ + "This is usually only noticeable at large outline widths."));\r
+ return values;\r
+ }\r
+\r
+ public void setValues (List values) {\r
+ for (Iterator iter = values.iterator(); iter.hasNext();) {\r
+ Value value = (Value)iter.next();\r
+ if (value.getName().equals("Color")) {\r
+ color = (Color)value.getObject();\r
+ } else if (value.getName().equals("Width")) {\r
+ width = ((Float)value.getObject()).floatValue();\r
+ } else if (value.getName().equals("Join")) {\r
+ join = Integer.parseInt((String)value.getObject());\r
+ }\r
+ }\r
+ }\r
+}\r
--- /dev/null
+/*\r
+ * Copyright 2006 Jerry Huxtable\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the\r
+ * License. You may obtain a copy of the License at\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"\r
+ * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language\r
+ * governing permissions and limitations under the License.\r
+ */\r
+\r
+package com.badlogic.gdx.hiero.unicodefont.effects;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Shape;\r
+import java.awt.Stroke;\r
+import java.awt.geom.FlatteningPathIterator;\r
+import java.awt.geom.GeneralPath;\r
+import java.awt.geom.PathIterator;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+/**\r
+ * @author Jerry Huxtable\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class OutlineWobbleEffect extends OutlineEffect {\r
+ float detail = 1;\r
+ float amplitude = 1;\r
+\r
+ public OutlineWobbleEffect () {\r
+ setStroke(new WobbleStroke());\r
+ }\r
+\r
+ public OutlineWobbleEffect (int width, Color color) {\r
+ super(width, color);\r
+ }\r
+\r
+ public String toString () {\r
+ return "Outline (Wobble)";\r
+ }\r
+\r
+ public List getValues () {\r
+ List values = super.getValues();\r
+ values.remove(2); // Remove "Join".\r
+ values.add(EffectUtil.floatValue("Detail", detail, 1, 50, "This setting controls how detailed the outline will be. "\r
+ + "Smaller numbers cause the outline to have more detail."));\r
+ values.add(EffectUtil.floatValue("Amplitude", amplitude, 0.5f, 50, "This setting controls the amplitude of the outline."));\r
+ return values;\r
+ }\r
+\r
+ public void setValues (List values) {\r
+ super.setValues(values);\r
+ for (Iterator iter = values.iterator(); iter.hasNext();) {\r
+ Value value = (Value)iter.next();\r
+ if (value.getName().equals("Detail")) {\r
+ detail = ((Float)value.getObject()).floatValue();\r
+ } else if (value.getName().equals("Amplitude")) {\r
+ amplitude = ((Float)value.getObject()).floatValue();\r
+ }\r
+ }\r
+ }\r
+\r
+ class WobbleStroke implements Stroke {\r
+ private static final float FLATNESS = 1;\r
+\r
+ public Shape createStrokedShape (Shape shape) {\r
+ GeneralPath result = new GeneralPath();\r
+ shape = new BasicStroke(getWidth(), BasicStroke.CAP_SQUARE, getJoin()).createStrokedShape(shape);\r
+ PathIterator it = new FlatteningPathIterator(shape.getPathIterator(null), FLATNESS);\r
+ float points[] = new float[6];\r
+ float moveX = 0, moveY = 0;\r
+ float lastX = 0, lastY = 0;\r
+ float thisX = 0, thisY = 0;\r
+ int type = 0;\r
+ float next = 0;\r
+ while (!it.isDone()) {\r
+ type = it.currentSegment(points);\r
+ switch (type) {\r
+ case PathIterator.SEG_MOVETO:\r
+ moveX = lastX = randomize(points[0]);\r
+ moveY = lastY = randomize(points[1]);\r
+ result.moveTo(moveX, moveY);\r
+ next = 0;\r
+ break;\r
+\r
+ case PathIterator.SEG_CLOSE:\r
+ points[0] = moveX;\r
+ points[1] = moveY;\r
+ // Fall into....\r
+\r
+ case PathIterator.SEG_LINETO:\r
+ thisX = randomize(points[0]);\r
+ thisY = randomize(points[1]);\r
+ float dx = thisX - lastX;\r
+ float dy = thisY - lastY;\r
+ float distance = (float)Math.sqrt(dx * dx + dy * dy);\r
+ if (distance >= next) {\r
+ float r = 1.0f / distance;\r
+ while (distance >= next) {\r
+ float x = lastX + next * dx * r;\r
+ float y = lastY + next * dy * r;\r
+ result.lineTo(randomize(x), randomize(y));\r
+ next += detail;\r
+ }\r
+ }\r
+ next -= distance;\r
+ lastX = thisX;\r
+ lastY = thisY;\r
+ break;\r
+ }\r
+ it.next();\r
+ }\r
+\r
+ return result;\r
+ }\r
+\r
+ private float randomize (float x) {\r
+ return x + (float)Math.random() * amplitude * 2 - 1;\r
+ }\r
+ }\r
+}\r
--- /dev/null
+/*\r
+ * Copyright 2006 Jerry Huxtable\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the\r
+ * License. You may obtain a copy of the License at\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"\r
+ * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language\r
+ * governing permissions and limitations under the License.\r
+ */\r
+\r
+package com.badlogic.gdx.hiero.unicodefont.effects;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Shape;\r
+import java.awt.Stroke;\r
+import java.awt.geom.FlatteningPathIterator;\r
+import java.awt.geom.GeneralPath;\r
+import java.awt.geom.PathIterator;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+/**\r
+ * @author Jerry Huxtable\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class OutlineZigzagEffect extends OutlineEffect {\r
+ float amplitude = 1;\r
+ float wavelength = 3;\r
+\r
+ public OutlineZigzagEffect () {\r
+ setStroke(new ZigzagStroke());\r
+ }\r
+\r
+ public OutlineZigzagEffect (int width, Color color) {\r
+ super(width, color);\r
+ }\r
+\r
+ public String toString () {\r
+ return "Outline (Zigzag)";\r
+ }\r
+\r
+ public List getValues () {\r
+ List values = super.getValues();\r
+ values.add(EffectUtil.floatValue("Wavelength", wavelength, 1, 100, "This setting controls the wavelength of the outline. "\r
+ + "The smaller the value, the more segments will be used to draw the outline."));\r
+ values.add(EffectUtil.floatValue("Amplitude", amplitude, 0.5f, 50, "This setting controls the amplitude of the outline. "\r
+ + "The bigger the value, the more the zigzags will vary."));\r
+ return values;\r
+ }\r
+\r
+ public void setValues (List values) {\r
+ super.setValues(values);\r
+ for (Iterator iter = values.iterator(); iter.hasNext();) {\r
+ Value value = (Value)iter.next();\r
+ if (value.getName().equals("Wavelength")) {\r
+ wavelength = ((Float)value.getObject()).floatValue();\r
+ } else if (value.getName().equals("Amplitude")) {\r
+ amplitude = ((Float)value.getObject()).floatValue();\r
+ }\r
+ }\r
+ }\r
+\r
+ class ZigzagStroke implements Stroke {\r
+ private static final float FLATNESS = 1;\r
+\r
+ public Shape createStrokedShape (Shape shape) {\r
+ GeneralPath result = new GeneralPath();\r
+ PathIterator it = new FlatteningPathIterator(shape.getPathIterator(null), FLATNESS);\r
+ float points[] = new float[6];\r
+ float moveX = 0, moveY = 0;\r
+ float lastX = 0, lastY = 0;\r
+ float thisX = 0, thisY = 0;\r
+ int type = 0;\r
+ float next = 0;\r
+ int phase = 0;\r
+ while (!it.isDone()) {\r
+ type = it.currentSegment(points);\r
+ switch (type) {\r
+ case PathIterator.SEG_MOVETO:\r
+ moveX = lastX = points[0];\r
+ moveY = lastY = points[1];\r
+ result.moveTo(moveX, moveY);\r
+ next = wavelength / 2;\r
+ break;\r
+\r
+ case PathIterator.SEG_CLOSE:\r
+ points[0] = moveX;\r
+ points[1] = moveY;\r
+ // Fall into....\r
+\r
+ case PathIterator.SEG_LINETO:\r
+ thisX = points[0];\r
+ thisY = points[1];\r
+ float dx = thisX - lastX;\r
+ float dy = thisY - lastY;\r
+ float distance = (float)Math.sqrt(dx * dx + dy * dy);\r
+ if (distance >= next) {\r
+ float r = 1.0f / distance;\r
+ while (distance >= next) {\r
+ float x = lastX + next * dx * r;\r
+ float y = lastY + next * dy * r;\r
+ if ((phase & 1) == 0)\r
+ result.lineTo(x + amplitude * dy * r, y - amplitude * dx * r);\r
+ else\r
+ result.lineTo(x - amplitude * dy * r, y + amplitude * dx * r);\r
+ next += wavelength;\r
+ phase++;\r
+ }\r
+ }\r
+ next -= distance;\r
+ lastX = thisX;\r
+ lastY = thisY;\r
+ if (type == PathIterator.SEG_CLOSE) result.closePath();\r
+ break;\r
+ }\r
+ it.next();\r
+ }\r
+ return new BasicStroke(getWidth(), BasicStroke.CAP_SQUARE, getJoin()).createStrokedShape(result);\r
+ }\r
+ }\r
+}\r
--- /dev/null
+\r
+package com.badlogic.gdx.hiero.unicodefont.effects;\r
+\r
+import java.awt.AlphaComposite;\r
+import java.awt.Color;\r
+import java.awt.Composite;\r
+import java.awt.Graphics2D;\r
+import java.awt.RenderingHints;\r
+import java.awt.image.BufferedImage;\r
+import java.awt.image.ConvolveOp;\r
+import java.awt.image.Kernel;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import com.badlogic.gdx.hiero.unicodefont.Glyph;\r
+import com.badlogic.gdx.hiero.unicodefont.UnicodeFont;\r
+\r
+/**\r
+ * @author Nathan Sweet <misc@n4te.com>\r
+ */\r
+public class ShadowEffect implements ConfigurableEffect {\r
+ /** The numberof kernels to apply */\r
+ public static final int NUM_KERNELS = 16;\r
+ /** The blur kernels applied across the effect */\r
+ public static final float[][] GAUSSIAN_BLUR_KERNELS = generateGaussianBlurKernels(NUM_KERNELS);\r
+\r
+ private Color color = Color.black;\r
+ private float opacity = 0.6f;\r
+ private float xDistance = 2, yDistance = 2;\r
+ private int blurKernelSize = 0;\r
+ private int blurPasses = 1;\r
+\r
+ public ShadowEffect () {\r
+ }\r
+\r
+ public ShadowEffect (Color color, int xDistance, int yDistance, float opacity) {\r
+ this.color = color;\r
+ this.xDistance = xDistance;\r
+ this.yDistance = yDistance;\r
+ this.opacity = opacity;\r
+ }\r
+\r
+ public void draw (BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {\r
+ g = (Graphics2D)g.create();\r
+ g.translate(xDistance, yDistance);\r
+ g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), Math.round(opacity * 255)));\r
+ g.fill(glyph.getShape());\r
+\r
+ // Also shadow the outline, if one exists.\r
+ for (Iterator iter = unicodeFont.getEffects().iterator(); iter.hasNext();) {\r
+ Effect effect = (Effect)iter.next();\r
+ if (effect instanceof OutlineEffect) {\r
+ Composite composite = g.getComposite();\r
+ g.setComposite(AlphaComposite.Src); // Prevent shadow and outline shadow alpha from combining.\r
+\r
+ g.setStroke(((OutlineEffect)effect).getStroke());\r
+ g.draw(glyph.getShape());\r
+\r
+ g.setComposite(composite);\r
+ break;\r
+ }\r
+ }\r
+\r
+ g.dispose();\r
+ if (blurKernelSize > 1 && blurKernelSize < NUM_KERNELS && blurPasses > 0) blur(image);\r
+ }\r
+\r
+ private void blur (BufferedImage image) {\r
+ float[] matrix = GAUSSIAN_BLUR_KERNELS[blurKernelSize - 1];\r
+ Kernel gaussianBlur1 = new Kernel(matrix.length, 1, matrix);\r
+ Kernel gaussianBlur2 = new Kernel(1, matrix.length, matrix);\r
+ RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);\r
+ ConvolveOp gaussianOp1 = new ConvolveOp(gaussianBlur1, ConvolveOp.EDGE_NO_OP, hints);\r
+ ConvolveOp gaussianOp2 = new ConvolveOp(gaussianBlur2, ConvolveOp.EDGE_NO_OP, hints);\r
+ BufferedImage scratchImage = EffectUtil.getScratchImage();\r
+ for (int i = 0; i < blurPasses; i++) {\r
+ gaussianOp1.filter(image, scratchImage);\r
+ gaussianOp2.filter(scratchImage, image);\r
+ }\r
+ }\r
+\r
+ public Color getColor () {\r
+ return color;\r
+ }\r
+\r
+ public void setColor (Color color) {\r
+ this.color = color;\r
+ }\r
+\r
+ public float getXDistance () {\r
+ return xDistance;\r
+ }\r
+\r
+ /**\r
+ * Sets the pixels to offset the shadow on the x axis. The glyphs will need padding so the shadow doesn't get clipped.\r
+ */\r
+ public void setXDistance (float distance) {\r
+ xDistance = distance;\r
+ }\r
+\r
+ public float getYDistance () {\r
+ return yDistance;\r
+ }\r
+\r
+ /**\r
+ * Sets the pixels to offset the shadow on the y axis. The glyphs will need padding so the shadow doesn't get clipped.\r
+ */\r
+ public void setYDistance (float distance) {\r
+ yDistance = distance;\r
+ }\r
+\r
+ public int getBlurKernelSize () {\r
+ return blurKernelSize;\r
+ }\r
+\r
+ /**\r
+ * Sets how many neighboring pixels are used to blur the shadow. Set to 0 for no blur.\r
+ */\r
+ public void setBlurKernelSize (int blurKernelSize) {\r
+ this.blurKernelSize = blurKernelSize;\r
+ }\r
+\r
+ public int getBlurPasses () {\r
+ return blurPasses;\r
+ }\r
+\r
+ /**\r
+ * Sets the number of times to apply a blur to the shadow. Set to 0 for no blur.\r
+ */\r
+ public void setBlurPasses (int blurPasses) {\r
+ this.blurPasses = blurPasses;\r
+ }\r
+\r
+ public float getOpacity () {\r
+ return opacity;\r
+ }\r
+\r
+ public void setOpacity (float opacity) {\r
+ this.opacity = opacity;\r
+ }\r
+\r
+ public String toString () {\r
+ return "Shadow";\r
+ }\r
+\r
+ public List getValues () {\r
+ List values = new ArrayList();\r
+ values.add(EffectUtil.colorValue("Color", color));\r
+ values.add(EffectUtil.floatValue("Opacity", opacity, 0, 1, "This setting sets the translucency of the shadow."));\r
+ values.add(EffectUtil.floatValue("X distance", xDistance, 0, 99, "This setting is the amount of pixels to offset the "\r
+ + "shadow on the x axis. The glyphs will need padding so the shadow doesn't get clipped."));\r
+ values.add(EffectUtil.floatValue("Y distance", yDistance, 0, 99, "This setting is the amount of pixels to offset the "\r
+ + "shadow on the y axis. The glyphs will need padding so the shadow doesn't get clipped."));\r
+\r
+ List options = new ArrayList();\r
+ options.add(new String[] {"None", "0"});\r
+ for (int i = 2; i < NUM_KERNELS; i++)\r
+ options.add(new String[] {String.valueOf(i)});\r
+ String[][] optionsArray = (String[][])options.toArray(new String[options.size()][]);\r
+ values.add(EffectUtil.optionValue("Blur kernel size", String.valueOf(blurKernelSize), optionsArray,\r
+ "This setting controls how many neighboring pixels are used to blur the shadow. Set to \"None\" for no blur."));\r
+\r
+ values.add(EffectUtil.intValue("Blur passes", blurPasses,\r
+ "The setting is the number of times to apply a blur to the shadow. Set to \"0\" for no blur."));\r
+ return values;\r
+ }\r
+\r
+ public void setValues (List values) {\r
+ for (Iterator iter = values.iterator(); iter.hasNext();) {\r
+ Value value = (Value)iter.next();\r
+ if (value.getName().equals("Color")) {\r
+ color = (Color)value.getObject();\r
+ } else if (value.getName().equals("Opacity")) {\r
+ opacity = ((Float)value.getObject()).floatValue();\r
+ } else if (value.getName().equals("X distance")) {\r
+ xDistance = ((Float)value.getObject()).floatValue();\r
+ } else if (value.getName().equals("Y distance")) {\r
+ yDistance = ((Float)value.getObject()).floatValue();\r
+ } else if (value.getName().equals("Blur kernel size")) {\r
+ blurKernelSize = Integer.parseInt((String)value.getObject());\r
+ } else if (value.getName().equals("Blur passes")) {\r
+ blurPasses = ((Integer)value.getObject()).intValue();\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Generate the blur kernels which will be repeatedly applied when blurring images\r
+ * \r
+ * @param level The number of kernels to generate\r
+ * @return The kernels generated\r
+ */\r
+ private static float[][] generateGaussianBlurKernels (int level) {\r
+ float[][] pascalsTriangle = generatePascalsTriangle(level);\r
+ float[][] gaussianTriangle = new float[pascalsTriangle.length][];\r
+ for (int i = 0; i < gaussianTriangle.length; i++) {\r
+ float total = 0.0f;\r
+ gaussianTriangle[i] = new float[pascalsTriangle[i].length];\r
+ for (int j = 0; j < pascalsTriangle[i].length; j++)\r
+ total += pascalsTriangle[i][j];\r
+ float coefficient = 1 / total;\r
+ for (int j = 0; j < pascalsTriangle[i].length; j++)\r
+ gaussianTriangle[i][j] = coefficient * pascalsTriangle[i][j];\r
+ }\r
+ return gaussianTriangle;\r
+ }\r
+\r
+ /**\r
+ * Generate Pascal's triangle\r
+ * \r
+ * @param level The level of the triangle to generate\r
+ * @return The Pascal's triangle kernel\r
+ */\r
+ private static float[][] generatePascalsTriangle (int level) {\r
+ if (level < 2) level = 2;\r
+ float[][] triangle = new float[level][];\r
+ triangle[0] = new float[1];\r
+ triangle[1] = new float[2];\r
+ triangle[0][0] = 1.0f;\r
+ triangle[1][0] = 1.0f;\r
+ triangle[1][1] = 1.0f;\r
+ for (int i = 2; i < level; i++) {\r
+ triangle[i] = new float[i + 1];\r
+ triangle[i][0] = 1.0f;\r
+ triangle[i][i] = 1.0f;\r
+ for (int j = 1; j < triangle[i].length - 1; j++)\r
+ triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];\r
+ }\r
+ return triangle;\r
+ }\r
+}\r