Merge commit '77d3a60869e7a780c6ae069e51530e1eacece5e2'
[fanfix.git] / src / jexer / backend / GlyphMaker.java
index 08cbee8ca10a91d48900e182d6b1fd25d9946c39..e5fcc522da927fb2c8788b98f8a5075815389f38 100644 (file)
@@ -29,6 +29,7 @@
 package jexer.backend;
 
 import java.awt.Font;
+import java.awt.FontFormatException;
 import java.awt.FontMetrics;
 import java.awt.Graphics2D;
 import java.awt.geom.Rectangle2D;
@@ -38,6 +39,7 @@ import java.io.IOException;
 import java.util.HashMap;
 
 import jexer.bits.Cell;
+import jexer.bits.StringUtils;
 
 /**
  * GlyphMakerFont creates glyphs as bitmaps from a font.
@@ -135,18 +137,28 @@ class GlyphMakerFont {
      * @param fontSize the size of font to use
      */
     public GlyphMakerFont(final String filename, final int fontSize) {
+
+        if (filename.length() == 0) {
+            // Fallback font
+            font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize - 2);
+            return;
+        }
+
         Font fontRoot = null;
         try {
             ClassLoader loader = Thread.currentThread().getContextClassLoader();
             InputStream in = loader.getResourceAsStream(filename);
             fontRoot = Font.createFont(Font.TRUETYPE_FONT, in);
-            font = fontRoot.deriveFont(Font.PLAIN, fontSize);
-        } catch (java.awt.FontFormatException e) {
-            e.printStackTrace();
-            font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
-        } catch (java.io.IOException e) {
-            e.printStackTrace();
-            font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
+            font = fontRoot.deriveFont(Font.PLAIN, fontSize - 2);
+        } catch (FontFormatException e) {
+            // Ideally we would report an error here, either via System.err
+            // or TExceptionDialog.  However, I do not want GlyphMaker to
+            // know about available backends, so we quietly fallback to
+            // whatever is available as MONO.
+            font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize - 2);
+        } catch (IOException e) {
+            // See comment above.
+            font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize - 2);
         }
     }
 
@@ -185,6 +197,11 @@ class GlyphMakerFont {
             getFontDimensions();
         }
 
