2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 package jexer
.backend
;
32 import java
.awt
.FontMetrics
;
33 import java
.awt
.Graphics2D
;
34 import java
.awt
.geom
.Rectangle2D
;
35 import java
.awt
.image
.BufferedImage
;
36 import java
.io
.InputStream
;
37 import java
.io
.IOException
;
38 import java
.util
.HashMap
;
40 import jexer
.bits
.Cell
;
43 * GlyphMakerFont creates glyphs as bitmaps from a font.
45 class GlyphMakerFont
{
47 // ------------------------------------------------------------------------
48 // Constants --------------------------------------------------------------
49 // ------------------------------------------------------------------------
51 // ------------------------------------------------------------------------
52 // Variables --------------------------------------------------------------
53 // ------------------------------------------------------------------------
56 * If true, enable debug messages.
58 private static boolean DEBUG
= false;
61 * If true, we were successful at getting the font dimensions.
63 private boolean gotFontDimensions
= false;
66 * The currently selected font.
68 private Font font
= null;
71 * Width of a character cell in pixels.
73 private int textWidth
= 1;
76 * Height of a character cell in pixels.
78 private int textHeight
= 1;
81 * Width of a character cell in pixels, as reported by font.
83 private int fontTextWidth
= 1;
86 * Height of a character cell in pixels, as reported by font.
88 private int fontTextHeight
= 1;
91 * Descent of a character cell in pixels.
93 private int maxDescent
= 0;
96 * System-dependent Y adjustment for text in the character cell.
98 private int textAdjustY
= 0;
101 * System-dependent X adjustment for text in the character cell.
103 private int textAdjustX
= 0;
106 * System-dependent height adjustment for text in the character cell.
108 private int textAdjustHeight
= 0;
111 * System-dependent width adjustment for text in the character cell.
113 private int textAdjustWidth
= 0;
116 * A cache of previously-rendered glyphs for blinking text, when it is
119 private HashMap
<Cell
, BufferedImage
> glyphCacheBlink
;
122 * A cache of previously-rendered glyphs for non-blinking, or
123 * blinking-and-visible, text.
125 private HashMap
<Cell
, BufferedImage
> glyphCache
;
127 // ------------------------------------------------------------------------
128 // Constructors -----------------------------------------------------------
129 // ------------------------------------------------------------------------
132 * Public constructor.
134 * @param filename the resource filename of the font to use
135 * @param fontSize the size of font to use
137 public GlyphMakerFont(final String filename
, final int fontSize
) {
138 Font fontRoot
= null;
140 ClassLoader loader
= Thread
.currentThread().getContextClassLoader();
141 InputStream in
= loader
.getResourceAsStream(filename
);
142 fontRoot
= Font
.createFont(Font
.TRUETYPE_FONT
, in
);
143 font
= fontRoot
.deriveFont(Font
.PLAIN
, fontSize
);
144 } catch (java
.awt
.FontFormatException e
) {
146 font
= new Font(Font
.MONOSPACED
, Font
.PLAIN
, fontSize
);
147 } catch (java
.io
.IOException e
) {
149 font
= new Font(Font
.MONOSPACED
, Font
.PLAIN
, fontSize
);
153 // ------------------------------------------------------------------------
154 // GlyphMakerFont ---------------------------------------------------------
155 // ------------------------------------------------------------------------
160 * @param cell the character to draw
161 * @param cellWidth the width of the text cell to draw into
162 * @param cellHeight the height of the text cell to draw into
163 * @return the glyph as an image
165 public BufferedImage
getImage(final Cell cell
, final int cellWidth
,
166 final int cellHeight
) {
168 return getImage(cell
, cellWidth
, cellHeight
, true);
174 * @param cell the character to draw
175 * @param cellWidth the width of the text cell to draw into
176 * @param cellHeight the height of the text cell to draw into
177 * @param blinkVisible if true, the cell is visible if it is blinking
178 * @return the glyph as an image
180 public BufferedImage
getImage(final Cell cell
, final int cellWidth
,
181 final int cellHeight
, final boolean blinkVisible
) {
183 if (gotFontDimensions
== false) {
184 // Lazy-load the text width/height and adjustments.
188 BufferedImage image
= null;
189 if (cell
.isBlink() && !blinkVisible
) {
190 image
= glyphCacheBlink
.get(cell
);
192 image
= glyphCache
.get(cell
);
198 // Generate glyph and draw it.
199 image
= new BufferedImage(cellWidth
, cellHeight
,
200 BufferedImage
.TYPE_INT_ARGB
);
201 Graphics2D gr2
= image
.createGraphics();
204 Cell cellColor
= new Cell(cell
);
207 if (cell
.isReverse()) {
208 cellColor
.setForeColor(cell
.getBackColor());
209 cellColor
.setBackColor(cell
.getForeColor());
212 // Draw the background rectangle, then the foreground character.
213 gr2
.setColor(SwingTerminal
.attrToBackgroundColor(cellColor
));
214 gr2
.fillRect(0, 0, cellWidth
, cellHeight
);
216 // Handle blink and underline
218 || (cell
.isBlink() && blinkVisible
)
220 gr2
.setColor(SwingTerminal
.attrToForegroundColor(cellColor
));
221 char [] chars
= new char[1];
222 chars
[0] = cell
.getChar();
223 gr2
.drawChars(chars
, 0, 1, textAdjustX
,
224 cellHeight
- maxDescent
+ textAdjustY
);
226 if (cell
.isUnderline()) {
227 gr2
.fillRect(0, cellHeight
- 2, cellWidth
, 2);
232 // We need a new key that will not be mutated by invertCell().
233 Cell key
= new Cell(cell
);
234 if (cell
.isBlink() && !blinkVisible
) {
235 glyphCacheBlink
.put(key
, image
);
237 glyphCache
.put(key
, image
);
241 System.err.println("cellWidth " + cellWidth +
242 " cellHeight " + cellHeight + " image " + image);
249 * Figure out my font dimensions.
251 private void getFontDimensions() {
252 glyphCacheBlink
= new HashMap
<Cell
, BufferedImage
>();
253 glyphCache
= new HashMap
<Cell
, BufferedImage
>();
255 BufferedImage image
= new BufferedImage(font
.getSize() * 2,
256 font
.getSize() * 2, BufferedImage
.TYPE_INT_ARGB
);
257 Graphics2D gr
= image
.createGraphics();
259 FontMetrics fm
= gr
.getFontMetrics();
260 maxDescent
= fm
.getMaxDescent();
261 Rectangle2D bounds
= fm
.getMaxCharBounds(gr
);
262 int leading
= fm
.getLeading();
263 fontTextWidth
= (int)Math
.round(bounds
.getWidth());
264 // fontTextHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
266 // This produces the same number, but works better for ugly
268 fontTextHeight
= fm
.getMaxAscent() + maxDescent
- leading
;
271 textHeight
= fontTextHeight
+ textAdjustHeight
;
272 textWidth
= fontTextWidth
+ textAdjustWidth
;
274 System.err.println("font " + font);
275 System.err.println("fontTextWidth " + fontTextWidth);
276 System.err.println("fontTextHeight " + fontTextHeight);
277 System.err.println("textWidth " + textWidth);
278 System.err.println("textHeight " + textHeight);
281 gotFontDimensions
= true;
287 * GlyphMaker presents unified interface to all of its supported fonts to
290 public class GlyphMaker
{
292 // ------------------------------------------------------------------------
293 // Constants --------------------------------------------------------------
294 // ------------------------------------------------------------------------
297 * The mono font resource filename (terminus).
299 private static final String MONO
= "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
302 * The CJKhk font resource filename.
304 // private static final String CJKhk = "NotoSansMonoCJKhk-Regular.otf";
307 * The CJKkr font resource filename.
309 // private static final String CJKkr = "NotoSansMonoCJKkr-Regular.otf";
312 * The CJKtc font resource filename.
314 private static final String CJKtc
= "NotoSansMonoCJKtc-Regular.otf";
316 // ------------------------------------------------------------------------
317 // Variables --------------------------------------------------------------
318 // ------------------------------------------------------------------------
321 * If true, enable debug messages.
323 private static boolean DEBUG
= false;
326 * Cache of font bundles by size.
328 private static HashMap
<Integer
, GlyphMaker
> makers
= new HashMap
<Integer
, GlyphMaker
>();
331 * The instance that has the mono (default) font.
333 private GlyphMakerFont makerMono
;
336 * The instance that has the CJKhk font.
338 // private GlyphMakerFont makerCJKhk;
341 * The instance that has the CJKkr font.
343 // private GlyphMakerFont makerCJKkr;
346 * The instance that has the CJKtc font.
348 private GlyphMakerFont makerCJKtc
;
350 // ------------------------------------------------------------------------
351 // Constructors -----------------------------------------------------------
352 // ------------------------------------------------------------------------
355 * Create an instance with references to the necessary fonts.
357 * @param fontSize the size of these fonts in pixels
359 private GlyphMaker(final int fontSize
) {
360 assert (fontSize
> 3);
361 makerMono
= new GlyphMakerFont(MONO
, fontSize
);
362 // makerCJKhk = new GlyphMakerFont(CJKhk, fontSize);
363 // makerCJKkr = new GlyphMakerFont(CJKkr, fontSize);
364 makerCJKtc
= new GlyphMakerFont(CJKtc
, fontSize
);
367 // ------------------------------------------------------------------------
368 // GlyphMaker -------------------------------------------------------------
369 // ------------------------------------------------------------------------
372 * Obtain the GlyphMaker instance for a particular font size.
374 * @param fontSize the size of these fonts in pixels
375 * @return the instance
377 public static GlyphMaker
getInstance(final int fontSize
) {
378 synchronized (GlyphMaker
.class) {
379 GlyphMaker maker
= makers
.get(fontSize
);
381 maker
= new GlyphMaker(fontSize
);
382 makers
.put(fontSize
, maker
);
391 * @param cell the character to draw
392 * @param cellWidth the width of the text cell to draw into
393 * @param cellHeight the height of the text cell to draw into
394 * @return the glyph as an image
396 public BufferedImage
getImage(final Cell cell
, final int cellWidth
,
397 final int cellHeight
) {
399 return getImage(cell
, cellWidth
, cellHeight
, true);
405 * @param cell the character to draw
406 * @param cellWidth the width of the text cell to draw into
407 * @param cellHeight the height of the text cell to draw into
408 * @param blinkVisible if true, the cell is visible if it is blinking
409 * @return the glyph as an image
411 public BufferedImage
getImage(final Cell cell
, final int cellWidth
,
412 final int cellHeight
, final boolean blinkVisible
) {
414 char ch
= cell
.getChar();
416 if ((ch >= 0x4e00) && (ch <= 0x9fff)) {
417 return makerCJKhk.getImage(cell, cellWidth, cellHeight, blinkVisible);
419 if ((ch >= 0x4e00) && (ch <= 0x9fff)) {
420 return makerCJKkr.getImage(cell, cellWidth, cellHeight, blinkVisible);
423 if ((ch
>= 0x2e80) && (ch
<= 0x9fff)) {
424 return makerCJKtc
.getImage(cell
, cellWidth
, cellHeight
, blinkVisible
);
427 // When all else fails, use the default.
428 return makerMono
.getImage(cell
, cellWidth
, cellHeight
, blinkVisible
);