support defining fonts in FlatLaf properties files (issue #384)

This commit is contained in:
Karl Tauber
2021-10-08 20:23:54 +02:00
parent 5ecf19ef4f
commit b9ec382589
6 changed files with 258 additions and 21 deletions

View File

@@ -33,6 +33,7 @@ FlatLaf Change Log
- Added more color functions to class `ColorFunctions` for easy use in
applications: `lighten()`, `darken()`, `saturate()`, `desaturate()`, `spin()`,
`tint()`, `shade()` and `luma()`.
- Support defining fonts in FlatLaf properties files. (issue #384)
#### Fixed bugs

View File

@@ -569,7 +569,7 @@ public abstract class FlatLaf
// use active value for all fonts to allow changing fonts in all components
// (similar as in Nimbus L&F) with:
// UIManager.put( "defaultFont", myFont );
Object activeFont = new ActiveFont( 1 );
Object activeFont = new ActiveFont( null, -1, 0, 0, 0, 0 );
// override fonts
for( Object key : defaults.keySet() ) {
@@ -577,9 +577,6 @@ public abstract class FlatLaf
defaults.put( key, activeFont );
}
// use smaller font for progress bar
defaults.put( "ProgressBar.font", new ActiveFont( 0.85f ) );
// set default font
defaults.put( "defaultFont", uiFont );
}
@@ -594,7 +591,7 @@ public abstract class FlatLaf
/** @since 1.1 */
public static ActiveValue createActiveFontValue( float scaleFactor ) {
return new ActiveFont( scaleFactor );
return new ActiveFont( null, -1, 0, 0, 0, scaleFactor );
}
/**
@@ -1162,17 +1159,38 @@ public abstract class FlatLaf
//---- class ActiveFont ---------------------------------------------------
private static class ActiveFont
static class ActiveFont
implements ActiveValue
{
private final float scaleFactor;
private final List<String> families;
private final int style;
private final int styleChange;
private final int absoluteSize;
private final int relativeSize;
private final float scaleSize;
// cache (scaled) font
// cache (scaled/derived) font
private Font font;
private Font lastDefaultFont;
ActiveFont( float scaleFactor ) {
this.scaleFactor = scaleFactor;
/**
* @param families list of font families, or {@code null}
* @param style new style of font, or {@code -1}
* @param styleChange derive style of base font; or {@code 0}
* (the lower 16 bits are added; the upper 16 bits are removed)
* @param absoluteSize new size of font, or {@code 0}
* @param relativeSize added to size of base font, or {@code 0}
* @param scaleSize multiply size of base font, or {@code 0}
*/
ActiveFont( List<String> families, int style, int styleChange,
int absoluteSize, int relativeSize, float scaleSize )
{
this.families = families;
this.style = style;
this.styleChange = styleChange;
this.absoluteSize = absoluteSize;
this.relativeSize = relativeSize;
this.scaleSize = scaleSize;
}
@Override
@@ -1186,20 +1204,57 @@ public abstract class FlatLaf
if( lastDefaultFont != defaultFont ) {
lastDefaultFont = defaultFont;
if( scaleFactor != 1 ) {
// scale font
int newFontSize = Math.round( defaultFont.getSize() * scaleFactor );
font = new FontUIResource( defaultFont.deriveFont( (float) newFontSize ) );
} else {
// make sure that font is a UIResource for LaF switching
font = (defaultFont instanceof UIResource)
? defaultFont
: new FontUIResource( defaultFont );
}
font = derive( defaultFont );
// make sure that font is a UIResource for LaF switching
if( !(font instanceof UIResource) )
font = new FontUIResource( font );
}
return font;
}
private Font derive( Font baseFont ) {
int baseStyle = baseFont.getStyle();
int baseSize = baseFont.getSize();
// new style
int newStyle = (style != -1)
? style
: (styleChange != 0)
? baseStyle & ~((styleChange >> 16) & 0xffff) | (styleChange & 0xffff)
: baseStyle;
// new size
int newSize = (absoluteSize > 0)
? UIScale.scale( absoluteSize )
: (relativeSize != 0)
? (baseSize + UIScale.scale( relativeSize ))
: (scaleSize > 0)
? Math.round( baseSize * scaleSize )
: baseSize;
if( newSize <= 0 )
newSize = 1;
// create font for family
if( families != null && !families.isEmpty() ) {
for( String family : families ) {
Font font = createCompositeFont( family, newStyle, newSize );
if( !isFallbackFont( font ) || family.equalsIgnoreCase( Font.DIALOG ) )
return font;
}
}
// derive font
if( newStyle != baseStyle || newSize != baseSize )
return baseFont.deriveFont( newStyle, newSize );
else
return baseFont;
}
private boolean isFallbackFont( Font font ) {
return Font.DIALOG.equalsIgnoreCase( font.getFamily() );
}
}
//---- class ImageIconUIResource ------------------------------------------

