#37 don't resize except initial and font change
[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
027de5ae 204 Cell cellColor = new Cell(cell);
0d86ab84
KL
205
206 // Check for reverse
207 if (cell.isReverse()) {
208 cellColor.setForeColor(cell.getBackColor());
209 cellColor.setBackColor(cell.getForeColor());
210 }
211
212 // Draw the background rectangle, then the foreground character.
213 gr2.setColor(SwingTerminal.attrToBackgroundColor(cellColor));
214 gr2.fillRect(0, 0, cellWidth, cellHeight);
215
216 // Handle blink and underline
217 if (!cell.isBlink()
218 || (cell.isBlink() && blinkVisible)
219 ) {
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);
225
226 if (cell.isUnderline()) {
227 gr2.fillRect(0, cellHeight - 2, cellWidth, 2);
228 }
229 }
230 gr2.dispose();
231
232 // We need a new key that will not be mutated by invertCell().
027de5ae 233 Cell key = new Cell(cell);
0d86ab84
KL
234 if (cell.isBlink() && !blinkVisible) {
235 glyphCacheBlink.put(key, image);
236 } else {
237 glyphCache.put(key, image);
238 }
239
240 return image;
241 }
242
243 /**
244 * Figure out my font dimensions.
245 */
246 private void getFontDimensions() {
247 glyphCacheBlink = new HashMap<Cell, BufferedImage>();
248 glyphCache = new HashMap<Cell, BufferedImage>();
249
9588c713
KL
250 BufferedImage image = new BufferedImage(font.getSize() * 2,
251 font.getSize() * 2, BufferedImage.TYPE_INT_ARGB);
0d86ab84
KL
252 Graphics2D gr = image.createGraphics();
253 gr.setFont(font);
254 FontMetrics fm = gr.getFontMetrics();
255 maxDescent = fm.getMaxDescent();
256 Rectangle2D bounds = fm.getMaxCharBounds(gr);
257 int leading = fm.getLeading();
258 fontTextWidth = (int)Math.round(bounds.getWidth());
259 // fontTextHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
260
261 // This produces the same number, but works better for ugly
262 // monospace.
263 fontTextHeight = fm.getMaxAscent() + maxDescent - leading;
264 gr.dispose();
265
266 textHeight = fontTextHeight + textAdjustHeight;
267 textWidth = fontTextWidth + textAdjustWidth;
268
269 gotFontDimensions = true;
270 }
271
272}
9588c713
KL
273
274/**
275 * GlyphMaker presents unified interface to all of its supported fonts to
276 * clients.
277 */
278public class GlyphMaker {
279
280 // ------------------------------------------------------------------------
281 // Constants --------------------------------------------------------------
282 // ------------------------------------------------------------------------
283
284 /**
285 * The mono font resource filename (terminus).
286 */
287 private static final String MONO = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
288
289 /**
290 * The CJKhk font resource filename.
291 */
292 // private static final String CJKhk = "NotoSansMonoCJKhk-Regular.otf";
293
294 /**
295 * The CJKkr font resource filename.
296 */
297 // private static final String CJKkr = "NotoSansMonoCJKkr-Regular.otf";
298
299 /**
300 * The CJKtc font resource filename.
301 */
302 private static final String CJKtc = "NotoSansMonoCJKtc-Regular.otf";
303
304 // ------------------------------------------------------------------------
305 // Variables --------------------------------------------------------------
306 // ------------------------------------------------------------------------
307
308 /**
309 * If true, enable debug messages.
310 */
311 private static boolean DEBUG = false;
312
313 /**
314 * Cache of font bundles by size.
315 */
316 private static HashMap<Integer, GlyphMaker> makers = new HashMap<Integer, GlyphMaker>();
317
318 /**
319 * The instance that has the mono (default) font.
320 */
321 private GlyphMakerFont makerMono;
322
323 /**
324 * The instance that has the CJKhk font.
325 */
326 // private GlyphMakerFont makerCJKhk;
327
328 /**
329 * The instance that has the CJKkr font.
330 */
331 // private GlyphMakerFont makerCJKkr;
332
333 /**
334 * The instance that has the CJKtc font.
335 */
336 private GlyphMakerFont makerCJKtc;
337
338 // ------------------------------------------------------------------------
339 // Constructors -----------------------------------------------------------
340 // ------------------------------------------------------------------------
341
342 /**
343 * Create an instance with references to the necessary fonts.
344 *
345 * @param fontSize the size of these fonts in pixels
346 */
347 private GlyphMaker(final int fontSize) {
348 makerMono = new GlyphMakerFont(MONO, fontSize);
349 // makerCJKhk = new GlyphMakerFont(CJKhk, fontSize);
350 // makerCJKkr = new GlyphMakerFont(CJKkr, fontSize);
351 makerCJKtc = new GlyphMakerFont(CJKtc, fontSize);
352 }
353
354 // ------------------------------------------------------------------------
355 // GlyphMaker -------------------------------------------------------------
356 // ------------------------------------------------------------------------
357
358 /**
359 * Obtain the GlyphMaker instance for a particular font size.
360 *
361 * @param fontSize the size of these fonts in pixels
362 * @return the instance
363 */
364 public static GlyphMaker getInstance(final int fontSize) {
365 synchronized (GlyphMaker.class) {
366 GlyphMaker maker = makers.get(fontSize);
367 if (maker == null) {
368 maker = new GlyphMaker(fontSize);
369 makers.put(fontSize, maker);
370 }
371 return maker;
372 }
373 }
374
375 /**
376 * Get a glyph image.
377 *
378 * @param cell the character to draw
379 * @param cellWidth the width of the text cell to draw into
380 * @param cellHeight the height of the text cell to draw into
381 * @return the glyph as an image
382 */
383 public BufferedImage getImage(final Cell cell, final int cellWidth,
384 final int cellHeight) {
385
386 return getImage(cell, cellWidth, cellHeight, true);
387 }
388
389 /**
390 * Get a glyph image.
391 *
392 * @param cell the character to draw
393 * @param cellWidth the width of the text cell to draw into
394 * @param cellHeight the height of the text cell to draw into
395 * @param blinkVisible if true, the cell is visible if it is blinking
396 * @return the glyph as an image
397 */
398 public BufferedImage getImage(final Cell cell, final int cellWidth,
399 final int cellHeight, final boolean blinkVisible) {
400
401 char ch = cell.getChar();
402 /*
403 if ((ch >= 0x4e00) && (ch <= 0x9fff)) {
404 return makerCJKhk.getImage(cell, cellWidth, cellHeight, blinkVisible);
405 }
406 if ((ch >= 0x4e00) && (ch <= 0x9fff)) {
407 return makerCJKkr.getImage(cell, cellWidth, cellHeight, blinkVisible);
408 }
409 */
410 if ((ch >= 0x2e80) && (ch <= 0x9fff)) {
411 return makerCJKtc.getImage(cell, cellWidth, cellHeight, blinkVisible);
412 }
413
414 // When all else fails, use the default.
415 return makerMono.getImage(cell, cellWidth, cellHeight, blinkVisible);
416 }
417
418}