#49 move drawing to separate thread
[fanfix.git] / src / jexer / backend / GlyphMaker.java
CommitLineData
0d86ab84
KL
1/*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29package jexer.backend;
30
31import java.awt.Font;
32import java.awt.FontMetrics;
33import java.awt.Graphics2D;
34import java.awt.geom.Rectangle2D;
35import java.awt.image.BufferedImage;
36import java.io.InputStream;
37import java.io.IOException;
38import java.util.HashMap;
39
40import jexer.bits.Cell;
41
42/**
9588c713 43 * GlyphMakerFont creates glyphs as bitmaps from a font.
0d86ab84 44 */
9588c713 45class GlyphMakerFont {
0d86ab84
KL
46
47 // ------------------------------------------------------------------------
48 // Constants --------------------------------------------------------------
49 // ------------------------------------------------------------------------
50
0d86ab84
KL
51 // ------------------------------------------------------------------------
52 // Variables --------------------------------------------------------------
53 // ------------------------------------------------------------------------
54
55 /**
56 * If true, enable debug messages.
57 */
58 private static boolean DEBUG = false;
59
0d86ab84
KL
60 /**
61 * If true, we were successful at getting the font dimensions.
62 */
63 private boolean gotFontDimensions = false;
64
65 /**
66 * The currently selected font.
67 */
68 private Font font = null;
69
0d86ab84
KL
70 /**
71 * Width of a character cell in pixels.
72 */
73 private int textWidth = 1;
74
75 /**
76 * Height of a character cell in pixels.
77 */
78 private int textHeight = 1;
79
80 /**
81 * Width of a character cell in pixels, as reported by font.
82 */
83 private int fontTextWidth = 1;
84
85 /**
86 * Height of a character cell in pixels, as reported by font.
87 */
88 private int fontTextHeight = 1;
89
90 /**
91 * Descent of a character cell in pixels.
92 */
93 private int maxDescent = 0;
94
95 /**
96 * System-dependent Y adjustment for text in the character cell.
97 */
98 private int textAdjustY = 0;
99
100 /**
101 * System-dependent X adjustment for text in the character cell.
102 */
103 private int textAdjustX = 0;
104
105 /**
106 * System-dependent height adjustment for text in the character cell.
107 */
108 private int textAdjustHeight = 0;
109
110 /**
111 * System-dependent width adjustment for text in the character cell.
112 */
113 private int textAdjustWidth = 0;
114
115 /**
116 * A cache of previously-rendered glyphs for blinking text, when it is
117 * not visible.
118 */
119 private HashMap<Cell, BufferedImage> glyphCacheBlink;
120
121 /**
122 * A cache of previously-rendered glyphs for non-blinking, or
123 * blinking-and-visible, text.
124 */
125 private HashMap<Cell, BufferedImage> glyphCache;
126
127 // ------------------------------------------------------------------------
128 // Constructors -----------------------------------------------------------
129 // ------------------------------------------------------------------------
130
0d86ab84
KL
131 /**
132 * Public constructor.
133 *
9588c713 134 * @param filename the resource filename of the font to use
0d86ab84
KL
135 * @param fontSize the size of font to use
136 */
9588c713
KL
137 public GlyphMakerFont(final String filename, final int fontSize) {
138 Font fontRoot = null;
139 try {
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) {
145 e.printStackTrace();
146 font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
147 } catch (java.io.IOException e) {
148 e.printStackTrace();
149 font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
150 }
0d86ab84
KL
151 }
152
153 // ------------------------------------------------------------------------
9588c713 154 // GlyphMakerFont ---------------------------------------------------------
0d86ab84
KL
155 // ------------------------------------------------------------------------
156
0d86ab84
KL
157 /**
158 * Get a glyph image.
159 *
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
164 */
165 public BufferedImage getImage(final Cell cell, final int cellWidth,
166 final int cellHeight) {
167
168 return getImage(cell, cellWidth, cellHeight, true);
169 }
170
171 /**
172 * Get a glyph image.
173 *
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
179 */
180 public BufferedImage getImage(final Cell cell, final int cellWidth,
181 final int cellHeight, final boolean blinkVisible) {
182
183 if (gotFontDimensions == false) {
184 // Lazy-load the text width/height and adjustments.
185 getFontDimensions();
186 }
187
188 BufferedImage image = null;
189 if (cell.isBlink() && !blinkVisible) {
190 image = glyphCacheBlink.get(cell);
191 } else {
192 image = glyphCache.get(cell);
193 }
194 if (image != null) {
195 return image;
196 }
197
198 // Generate glyph and draw it.
199 image = new BufferedImage(cellWidth, cellHeight,
200 BufferedImage.TYPE_INT_ARGB);
201 Graphics2D gr2 = image.createGraphics();
202 gr2.setFont(font);
203
204 Cell cellColor = new Cell();
205 cellColor.setTo(cell);
206
207 // Check for reverse
208 if (cell.isReverse()) {
209 cellColor.setForeColor(cell.getBackColor());
210 cellColor.setBackColor(cell.getForeColor());
211 }
212
213 // Draw the background rectangle, then the foreground character.
214 gr2.setColor(SwingTerminal.attrToBackgroundColor(cellColor));
215 gr2.fillRect(0, 0, cellWidth, cellHeight);
216
217 // Handle blink and underline
218 if (!cell.isBlink()
219 || (cell.isBlink() && blinkVisible)
220 ) {
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);
226
227 if (cell.isUnderline()) {
228 gr2.fillRect(0, cellHeight - 2, cellWidth, 2);
229 }
230 }
231 gr2.dispose();
232
233 // We need a new key that will not be mutated by invertCell().
234 Cell key = new Cell();
235 key.setTo(cell);
236 if (cell.isBlink() && !blinkVisible) {
237 glyphCacheBlink.put(key, image);
238 } else {
239 glyphCache.put(key, image);
240 }
241
242 return image;
243 }
244
245 /**
246 * Figure out my font dimensions.
247 */
248 private void getFontDimensions() {
249 glyphCacheBlink = new HashMap<Cell, BufferedImage>();
250 glyphCache = new HashMap<Cell, BufferedImage>();
251
9588c713
KL
252 BufferedImage image = new BufferedImage(font.getSize() * 2,
253 font.getSize() * 2, BufferedImage.TYPE_INT_ARGB);
0d86ab84
KL
254 Graphics2D gr = image.createGraphics();
255 gr.setFont(font);
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;
262
263 // This produces the same number, but works better for ugly
264 // monospace.
265 fontTextHeight = fm.getMaxAscent() + maxDescent - leading;
266 gr.dispose();
267
268 textHeight = fontTextHeight + textAdjustHeight;
269 textWidth = fontTextWidth + textAdjustWidth;
270
271 gotFontDimensions = true;
272 }
273
274}
9588c713
KL
275
276/**
277 * GlyphMaker presents unified interface to all of its supported fonts to
278 * clients.
279 */
280public class GlyphMaker {
281
282 // ------------------------------------------------------------------------
283 // Constants --------------------------------------------------------------
284 // ------------------------------------------------------------------------
285
286 /**
287 * The mono font resource filename (terminus).
288 */
289 private static final String MONO = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
290
291 /**
292 * The CJKhk font resource filename.
293 */
294 // private static final String CJKhk = "NotoSansMonoCJKhk-Regular.otf";
295
296 /**
297 * The CJKkr font resource filename.
298 */
299 // private static final String CJKkr = "NotoSansMonoCJKkr-Regular.otf";
300
301 /**
302 * The CJKtc font resource filename.
303 */
304 private static final String CJKtc = "NotoSansMonoCJKtc-Regular.otf";
305
306 // ------------------------------------------------------------------------
307 // Variables --------------------------------------------------------------
308 // ------------------------------------------------------------------------
309
310 /**
311 * If true, enable debug messages.
312 */
313 private static boolean DEBUG = false;
314
315 /**
316 * Cache of font bundles by size.
317 */
318 private static HashMap<Integer, GlyphMaker> makers = new HashMap<Integer, GlyphMaker>();
319
320 /**
321 * The instance that has the mono (default) font.
322 */
323 private GlyphMakerFont makerMono;
324
325 /**
326 * The instance that has the CJKhk font.
327 */
328 // private GlyphMakerFont makerCJKhk;
329
330 /**
331 * The instance that has the CJKkr font.
332 */
333 // private GlyphMakerFont makerCJKkr;
334
335 /**
336 * The instance that has the CJKtc font.
337 */
338 private GlyphMakerFont makerCJKtc;
339
340 // ------------------------------------------------------------------------
341 // Constructors -----------------------------------------------------------
342 // ------------------------------------------------------------------------
343
344 /**
345 * Create an instance with references to the necessary fonts.
346 *
347 * @param fontSize the size of these fonts in pixels
348 */
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);
354 }
355
356 // ------------------------------------------------------------------------
357 // GlyphMaker -------------------------------------------------------------
358 // ------------------------------------------------------------------------
359
360 /**
361 * Obtain the GlyphMaker instance for a particular font size.
362 *
363 * @param fontSize the size of these fonts in pixels
364 * @return the instance
365 */
366 public static GlyphMaker getInstance(final int fontSize) {
367 synchronized (GlyphMaker.class) {
368 GlyphMaker maker = makers.get(fontSize);
369 if (maker == null) {
370 maker = new GlyphMaker(fontSize);
371 makers.put(fontSize, maker);
372 }
373 return maker;
374 }
375 }
376
377 /**
378 * Get a glyph image.
379 *
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
384 */
385 public BufferedImage getImage(final Cell cell, final int cellWidth,
386 final int cellHeight) {
387
388 return getImage(cell, cellWidth, cellHeight, true);
389 }
390
391 /**
392 * Get a glyph image.
393 *
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
399 */
400 public BufferedImage getImage(final Cell cell, final int cellWidth,
401 final int cellHeight, final boolean blinkVisible) {
402
403 char ch = cell.getChar();
404 /*
405 if ((ch >= 0x4e00) && (ch <= 0x9fff)) {
406 return makerCJKhk.getImage(cell, cellWidth, cellHeight, blinkVisible);
407 }
408 if ((ch >= 0x4e00) && (ch <= 0x9fff)) {
409 return makerCJKkr.getImage(cell, cellWidth, cellHeight, blinkVisible);
410 }
411 */
412 if ((ch >= 0x2e80) && (ch <= 0x9fff)) {
413 return makerCJKtc.getImage(cell, cellWidth, cellHeight, blinkVisible);
414 }
415
416 // When all else fails, use the default.
417 return makerMono.getImage(cell, cellWidth, cellHeight, blinkVisible);
418 }
419
420}