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();
205 cellColor
.setTo(cell
);
208 if (cell
.isReverse()) {
209 cellColor
.setForeColor(cell
.getBackColor());
210 cellColor
.setBackColor(cell
.getForeColor());
213 // Draw the background rectangle, then the foreground character.
214 gr2
.setColor(SwingTerminal
.attrToBackgroundColor(cellColor
));
215 gr2
.fillRect(0, 0, cellWidth
, cellHeight
);
217 // Handle blink and underline
219 || (cell
.isBlink() && blinkVisible
)
221 gr2
.setColor(SwingTerminal
.attrToForegroundColor(cellColor
));
222 char [] chars
= new char[1];
223 chars
[0] = cell
.getChar();
224 gr2
.drawChars(chars
, 0, 1, textAdjustX
,
225 cellHeight
- maxDescent
+ textAdjustY
);
227 if (cell
.isUnderline()) {
228 gr2
.fillRect(0, cellHeight
- 2, cellWidth
, 2);
233 // We need a new key that will not be mutated by invertCell().
234 Cell key
= new Cell();
236 if (cell
.isBlink() && !blinkVisible
) {
237 glyphCacheBlink
.put(key
, image
);
239 glyphCache
.put(key
, image
);
246 * Figure out my font dimensions.
248 private void getFontDimensions() {
249 glyphCacheBlink
= new HashMap
<Cell
, BufferedImage
>();
250 glyphCache
= new HashMap
<Cell
, BufferedImage
>();
252 BufferedImage image
= new BufferedImage(font
.getSize() * 2,
253 font
.getSize() * 2, BufferedImage
.TYPE_INT_ARGB
);
254 Graphics2D gr
= image
.createGraphics();
256 FontMetrics fm
= gr
.getFontMetrics();
257 maxDescent
= fm
.getMaxDescent();
258 Rectangle2D bounds
= fm
.getMaxCharBounds(gr
);
259 int leading
= fm
.getLeading();
260 fontTextWidth
= (int)Math
.round(bounds
.getWidth());
261 // fontTextHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
263 // This produces the same number, but works better for ugly
265 fontTextHeight
= fm
.getMaxAscent() + maxDescent
- leading
;
268 textHeight
= fontTextHeight
+ textAdjustHeight
;
269 textWidth
= fontTextWidth
+ textAdjustWidth
;
271 gotFontDimensions
= true;
277 * GlyphMaker presents unified interface to all of its supported fonts to
280 public class GlyphMaker
{
282 // ------------------------------------------------------------------------
283 // Constants --------------------------------------------------------------
284 // ------------------------------------------------------------------------
287 * The mono font resource filename (terminus).
289 private static final String MONO
= "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
292 * The CJKhk font resource filename.
294 // private static final String CJKhk = "NotoSansMonoCJKhk-Regular.otf";
297 * The CJKkr font resource filename.
299 // private static final String CJKkr = "NotoSansMonoCJKkr-Regular.otf";
302 * The CJKtc font resource filename.
304 private static final String CJKtc
= "NotoSansMonoCJKtc-Regular.otf";
306 // ------------------------------------------------------------------------
307 // Variables --------------------------------------------------------------
308 // ------------------------------------------------------------------------
311 * If true, enable debug messages.
313 private static boolean DEBUG
= false;
316 * Cache of font bundles by size.
318 private static HashMap
<Integer
, GlyphMaker
> makers
= new HashMap
<Integer
, GlyphMaker
>();
321 * The instance that has the mono (default) font.
323 private GlyphMakerFont makerMono
;
326 * The instance that has the CJKhk font.
328 // private GlyphMakerFont makerCJKhk;
331 * The instance that has the CJKkr font.
333 // private GlyphMakerFont makerCJKkr;
336 * The instance that has the CJKtc font.
338 private GlyphMakerFont makerCJKtc
;
340 // ------------------------------------------------------------------------
341 // Constructors -----------------------------------------------------------
342 // ------------------------------------------------------------------------
345 * Create an instance with references to the necessary fonts.
347 * @param fontSize the size of these fonts in pixels
349 private GlyphMaker(final int fontSize
) {
350 makerMono
= new GlyphMakerFont(MONO
, fontSize
);
351 // makerCJKhk = new GlyphMakerFont(CJKhk, fontSize);
352 // makerCJKkr = new GlyphMakerFont(CJKkr, fontSize);
353 makerCJKtc
= new GlyphMakerFont(CJKtc
, fontSize
);
356 // ------------------------------------------------------------------------
357 // GlyphMaker -------------------------------------------------------------
358 // ------------------------------------------------------------------------
361 * Obtain the GlyphMaker instance for a particular font size.
363 * @param fontSize the size of these fonts in pixels
364 * @return the instance
366 public static GlyphMaker
getInstance(final int fontSize
) {
367 synchronized (GlyphMaker
.class) {
368 GlyphMaker maker
= makers
.get(fontSize
);
370 maker
= new GlyphMaker(fontSize
);
371 makers
.put(fontSize
, maker
);
380 * @param cell the character to draw
381 * @param cellWidth the width of the text cell to draw into
382 * @param cellHeight the height of the text cell to draw into
383 * @return the glyph as an image
385 public BufferedImage
getImage(final Cell cell
, final int cellWidth
,
386 final int cellHeight
) {
388 return getImage(cell
, cellWidth
, cellHeight
, true);
394 * @param cell the character to draw
395 * @param cellWidth the width of the text cell to draw into
396 * @param cellHeight the height of the text cell to draw into
397 * @param blinkVisible if true, the cell is visible if it is blinking
398 * @return the glyph as an image
400 public BufferedImage
getImage(final Cell cell
, final int cellWidth
,
401 final int cellHeight
, final boolean blinkVisible
) {
403 char ch
= cell
.getChar();
405 if ((ch >= 0x4e00) && (ch <= 0x9fff)) {
406 return makerCJKhk.getImage(cell, cellWidth, cellHeight, blinkVisible);
408 if ((ch >= 0x4e00) && (ch <= 0x9fff)) {
409 return makerCJKkr.getImage(cell, cellWidth, cellHeight, blinkVisible);
412 if ((ch
>= 0x2e80) && (ch
<= 0x9fff)) {
413 return makerCJKtc
.getImage(cell
, cellWidth
, cellHeight
, blinkVisible
);
416 // When all else fails, use the default.
417 return makerMono
.getImage(cell
, cellWidth
, cellHeight
, blinkVisible
);