View File

@@ -18,11 +18,14 @@ package com.formdev.flatlaf;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -309,7 +312,7 @@ class UIDefaultsLoader
throw new IllegalArgumentException( "property value type '" + newValue.getClass().getName() + "' not supported in references" );
}
enum ValueType { UNKNOWN, STRING, BOOLEAN, CHARACTER, INTEGER, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR,
enum ValueType { UNKNOWN, STRING, BOOLEAN, CHARACTER, INTEGER, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR, FONT,
SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER, NULL, LAZY }
private static ValueType[] tempResultValueType = new ValueType[1];
@@ -371,6 +374,7 @@ class UIDefaultsLoader
javaValueTypes.put( Insets.class, ValueType.INSETS );
javaValueTypes.put( Dimension.class, ValueType.DIMENSION );
javaValueTypes.put( Color.class, ValueType.COLOR );
javaValueTypes.put( Font.class, ValueType.FONT );
}
// map java value type to parser value type
@@ -428,6 +432,8 @@ class UIDefaultsLoader
(key.endsWith( ".background" ) || key.endsWith( "Background" ) || key.equals( "background" ) ||
key.endsWith( ".foreground" ) || key.endsWith( "Foreground" ) || key.equals( "foreground" ))) )
valueType = ValueType.COLOR;
else if( key.endsWith( ".font" ) || key.endsWith( "Font" ) || key.equals( "font" ) )
valueType = ValueType.FONT;
else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) || key.equals( "border" ) )
valueType = ValueType.BORDER;
else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) || key.equals( "icon" ) )
@@ -461,6 +467,7 @@ class UIDefaultsLoader
case INSETS: return parseInsets( value );
case DIMENSION: return parseDimension( value );
case COLOR: return parseColorOrFunction( value, resolver, true );
case FONT: return parseFont( value );
case SCALEDINTEGER: return parseScaledInteger( value );
case SCALEDFLOAT: return parseScaledFloat( value );
case SCALEDINSETS: return parseScaledInsets( value );
@@ -984,6 +991,94 @@ class UIDefaultsLoader
return new ColorUIResource( newColor );
}
/**
* Syntax: [normal] [bold|+bold|-bold] [italic|+italic|-italic] [<size>|+<incr>|-<decr>|<percent>%] [family[, family]]
*/
private static Object parseFont( String value ) {
int style = -1;
int styleChange = 0;
int absoluteSize = 0;
int relativeSize = 0;
float scaleSize = 0;
List<String> families = null;
// use StreamTokenizer to split string because it supports quoted strings
StreamTokenizer st = new StreamTokenizer( new StringReader( value ) );
st.resetSyntax();
st.wordChars( ' ' + 1, 255 );
st.whitespaceChars( 0, ' ' );
st.whitespaceChars( ',', ',' ); // ignore ','
st.quoteChar( '"' );
st.quoteChar( '\'' );
try {
while( st.nextToken() != StreamTokenizer.TT_EOF ) {
String param = st.sval;
switch( param ) {
// font style
case "normal":
style = 0;
break;
case "bold":
if( style == -1 )
style = 0;
style |= Font.BOLD;
break;
case "italic":
if( style == -1 )
style = 0;
style |= Font.ITALIC;
break;
case "+bold": styleChange |= Font.BOLD; break;
case "-bold": styleChange |= Font.BOLD << 16; break;
case "+italic": styleChange |= Font.ITALIC; break;
case "-italic": styleChange |= Font.ITALIC << 16; break;
default:
char firstChar = param.charAt( 0 );
if( Character.isDigit( firstChar ) || firstChar == '+' || firstChar == '-' ) {
// font size
if( absoluteSize != 0 || relativeSize != 0 || scaleSize != 0 )
throw new IllegalArgumentException( "size specified more than once in '" + value + "'" );
if( firstChar == '+' || firstChar == '-' )
relativeSize = parseInteger( param, true );
else if( param.endsWith( "%" ) )
scaleSize = parseInteger( param.substring( 0, param.length() - 1 ), true ) / 100f;
else
absoluteSize = parseInteger( param, true );
} else {
// font family
if( families == null )
families = Collections.singletonList( param );
else {
if( families.size() == 1 )
families = new ArrayList<>( families );
families.add( param );
}
}
break;
}
}
} catch( IOException ex ) {
throw new IllegalArgumentException( ex );
}
if( style != -1 && styleChange != 0 )
throw new IllegalArgumentException( "can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic') in '" + value + "'" );
if( styleChange != 0 ) {
if( (styleChange & Font.BOLD) != 0 && (styleChange & (Font.BOLD << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+bold' and '-bold' in '" + value + "'" );
if( (styleChange & Font.ITALIC) != 0 && (styleChange & (Font.ITALIC << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+italic' and '-italic' in '" + value + "'" );
}
return new FlatLaf.ActiveFont( families, style, styleChange, absoluteSize, relativeSize, scaleSize );
}
private static int parsePercentage( String value ) {
if( !value.endsWith( "%" ) )
throw new NumberFormatException( "invalid percentage '" + value + "'" );

View File

@@ -449,6 +449,7 @@ ProgressBar.horizontalSize = 146,4
ProgressBar.verticalSize = 4,146
ProgressBar.cycleTime = 4000
ProgressBar.repaintInterval = 15
ProgressBar.font = -2
#---- RadioButton ----

View File

@@ -19,7 +19,12 @@ package com.formdev.flatlaf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import javax.swing.UIManager;
import javax.swing.UIDefaults.ActiveValue;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
/**
@@ -27,6 +32,16 @@ import org.junit.jupiter.api.Test;
*/
public class TestUIDefaultsLoader
{
@BeforeAll
static void setup() {
System.setProperty( FlatSystemProperties.UI_SCALE_ENABLED, "false" );
}
@AfterAll
static void cleanup() {
System.clearProperty( FlatSystemProperties.UI_SCALE_ENABLED );
}
@Test
void parseValue() {
assertEquals( null, UIDefaultsLoader.parseValue( "dummy", "null", null ) );
@@ -71,4 +86,56 @@ public class TestUIDefaultsLoader
assertEquals( new Dimension( 2,2 ), UIDefaultsLoader.parseValue( "dummy", "2,2", Dimension.class ) );
assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummy", "#f00", Color.class ) );
}
@Test
void parseFonts() {
// style
UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.PLAIN, 10 ) );
assertFontEquals( Font.DIALOG, Font.PLAIN, 10, "" );
assertFontEquals( Font.DIALOG, Font.PLAIN, 10, "normal" );
assertFontEquals( Font.DIALOG, Font.BOLD, 10, "bold" );
assertFontEquals( Font.DIALOG, Font.ITALIC, 10, "italic" );
assertFontEquals( Font.DIALOG, Font.BOLD|Font.ITALIC, 10, "bold italic" );
// derived style
assertFontEquals( Font.DIALOG, Font.BOLD, 10, "+bold" );
assertFontEquals( Font.DIALOG, Font.ITALIC, 10, "+italic" );
assertFontEquals( Font.DIALOG, Font.BOLD|Font.ITALIC, 10, "+bold +italic" );
UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.BOLD|Font.ITALIC, 10 ) );
assertFontEquals( Font.DIALOG, Font.ITALIC, 10, "-bold" );
assertFontEquals( Font.DIALOG, Font.BOLD, 10, "-italic" );
assertFontEquals( Font.DIALOG, Font.PLAIN, 10, "-bold -italic" );
UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.BOLD, 10 ) );
assertFontEquals( Font.DIALOG, Font.ITALIC, 10, "-bold +italic" );
// size
UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.PLAIN, 10 ) );
assertFontEquals( Font.DIALOG, Font.PLAIN, 12, "12" );
assertFontEquals( Font.DIALOG, Font.PLAIN, 13, "+3" );
assertFontEquals( Font.DIALOG, Font.PLAIN, 6, "-4" );
assertFontEquals( Font.DIALOG, Font.PLAIN, 15, "150%" );
// family
assertFontEquals( Font.MONOSPACED, Font.PLAIN, 10, "Monospaced" );
assertFontEquals( Font.MONOSPACED, Font.PLAIN, 10, "Monospaced, Dialog" );
assertFontEquals( Font.DIALOG, Font.PLAIN, 10, "Dialog, Monospaced" );
// unknown family
assertFontEquals( Font.MONOSPACED, Font.PLAIN, 12, "normal 12 UnknownFamily, Monospaced" );
assertFontEquals( Font.DIALOG, Font.PLAIN, 12, "normal 12 UnknownFamily, Dialog" );
assertFontEquals( Font.DIALOG, Font.PLAIN, 12, "normal 12 UnknownFamily, 'Another unknown family'" );
// all
assertFontEquals( Font.MONOSPACED, Font.BOLD, 13, "bold 13 Monospaced" );
assertFontEquals( Font.DIALOG, Font.ITALIC, 14, "italic 14 Dialog" );
assertFontEquals( Font.DIALOG, Font.BOLD|Font.ITALIC, 15, "bold italic 15 Dialog" );
UIManager.put( "defaultFont", null );
}
private void assertFontEquals( String name, int style, int size, String actualStyle ) {
assertEquals(
new Font( name, style, size ),
((ActiveValue)UIDefaultsLoader.parseValue( "dummyFont", actualStyle, null )).createValue( null ) );
}
}

