2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
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.
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.
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/>.
17 * Copyright (C) 2010-2015 Martin
19 package com
.googlecode
.lanterna
.terminal
.swing
;
21 import com
.googlecode
.lanterna
.Symbols
;
22 import com
.googlecode
.lanterna
.TextCharacter
;
25 import java
.awt
.font
.FontRenderContext
;
26 import java
.awt
.geom
.Rectangle2D
;
27 import java
.lang
.reflect
.Field
;
28 import java
.lang
.reflect
.Modifier
;
30 import java
.util
.List
;
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.
37 public class AWTTerminalFontConfiguration
{
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
45 public enum BoldMode
{
47 * All characters with SGR Bold enabled will be rendered using a bold font
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
54 EVERYTHING_BUT_SYMBOLS
,
56 * Bold font will not be used for characters with SGR bold enabled
62 private static final Set
<String
> MONOSPACE_CHECK_OVERRIDE
= Collections
.unmodifiableSet(new HashSet
<String
>(Arrays
.asList(
65 "WenQuanYi Zen Hei Mono",
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)));
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)));
96 private static List
<Font
> getDefaultFonts() {
97 return Collections
.unmodifiableList(Collections
.singletonList(
98 new Font("Monospaced", Font
.PLAIN
, 14)));
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()]);
107 else if(osName
.contains("linux")) {
108 List
<Font
> linuxFonts
= getDefaultLinuxFonts();
109 return linuxFonts
.toArray(new Font
[linuxFonts
.size()]);
112 List
<Font
> defaultFonts
= getDefaultFonts();
113 return defaultFonts
.toArray(new Font
[defaultFonts
.size()]);
118 * This is the default font settings that will be used if you don't specify anything
120 public static AWTTerminalFontConfiguration
getDefault() {
121 return newInstance(filterMonospaced(selectDefaultFont()));
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
128 * @param fonts Fonts to filter monospaced fonts from
129 * @return Array with the fonts from the input parameter that were monospaced
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
)) {
138 return result
.toArray(new Font
[result
.size()]);
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
146 * @param fontsInOrderOfPriority Fonts to use when drawing text, in order of priority
147 * @return Font configuration built from the font list
149 @SuppressWarnings("WeakerAccess")
150 public static AWTTerminalFontConfiguration
newInstance(Font
... fontsInOrderOfPriority
) {
151 return new AWTTerminalFontConfiguration(true, BoldMode
.EVERYTHING_BUT_SYMBOLS
, fontsInOrderOfPriority
);
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
;
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");
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));
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!");
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
;
184 throw new IllegalStateException("Unable to shrink font " + (i
+1) + " to fit the size of highest priority font " + fontPriority
.get(0));
186 font
= font
.deriveFont(newSize
);
187 fontPriority
.set(i
, font
);
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
);
202 private Font
getFontForCharacter(char c
) {
203 for(Font font
: fontPriority
) {
204 if(font
.canDisplay(c
)) {
208 //No available font here, what to do...?
209 return fontPriority
.get(0);
216 int getFontHeight() {
220 boolean isAntiAliased() {
221 return useAntiAliasing
;
224 private static boolean isFontMonospaced(Font font
) {
225 if(MONOSPACE_CHECK_OVERRIDE
.contains(font
.getName())) {
228 FontRenderContext frc
= new FontRenderContext(
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();
237 private int getFontWidth(Font font
) {
238 return (int)font
.getStringBounds("W", getFontRenderContext()).getWidth();
241 private int getFontHeight(Font font
) {
242 return (int)font
.getStringBounds("W", getFontRenderContext()).getHeight();
245 private FontRenderContext
getFontRenderContext() {
246 return new FontRenderContext(
249 RenderingHints
.VALUE_TEXT_ANTIALIAS_ON
: RenderingHints
.VALUE_TEXT_ANTIALIAS_OFF
,
250 RenderingHints
.VALUE_FRACTIONALMETRICS_DEFAULT
);
254 private static final Set
<Character
> SYMBOLS_CACHE
= new HashSet
<Character
>();
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) {
261 SYMBOLS_CACHE
.add(field
.getChar(null));
263 catch(IllegalArgumentException ignore
) {
264 //Should never happen!
266 catch(IllegalAccessException ignore
) {
267 //Should never happen!
273 private boolean isNotASymbol(char character
) {
274 return !SYMBOLS_CACHE
.contains(character
);