+        if (DEBUG && !font.canDisplay(cell.getChar())) {
+            System.err.println("font " + font + " has no glyph for " +
+                String.format("0x%x", cell.getChar()));
+        }
+
         BufferedImage image = null;
         if (cell.isBlink() && !blinkVisible) {
             image = glyphCacheBlink.get(cell);
@@ -218,9 +235,8 @@ class GlyphMakerFont {
             || (cell.isBlink() && blinkVisible)
         ) {
             gr2.setColor(SwingTerminal.attrToForegroundColor(cellColor));
-            char [] chars = new char[1];
-            chars[0] = cell.getChar();
-            gr2.drawChars(chars, 0, 1, textAdjustX,
+            char [] chars = Character.toChars(cell.getChar());
+            gr2.drawChars(chars, 0, chars.length, textAdjustX,
                 cellHeight - maxDescent + textAdjustY);
 
             if (cell.isUnderline()) {
@@ -237,6 +253,11 @@ class GlyphMakerFont {
             glyphCache.put(key, image);
         }
 
+        /*
+        System.err.println("cellWidth " + cellWidth +
+            " cellHeight " + cellHeight + " image " + image);
+         */
+
         return image;
     }
 
@@ -265,10 +286,28 @@ class GlyphMakerFont {
 
         textHeight = fontTextHeight + textAdjustHeight;
         textWidth = fontTextWidth + textAdjustWidth;
+        /*
+        System.err.println("font " + font);
+        System.err.println("fontTextWidth " + fontTextWidth);
+        System.err.println("fontTextHeight " + fontTextHeight);
+        System.err.println("textWidth " + textWidth);
+        System.err.println("textHeight " + textHeight);
+         */
 
         gotFontDimensions = true;
     }
 
+    /**
+     * Checks if this maker's Font has a glyph for the specified character.
+     *
+     * @param codePoint the character (Unicode code point) for which a glyph
+     * is needed.
+     * @return true if this Font has a glyph for the character; false
+     * otherwise.
+     */
+    public boolean canDisplay(final int codePoint) {
+        return font.canDisplay(codePoint);
+    }
 }
 
 /**
@@ -287,19 +326,19 @@ public class GlyphMaker {
     private static final String MONO = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
 
     /**
-     * The CJKhk font resource filename.
+     * The CJK font resource filename.
      */
-    // private static final String CJKhk = "NotoSansMonoCJKhk-Regular.otf";
+    private static final String cjkFontFilename = "NotoSansMonoCJKtc-Regular.otf";
 
     /**
-     * The CJKkr font resource filename.
+     * The emoji font resource filename.
      */
-    // private static final String CJKkr = "NotoSansMonoCJKkr-Regular.otf";
+    private static final String emojiFontFilename = "OpenSansEmoji.ttf";
 
     /**
-     * The CJKtc font resource filename.
+     * The fallback font resource filename.
      */
-    private static final String CJKtc = "NotoSansMonoCJKtc-Regular.otf";
+    private static final String fallbackFontFilename = "";
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
@@ -321,19 +360,19 @@ public class GlyphMaker {
     private GlyphMakerFont makerMono;
 
     /**
-     * The instance that has the CJKhk font.
+     * The instance that has the CJK font.
      */
-    // private GlyphMakerFont makerCJKhk;
+    private GlyphMakerFont makerCjk;
 
     /**
-     * The instance that has the CJKkr font.
+     * The instance that has the emoji font.
      */
-    // private GlyphMakerFont makerCJKkr;
+    private GlyphMakerFont makerEmoji;
 
     /**
-     * The instance that has the CJKtc font.
+     * The instance that has the fallback font.
      */
-    private GlyphMakerFont makerCJKtc;
+    private GlyphMakerFont makerFallback;
 
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
@@ -346,9 +385,17 @@ public class GlyphMaker {
      */
     private GlyphMaker(final int fontSize) {
         makerMono = new GlyphMakerFont(MONO, fontSize);
-        // makerCJKhk = new GlyphMakerFont(CJKhk, fontSize);
-        // makerCJKkr = new GlyphMakerFont(CJKkr, fontSize);
-        makerCJKtc = new GlyphMakerFont(CJKtc, fontSize);
+
+        String fontFilename = null;
+        fontFilename = System.getProperty("jexer.cjkFont.filename",
+            cjkFontFilename);
+        makerCjk = new GlyphMakerFont(fontFilename, fontSize);
+        fontFilename = System.getProperty("jexer.emojiFont.filename",
+            emojiFontFilename);
+        makerEmoji = new GlyphMakerFont(fontFilename, fontSize);
+        fontFilename = System.getProperty("jexer.fallbackFont.filename",
+            fallbackFontFilename);
+        makerFallback = new GlyphMakerFont(fontFilename, fontSize);
     }
 
     // ------------------------------------------------------------------------
@@ -398,21 +445,29 @@ public class GlyphMaker {
     public BufferedImage getImage(final Cell cell, final int cellWidth,
         final int cellHeight, final boolean blinkVisible) {
 
-        char ch = cell.getChar();
-        /*
-        if ((ch >= 0x4e00) && (ch <= 0x9fff)) {
-            return makerCJKhk.getImage(cell, cellWidth, cellHeight, blinkVisible);
-        }
-        if ((ch >= 0x4e00) && (ch <= 0x9fff)) {
-            return makerCJKkr.getImage(cell, cellWidth, cellHeight, blinkVisible);
+        int ch = cell.getChar();
+        if (StringUtils.isCjk(ch)) {
+            if (makerCjk.canDisplay(ch)) {
+                return makerCjk.getImage(cell, cellWidth, cellHeight,
+                    blinkVisible);
+            }
         }
-         */
-        if ((ch >= 0x2e80) && (ch <= 0x9fff)) {
-            return makerCJKtc.getImage(cell, cellWidth, cellHeight, blinkVisible);
+        if (StringUtils.isEmoji(ch)) {
+            if (makerEmoji.canDisplay(ch)) {
+                // System.err.println("emoji: " + String.format("0x%x", ch));
+                return makerEmoji.getImage(cell, cellWidth, cellHeight,
+                    blinkVisible);
+            }
         }
 
         // When all else fails, use the default.
-        return makerMono.getImage(cell, cellWidth, cellHeight, blinkVisible);
+        if (makerMono.canDisplay(ch)) {
+            return makerMono.getImage(cell, cellWidth, cellHeight,
+                blinkVisible);
+        }
+
+        return makerFallback.getImage(cell, cellWidth, cellHeight,
+            blinkVisible);
     }
 
 }