Change build scripts
[jvcard.git] / src / com / googlecode / lanterna / terminal / swing / AWTTerminalFontConfiguration.java
CommitLineData
a3b510ab
NR
1/*
2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
3 *
4 * lanterna is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Copyright (C) 2010-2015 Martin
18 */
19package com.googlecode.lanterna.terminal.swing;
20
21import com.googlecode.lanterna.Symbols;
22import com.googlecode.lanterna.TextCharacter;
23
24import java.awt.*;
25import java.awt.font.FontRenderContext;
26import java.awt.geom.Rectangle2D;
27import java.lang.reflect.Field;
28import java.lang.reflect.Modifier;
29import java.util.*;
30import java.util.List;
31
32/**
33 * This class encapsulates the font information used by an {@link AWTTerminal}. By customizing this class, you can
34 * choose which fonts are going to be used by an {@link AWTTerminal} component and some other related settings.
35 * @author martin
36 */
37public class AWTTerminalFontConfiguration {
38
39 /**
40 * Controls how the SGR bold will take effect when enabled on a character. Mainly this is controlling if the
41 * character should be rendered with a bold font or not. The reason for this is that some characters, notably the
42 * lines and double-lines in defined in Symbol, usually doesn't look very good with bold font when you try to
43 * construct a GUI.
44 */
45 public enum BoldMode {
46 /**
47 * All characters with SGR Bold enabled will be rendered using a bold font
48 */
49 EVERYTHING,
50 /**
51 * All characters with SGR Bold enabled, except for the characters defined as constants in Symbols class, will
52 * be rendered using a bold font
53 */
54 EVERYTHING_BUT_SYMBOLS,
55 /**
56 * Bold font will not be used for characters with SGR bold enabled
57 */
58 NOTHING,
59 ;
60 }
61
62 private static final Set<String> MONOSPACE_CHECK_OVERRIDE = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
63 "VL Gothic Regular",
64 "NanumGothic",
65 "WenQuanYi Zen Hei Mono",
66 "WenQuanYi Zen Hei",
67 "AR PL UMing TW",
68 "AR PL UMing HK",
69 "AR PL UMing CN"
70 )));
71
72 private static List<Font> getDefaultWindowsFonts() {
73 return Collections.unmodifiableList(Arrays.asList(
74 new Font("Courier New", Font.PLAIN, 14), //Monospaced can look pretty bad on Windows, so let's override it
75 new Font("Monospaced", Font.PLAIN, 14)));
76 }
77
78 private static List<Font> getDefaultLinuxFonts() {
79 return Collections.unmodifiableList(Arrays.asList(
80 new Font("DejaVu Sans Mono", Font.PLAIN, 14),
81 new Font("Monospaced", Font.PLAIN, 14),
82 //Below, these should be redundant (Monospaced is supposed to catch-all)
83 // but Java 6 seems to have issues with finding monospaced fonts sometimes
84 new Font("Ubuntu Mono", Font.PLAIN, 14),
85 new Font("FreeMono", Font.PLAIN, 14),
86 new Font("Liberation Mono", Font.PLAIN, 14),
87 new Font("VL Gothic Regular", Font.PLAIN, 14),
88 new Font("NanumGothic", Font.PLAIN, 14),
89 new Font("WenQuanYi Zen Hei Mono", Font.PLAIN, 14),
90 new Font("WenQuanYi Zen Hei", Font.PLAIN, 14),
91 new Font("AR PL UMing TW", Font.PLAIN, 14),
92 new Font("AR PL UMing HK", Font.PLAIN, 14),
93 new Font("AR PL UMing CN", Font.PLAIN, 14)));
94 }
95
96 private static List<Font> getDefaultFonts() {
97 return Collections.unmodifiableList(Collections.singletonList(
98 new Font("Monospaced", Font.PLAIN, 14)));
99 }
100
101 protected static Font[] selectDefaultFont() {
102 String osName = System.getProperty("os.name", "").toLowerCase();
103 if(osName.contains("win")) {
104 List<Font> windowsFonts = getDefaultWindowsFonts();
105 return windowsFonts.toArray(new Font[windowsFonts.size()]);
106 }
107 else if(osName.contains("linux")) {
108 List<Font> linuxFonts = getDefaultLinuxFonts();
109 return linuxFonts.toArray(new Font[linuxFonts.size()]);
110 }
111 else {
112 List<Font> defaultFonts = getDefaultFonts();
113 return defaultFonts.toArray(new Font[defaultFonts.size()]);
114 }
115 }
116
117 /**
118 * This is the default font settings that will be used if you don't specify anything
119 */
120 public static AWTTerminalFontConfiguration getDefault() {
121 return newInstance(filterMonospaced(selectDefaultFont()));
122 }
123
124 /**
125 * Given an array of fonts, returns another array with only the ones that are monospaced. The fonts in the result
126 * will have the same order as in which they came in. A font is considered monospaced if the width of 'i' and 'W' is
127 * the same.
128 * @param fonts Fonts to filter monospaced fonts from
129 * @return Array with the fonts from the input parameter that were monospaced
130 */
131 public static Font[] filterMonospaced(Font... fonts) {
132 List<Font> result = new ArrayList<Font>(fonts.length);
133 for(Font font: fonts) {
134 if (isFontMonospaced(font)) {
135 result.add(font);
136 }
137 }
138 return result.toArray(new Font[result.size()]);
139 }
140
141 /**
142 * Creates a new font configuration from a list of fonts in order of priority. This works by having the terminal
143 * attempt to draw each character with the fonts in the order they are specified in and stop once we find a font
144 * that can actually draw the character. For ASCII characters, it's very likely that the first font will always be
145 * used.
146 * @param fontsInOrderOfPriority Fonts to use when drawing text, in order of priority
147 * @return Font configuration built from the font list
148 */
149 @SuppressWarnings("WeakerAccess")
150 public static AWTTerminalFontConfiguration newInstance(Font... fontsInOrderOfPriority) {
151 return new AWTTerminalFontConfiguration(true, BoldMode.EVERYTHING_BUT_SYMBOLS, fontsInOrderOfPriority);
152 }
153
154 private final List<Font> fontPriority;
155 private final int fontWidth;
156 private final int fontHeight;
157 private final boolean useAntiAliasing;
158 private final BoldMode boldMode;
159
160 @SuppressWarnings("WeakerAccess")
161 protected AWTTerminalFontConfiguration(boolean useAntiAliasing, BoldMode boldMode, Font... fontsInOrderOfPriority) {
162 if(fontsInOrderOfPriority == null || fontsInOrderOfPriority.length == 0) {
163 throw new IllegalArgumentException("Must pass in a valid list of fonts to SwingTerminalFontConfiguration");
164 }
165 this.useAntiAliasing = useAntiAliasing;
166 this.boldMode = boldMode;
167 this.fontPriority = new ArrayList<Font>(Arrays.asList(fontsInOrderOfPriority));
168 this.fontWidth = getFontWidth(fontPriority.get(0));
169 this.fontHeight = getFontHeight(fontPriority.get(0));
170
171 //Make sure all the fonts are monospace
172 for(Font font: fontPriority) {
173 if(!isFontMonospaced(font)) {
174 throw new IllegalArgumentException("Font " + font + " isn't monospaced!");
175 }
176 }
177
178 //Make sure all lower-priority fonts are less or equal in width and height, shrink if necessary
179 for(int i = 1; i < fontPriority.size(); i++) {
180 Font font = fontPriority.get(i);
181 while(getFontWidth(font) > fontWidth || getFontHeight(font) > fontHeight) {
182 float newSize = font.getSize2D() - 0.5f;
183 if(newSize < 0.01) {
184 throw new IllegalStateException("Unable to shrink font " + (i+1) + " to fit the size of highest priority font " + fontPriority.get(0));
185 }
186 font = font.deriveFont(newSize);
187 fontPriority.set(i, font);
188 }
189 }
190 }
191
192 Font getFontForCharacter(TextCharacter character) {
193 Font normalFont = getFontForCharacter(character.getCharacter());
194 if(boldMode == BoldMode.EVERYTHING || (boldMode == BoldMode.EVERYTHING_BUT_SYMBOLS && isNotASymbol(character.getCharacter()))) {
195 if(character.isBold()) {
196 normalFont = normalFont.deriveFont(Font.BOLD);
197 }
198 }
199 return normalFont;
200 }
201
202 private Font getFontForCharacter(char c) {
203 for(Font font: fontPriority) {
204 if(font.canDisplay(c)) {
205 return font;
206 }
207 }
208 //No available font here, what to do...?
209 return fontPriority.get(0);
210 }
211
212 int getFontWidth() {
213 return fontWidth;
214 }
215
216 int getFontHeight() {
217 return fontHeight;
218 }
219
220 boolean isAntiAliased() {
221 return useAntiAliasing;
222 }
223
224 private static boolean isFontMonospaced(Font font) {
225 if(MONOSPACE_CHECK_OVERRIDE.contains(font.getName())) {
226 return true;
227 }
228 FontRenderContext frc = new FontRenderContext(
229 null,
230 RenderingHints.VALUE_TEXT_ANTIALIAS_OFF,
231 RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
232 Rectangle2D iBounds = font.getStringBounds("i", frc);
233 Rectangle2D mBounds = font.getStringBounds("W", frc);
234 return iBounds.getWidth() == mBounds.getWidth();
235 }
236
237 private int getFontWidth(Font font) {
238 return (int)font.getStringBounds("W", getFontRenderContext()).getWidth();
239 }
240
241 private int getFontHeight(Font font) {
242 return (int)font.getStringBounds("W", getFontRenderContext()).getHeight();
243 }
244
245 private FontRenderContext getFontRenderContext() {
246 return new FontRenderContext(
247 null,
248 useAntiAliasing ?
249 RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF,
250 RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
251 }
252
253
254 private static final Set<Character> SYMBOLS_CACHE = new HashSet<Character>();
255 static {
256 for(Field field: Symbols.class.getFields()) {
257 if(field.getType() == char.class &&
258 (field.getModifiers() & Modifier.FINAL) != 0 &&
259 (field.getModifiers() & Modifier.STATIC) != 0) {
260 try {
261 SYMBOLS_CACHE.add(field.getChar(null));
262 }
263 catch(IllegalArgumentException ignore) {
264 //Should never happen!
265 }
266 catch(IllegalAccessException ignore) {
267 //Should never happen!
268 }
269 }
270 }
271 }
272
273 private boolean isNotASymbol(char character) {
274 return !SYMBOLS_CACHE.contains(character);
275 }
276}