View File

@@ -243,6 +243,7 @@ public class TestFlatStyling
ui.applyStyle( b, "background: #fff" );
ui.applyStyle( b, "foreground: #fff" );
ui.applyStyle( b, "border: 2,2,2,2,#f00" );
ui.applyStyle( b, "font: italic 12 monospaced" );
// AbstractButton properties
ui.applyStyle( b, "margin: 2,2,2,2" );
@@ -295,6 +296,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
}
@Test
@@ -311,6 +313,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
// JTextComponent properties
ui.applyStyle( "caretColor: #fff" );
@@ -363,6 +366,7 @@ public class TestFlatStyling
ui.applyStyle( c, "background: #fff" );
ui.applyStyle( c, "foreground: #fff" );
ui.applyStyle( c, "border: 2,2,2,2,#f00" );
ui.applyStyle( c, "font: italic 12 monospaced" );
// JLabel properties
ui.applyStyle( c, "icon: com.formdev.flatlaf.icons.FlatTreeExpandedIcon" );
@@ -387,6 +391,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
// JList properties
ui.applyStyle( "visibleRowCount: 20" );
@@ -410,6 +415,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
}
@Test
@@ -467,6 +473,7 @@ public class TestFlatStyling
applyStyle.accept( "background: #fff" );
applyStyle.accept( "foreground: #fff" );
applyStyle.accept( "border: 2,2,2,2,#f00" );
applyStyle.accept( "font: italic 12 monospaced" );
// AbstractButton properties
applyStyle.accept( "margin: 2,2,2,2" );
@@ -557,6 +564,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
}
@Test
@@ -578,6 +586,7 @@ public class TestFlatStyling
ui.applyStyle( b, "background: #fff" );
ui.applyStyle( b, "foreground: #fff" );
ui.applyStyle( b, "border: 2,2,2,2,#f00" );
ui.applyStyle( b, "font: italic 12 monospaced" );
// AbstractButton properties
ui.applyStyle( b, "margin: 2,2,2,2" );
@@ -714,6 +723,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
ui.applyStyle( "minimum: 0" );
ui.applyStyle( "maximum: 50" );
ui.applyStyle( "value: 20" );
@@ -760,6 +770,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
}
@Test
@@ -856,6 +867,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
}
@Test
@@ -878,6 +890,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
// JTable properties
ui.applyStyle( "fillsViewportHeight: true" );
@@ -909,6 +922,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
}
@Test
@@ -925,6 +939,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
// JTextComponent properties
ui.applyStyle( "caretColor: #fff" );
@@ -958,6 +973,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
// JTextComponent properties
ui.applyStyle( "caretColor: #fff" );
@@ -980,6 +996,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
// JTextComponent properties
ui.applyStyle( "caretColor: #fff" );
@@ -1063,6 +1080,7 @@ public class TestFlatStyling
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
ui.applyStyle( "border: 2,2,2,2,#f00" );
ui.applyStyle( "font: italic 12 monospaced" );
// JTree properties
ui.applyStyle( "rootVisible: true" );