X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2Fbackend%2FGlyphMaker.java;h=0da2918def6c8d9d0f57d7a77506828039994d69;hb=12b90437b5f22c2ae6e9b9b14c3b62b60f6143e5;hp=0094145dafa811c34242530a94c64404f3639e7a;hpb=0d86ab8480cabbe32fc87588304ddc795a4df14f;p=nikiroo-utils.git diff --git a/src/jexer/backend/GlyphMaker.java b/src/jexer/backend/GlyphMaker.java index 0094145..0da2918 100644 --- a/src/jexer/backend/GlyphMaker.java +++ b/src/jexer/backend/GlyphMaker.java @@ -38,26 +38,17 @@ import java.io.IOException; import java.util.HashMap; import jexer.bits.Cell; +import jexer.bits.StringUtils; /** - * GlyphMaker creates glyphs as bitmaps from a font. + * GlyphMakerFont creates glyphs as bitmaps from a font. */ -public class GlyphMaker { +class GlyphMakerFont { // ------------------------------------------------------------------------ // Constants -------------------------------------------------------------- // ------------------------------------------------------------------------ - /** - * The mono font resource filename (terminus). - */ - public static final String MONO = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf"; - - /** - * The CJK font resource filename. - */ - public static final String CJK = "NotoSansMonoCJKhk-Regular.otf"; - // ------------------------------------------------------------------------ // Variables -------------------------------------------------------------- // ------------------------------------------------------------------------ @@ -67,16 +58,6 @@ public class GlyphMaker { */ private static boolean DEBUG = false; - /** - * The instance that has the mono (default) font. - */ - private static GlyphMaker INSTANCE_MONO; - - /** - * The instance that has the CJK font. - */ - private static GlyphMaker INSTANCE_CJK; - /** * If true, we were successful at getting the font dimensions. */ @@ -87,11 +68,6 @@ public class GlyphMaker { */ private Font font = null; - /** - * The currently selected font size in points. - */ - private int fontSize = 16; - /** * Width of a character cell in pixels. */ @@ -153,132 +129,41 @@ public class GlyphMaker { // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ - /** - * Private constructor used by the static instance methods. - * - * @param font the font to use - */ - private GlyphMaker(final Font font) { - this.font = font; - fontSize = font.getSize(); - } - /** * Public constructor. * - * @param fontName the name of the font to use + * @param filename the resource filename of the font to use * @param fontSize the size of font to use */ - public GlyphMaker(final String fontName, final int fontSize) { - font = new Font(fontName, Font.PLAIN, fontSize); - } - - // ------------------------------------------------------------------------ - // GlyphMaker ------------------------------------------------------------- - // ------------------------------------------------------------------------ - - /** - * Obtain the GlyphMaker instance that uses the default monospace font. - * - * @return the instance - */ - public static GlyphMaker getDefault() { + public GlyphMakerFont(final String filename, final int fontSize) { - synchronized (GlyphMaker.class) { - if (INSTANCE_MONO != null) { - return INSTANCE_MONO; - } - - int fallbackFontSize = 16; - Font monoRoot = null; - try { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - InputStream in = loader.getResourceAsStream(MONO); - monoRoot = Font.createFont(Font.TRUETYPE_FONT, in); - } catch (java.awt.FontFormatException e) { - e.printStackTrace(); - monoRoot = new Font(Font.MONOSPACED, Font.PLAIN, - fallbackFontSize); - } catch (java.io.IOException e) { - e.printStackTrace(); - monoRoot = new Font(Font.MONOSPACED, Font.PLAIN, - fallbackFontSize); - } - INSTANCE_MONO = new GlyphMaker(monoRoot); - return INSTANCE_MONO; + if (filename.length() == 0) { + // Fallback font + font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize); + return; } - } - /** - * Obtain the GlyphMaker instance that uses the CJK font. - * - * @return the instance - */ - public static GlyphMaker getCJK() { - - synchronized (GlyphMaker.class) { - if (INSTANCE_CJK != null) { - return INSTANCE_CJK; - } - - int fallbackFontSize = 16; - Font cjkRoot = null; - try { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - InputStream in = loader.getResourceAsStream(CJK); - cjkRoot = Font.createFont(Font.TRUETYPE_FONT, in); - } catch (java.awt.FontFormatException e) { - e.printStackTrace(); - cjkRoot = new Font(Font.MONOSPACED, Font.PLAIN, - fallbackFontSize); - } catch (java.io.IOException e) { - e.printStackTrace(); - cjkRoot = new Font(Font.MONOSPACED, Font.PLAIN, - fallbackFontSize); - } - INSTANCE_CJK = new GlyphMaker(cjkRoot); - return INSTANCE_CJK; - } - } - - /** - * Obtain the GlyphMaker instance that uses the correct font for this - * character. - * - * @param ch the character - * @return the instance - */ - public static GlyphMaker getInstance(final int ch) { - if (((ch >= 0x4e00) && (ch <= 0x9fff)) - || ((ch >= 0x3400) && (ch <= 0x4dbf)) - || ((ch >= 0x20000) && (ch <= 0x2ebef)) - ) { - return getCJK(); + 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) { + // 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); + } catch (java.io.IOException e) { + // See comment above. + font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize); } - return getDefault(); } - /** - * Get a derived font at a specific size. - * - * @param fontSize the size to use - * @return a new instance at that font size - */ - public GlyphMaker size(final int fontSize) { - GlyphMaker maker = new GlyphMaker(font.deriveFont(Font.PLAIN, - fontSize)); - return maker; - } - - /** - * Get a glyph image, using the font's idea of cell width and height. - * - * @param cell the character to draw - * @return the glyph as an image - */ - public BufferedImage getImage(final Cell cell) { - return getImage(cell, textWidth, textHeight, true); - } + // ------------------------------------------------------------------------ + // GlyphMakerFont --------------------------------------------------------- + // ------------------------------------------------------------------------ /** * Get a glyph image. @@ -311,6 +196,11 @@ public class GlyphMaker { 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); @@ -327,8 +217,7 @@ public class GlyphMaker { Graphics2D gr2 = image.createGraphics(); gr2.setFont(font); - Cell cellColor = new Cell(); - cellColor.setTo(cell); + Cell cellColor = new Cell(cell); // Check for reverse if (cell.isReverse()) { @@ -345,9 +234,8 @@ public class GlyphMaker { || (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()) { @@ -357,14 +245,18 @@ public class GlyphMaker { gr2.dispose(); // We need a new key that will not be mutated by invertCell(). - Cell key = new Cell(); - key.setTo(cell); + Cell key = new Cell(cell); if (cell.isBlink() && !blinkVisible) { glyphCacheBlink.put(key, image); } else { glyphCache.put(key, image); } + /* + System.err.println("cellWidth " + cellWidth + + " cellHeight " + cellHeight + " image " + image); + */ + return image; } @@ -375,8 +267,8 @@ public class GlyphMaker { glyphCacheBlink = new HashMap(); glyphCache = new HashMap(); - BufferedImage image = new BufferedImage(fontSize * 2, fontSize * 2, - BufferedImage.TYPE_INT_ARGB); + BufferedImage image = new BufferedImage(font.getSize() * 2, + font.getSize() * 2, BufferedImage.TYPE_INT_ARGB); Graphics2D gr = image.createGraphics(); gr.setFont(font); FontMetrics fm = gr.getFontMetrics(); @@ -393,8 +285,188 @@ public class GlyphMaker { 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); + } +} + +/** + * GlyphMaker presents unified interface to all of its supported fonts to + * clients. + */ +public class GlyphMaker { + + // ------------------------------------------------------------------------ + // Constants -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * The mono font resource filename (terminus). + */ + private static final String MONO = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf"; + + /** + * The CJK font resource filename. + */ + private static final String cjkFontFilename = "NotoSansMonoCJKtc-Regular.otf"; + + /** + * The emoji font resource filename. + */ + private static final String emojiFontFilename = "OpenSansEmoji.ttf"; + + /** + * The fallback font resource filename. + */ + private static final String fallbackFontFilename = ""; + + // ------------------------------------------------------------------------ + // Variables -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * If true, enable debug messages. + */ + private static boolean DEBUG = false; + + /** + * Cache of font bundles by size. + */ + private static HashMap makers = new HashMap(); + + /** + * The instance that has the mono (default) font. + */ + private GlyphMakerFont makerMono; + + /** + * The instance that has the CJK font. + */ + private GlyphMakerFont makerCjk; + + /** + * The instance that has the emoji font. + */ + private GlyphMakerFont makerEmoji; + + /** + * The instance that has the fallback font. + */ + private GlyphMakerFont makerFallback; + + // ------------------------------------------------------------------------ + // Constructors ----------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Create an instance with references to the necessary fonts. + * + * @param fontSize the size of these fonts in pixels + */ + private GlyphMaker(final int fontSize) { + makerMono = new GlyphMakerFont(MONO, 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); + } + + // ------------------------------------------------------------------------ + // GlyphMaker ------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Obtain the GlyphMaker instance for a particular font size. + * + * @param fontSize the size of these fonts in pixels + * @return the instance + */ + public static GlyphMaker getInstance(final int fontSize) { + synchronized (GlyphMaker.class) { + GlyphMaker maker = makers.get(fontSize); + if (maker == null) { + maker = new GlyphMaker(fontSize); + makers.put(fontSize, maker); + } + return maker; + } + } + + /** + * Get a glyph image. + * + * @param cell the character to draw + * @param cellWidth the width of the text cell to draw into + * @param cellHeight the height of the text cell to draw into + * @return the glyph as an image + */ + public BufferedImage getImage(final Cell cell, final int cellWidth, + final int cellHeight) { + + return getImage(cell, cellWidth, cellHeight, true); + } + + /** + * Get a glyph image. + * + * @param cell the character to draw + * @param cellWidth the width of the text cell to draw into + * @param cellHeight the height of the text cell to draw into + * @param blinkVisible if true, the cell is visible if it is blinking + * @return the glyph as an image + */ + public BufferedImage getImage(final Cell cell, final int cellWidth, + final int cellHeight, final boolean blinkVisible) { + + int ch = cell.getChar(); + if (StringUtils.isCjk(ch)) { + if (makerCjk.canDisplay(ch)) { + return makerCjk.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. + if (makerMono.canDisplay(ch)) { + return makerMono.getImage(cell, cellWidth, cellHeight, + blinkVisible); + } + + return makerFallback.getImage(cell, cellWidth, cellHeight, + blinkVisible); + } + }