Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fonts #545

Merged
merged 6 commits into from Nov 18, 2022
Merged

Fonts #545

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Expand Up @@ -20,7 +20,9 @@
*.gif binary
*.jar binary
*.lib binary
*.otf binary
*.png binary
*.sketch binary
*.so binary
*.ttf binary
*.zip binary
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Expand Up @@ -54,6 +54,7 @@ jobs:
name: FlatLaf-build-artifacts
path: |
flatlaf-*/build/libs
flatlaf-*/flatlaf-*/build/libs
!**/*-javadoc.jar
!**/*-sources.jar

Expand Down
47 changes: 47 additions & 0 deletions .github/workflows/fonts.yml
@@ -0,0 +1,47 @@
# https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle

name: Fonts

on:
push:
branches:
- '*'
tags:
- 'fonts/*-[0-9]*'
paths:
- 'flatlaf-fonts/**'
- '.github/workflows/fonts.yml'

jobs:
Release:
runs-on: ubuntu-latest
if: |
github.event_name == 'push' &&
github.repository == 'JFormDesigner/FlatLaf'

strategy:
matrix:
font:
- inter
- jetbrains-mono

steps:
- uses: actions/checkout@v3
if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) )

- name: Setup Java 11
uses: actions/setup-java@v3
with:
java-version: 11
distribution: adopt # pre-installed on ubuntu-latest
cache: gradle
if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) )

- name: Release a new stable version to Maven Central
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build :flatlaf-fonts-${{ matrix.font }}:publish -Drelease=true
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) )
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -83,6 +83,8 @@ Addons
- [SwingX](flatlaf-swingx) - support for SwingX components
- [JIDE Common Layer](flatlaf-jide-oss) - support for JIDE Common Layer
components
- [Fonts](flatlaf-fonts) - some font families bundled in easy-to-use and
redistributable JARs


Getting started
Expand Down
124 changes: 122 additions & 2 deletions flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java
Expand Up @@ -114,6 +114,11 @@ public abstract class FlatLaf
private Consumer<UIDefaults> postInitialization;
private List<Function<Object, Object>> uiDefaultsGetters;

private static String preferredFontFamily;
private static String preferredLightFontFamily;
private static String preferredSemiboldFontFamily;
private static String preferredMonospacedFontFamily;

/**
* Sets the application look and feel to the given LaF
* using {@link UIManager#setLookAndFeel(javax.swing.LookAndFeel)}.
Expand Down Expand Up @@ -631,6 +636,13 @@ private void initDefaultFont( UIDefaults defaults ) {
if( uiFont == null )
uiFont = createCompositeFont( Font.SANS_SERIF, Font.PLAIN, 12 );

// use preferred font family (if specified)
if( preferredFontFamily != null ) {
FontUIResource preferredFont = createCompositeFont( preferredFontFamily, uiFont.getStyle(), uiFont.getSize() );
if( !ActiveFont.isFallbackFont( preferredFont ) || ActiveFont.isDialogFamily( preferredFontFamily ) )
uiFont = preferredFont;
}

// get/remove "defaultFont" from defaults if set in properties files
// (use remove() to avoid that ActiveFont.createValue() gets invoked)
Object defaultFont = defaults.remove( "defaultFont" );
Expand Down Expand Up @@ -1323,6 +1335,90 @@ private static StyleableUI getStyleableUI( JComponent c ) {
private static boolean getUIMethodInitialized;
private static MethodHandle getUIMethod;

/**
* Returns the preferred font family to be used for (nearly) all fonts; or {@code null}.
*
* @since 3
*/
public static String getPreferredFontFamily() {
return preferredFontFamily;
}

/**
* Sets the preferred font family to be used for (nearly) all fonts.
* <p>
* <strong>Note</strong>: This must be invoked <strong>before</strong> setting
* the application look and feel.
*
* @since 3
*/
public static void setPreferredFontFamily( String preferredFontFamily ) {
FlatLaf.preferredFontFamily = preferredFontFamily;
}

/**
* Returns the preferred font family to be used for "light" fonts; or {@code null}.
*
* @since 3
*/
public static String getPreferredLightFontFamily() {
return preferredLightFontFamily;
}

/**
* Sets the preferred font family to be used for "light" fonts.
* <p>
* <strong>Note</strong>: This must be invoked <strong>before</strong> setting
* the application look and feel.
*
* @since 3
*/
public static void setPreferredLightFontFamily( String preferredLightFontFamily ) {
FlatLaf.preferredLightFontFamily = preferredLightFontFamily;
}

/**
* Returns the preferred font family to be used for "semibold" fonts; or {@code null}.
*
* @since 3
*/
public static String getPreferredSemiboldFontFamily() {
return preferredSemiboldFontFamily;
}

/**
* Sets the preferred font family to be used for "semibold" fonts.
* <p>
* <strong>Note</strong>: This must be invoked <strong>before</strong> setting
* the application look and feel.
*
* @since 3
*/
public static void setPreferredSemiboldFontFamily( String preferredSemiboldFontFamily ) {
FlatLaf.preferredSemiboldFontFamily = preferredSemiboldFontFamily;
}

/**
* Returns the preferred font family to be used for monospaced fonts; or {@code null}.
*
* @since 3
*/
public static String getPreferredMonospacedFontFamily() {
return preferredMonospacedFontFamily;
}

/**
* Sets the preferred font family to be used for monospaced fonts.
* <p>
* <strong>Note</strong>: This must be invoked <strong>before</strong> setting
* the application look and feel.
*
* @since 3
*/
public static void setPreferredMonospacedFontFamily( String preferredMonospacedFontFamily ) {
FlatLaf.preferredMonospacedFontFamily = preferredMonospacedFontFamily;
}

//---- class FlatUIDefaults -----------------------------------------------

private class FlatUIDefaults
Expand Down Expand Up @@ -1457,9 +1553,16 @@ FontUIResource derive( Font baseFont, IntUnaryOperator scale ) {

// create font for family
if( families != null && !families.isEmpty() ) {
String preferredFamily = preferredFamily( families );
if( preferredFamily != null ) {
Font font = createCompositeFont( preferredFamily, newStyle, newSize );
if( !isFallbackFont( font ) || isDialogFamily( preferredFamily ) )
return toUIResource( font );
}

for( String family : families ) {
Font font = createCompositeFont( family, newStyle, newSize );
if( !isFallbackFont( font ) || family.equalsIgnoreCase( Font.DIALOG ) )
if( !isFallbackFont( font ) || isDialogFamily( family ) )
return toUIResource( font );
}
}
Expand Down Expand Up @@ -1488,9 +1591,26 @@ private FontUIResource toUIResource( Font font ) {
: new FontUIResource( font );
}

private boolean isFallbackFont( Font font ) {
private static boolean isFallbackFont( Font font ) {
return Font.DIALOG.equalsIgnoreCase( font.getFamily() );
}

private static boolean isDialogFamily( String family ) {
return family.equalsIgnoreCase( Font.DIALOG );
}

private static String preferredFamily( List<String> families ) {
for( String family : families ) {
family = family.toLowerCase( Locale.ENGLISH );
if( family.endsWith( " light" ) || family.endsWith( "-thin" ) )
return preferredLightFontFamily;
if( family.endsWith( " semibold" ) || family.endsWith( "-medium" ) )
return preferredSemiboldFontFamily;
if( family.equals( "monospaced" ) )
return preferredMonospacedFontFamily;
}
return null;
}
}

//---- class ImageIconUIResource ------------------------------------------
Expand Down
123 changes: 94 additions & 29 deletions flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java
Expand Up @@ -16,6 +16,7 @@

package com.formdev.flatlaf.util;

import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.GlyphVector;
Expand Down Expand Up @@ -141,37 +142,101 @@ public static float computeTextYCorrection( Graphics2D g ) {
if( !useTextYCorrection() || !SystemInfo.isWindows )
return 0;

if( !SystemInfo.isJava_9_orLater )
return UIScale.getUserScaleFactor() > 1 ? -UIScale.scale( 0.625f ) : 0;
if( !SystemInfo.isJava_9_orLater ) {
// Java 8
float scaleFactor = getUserScaleFactor();
if( scaleFactor > 1 ) {
switch( g.getFont().getFamily() ) {
case "Segoe UI":
case "Segoe UI Light":
case "Segoe UI Semibold":
return -((scaleFactor == 2.25f || scaleFactor == 4f ? 0.875f : 0.625f) * scaleFactor);

case "Noto Sans":
case "Open Sans":
return -(0.3f * scaleFactor);

case "Verdana":
return -((scaleFactor < 2 ? 0.4f : 0.3f) * scaleFactor);
}
}
} else {
// Java 9 and later

// Text is painted at slightly different Y positions depending on scale factor
// and Y position of component.
// The exact reason is not yet known (to me), but there are several factors:
// - fractional scale factors result in fractional component Y device coordinates
// - fractional text Y device coordinates are rounded for horizontal lines of characters
// - maybe different rounding methods for drawing primitives (e.g. rectangle) and text
// - Java adds 0.5 to X/Y positions before drawing string in BufferedTextPipe.enqueueGlyphList()

// this is not the optimal solution, but works very good in most cases
// (tested with class FlatPaintingStringTest on Windows 11)

switch( g.getFont().getFamily() ) {
case "Segoe UI":
case "Segoe UI Light":
case "Segoe UI Semibold":
case "Verdana":
case Font.DIALOG:
case Font.SANS_SERIF:
return correctionForScaleY( g, CORRECTION_SEGOE_UI );

case "Tahoma":
return correctionForScaleY( g, CORRECTION_TAHOMA );

case "Inter":
case "Inter Light":
case "Inter Semi Bold":
case "Roboto":
return correctionForScaleY( g, CORRECTION_INTER );

case "Noto Sans":
case "Open Sans":
return correctionForScaleY( g, CORRECTION_OPEN_SANS );
}
}

AffineTransform t = g.getTransform();
double scaleY = t.getScaleY();
if( scaleY < 1.25 )
return 0;
return 0;
}

private static final float[]
SCALE_FACTORS = { 1.25f, 1.5f, 1.75f, 2f, 2.25f, 2.5f, 3f, 3.5f, 4f },

CORRECTION_SEGOE_UI = { -0.5f, -0.5f, -0.625f, -0.75f, -0.75f, -0.75f, -0.75f, -0.75f, -0.875f },
CORRECTION_TAHOMA = { -0.25f, -0.25f, -0.25f, -0f, -0.125f, -0.125f, -0.125f, -0.125f, -0f },
CORRECTION_INTER = { -0.25f, -0.25f, -0.25f, -0f, -0.125f, -0.125f, -0f, -0.25f, -0f },
CORRECTION_OPEN_SANS = { -0.5f, -0.25f, -0.25f, -0f, -0.25f, -0.25f, -0f, -0.25f, -0.25f };

private static float correctionForScaleY( Graphics2D g, float[] correction ) {
if( correction.length != 9 )
throw new IllegalArgumentException();

double scaleY = g.getTransform().getScaleY();
return (scaleY < 1.25) ? 0 : correction[scaleFactor2index( (float) scaleY )];
}

private static int scaleFactor2index( float scaleFactor ) {
for( int i = 0; i < SCALE_FACTORS.length; i++ ) {
if( scaleFactor <= SCALE_FACTORS[i] )
return i;
}
return SCALE_FACTORS.length - 1;
}

private static Boolean useDebugScaleFactor;

private static boolean useDebugScaleFactor() {
if( useDebugScaleFactor == null )
useDebugScaleFactor = FlatSystemProperties.getBoolean( "FlatLaf.debug.HiDPIUtils.useDebugScaleFactor", false );
return useDebugScaleFactor;
}

// Text is painted at slightly different Y positions depending on scale factor
// and Y position of component.
// The exact reason is not yet known (to me), but there are several factors:
// - fractional scale factors result in fractional component Y device coordinates
// - fractional text Y device coordinates are rounded for horizontal lines of characters
// - maybe different rounding methods for drawing primitives (e.g. rectangle) and text
// - Java adds 0.5 to X/Y positions before drawing string in BufferedTextPipe.enqueueGlyphList()

// this is not the optimal solution, but works very good in most cases
// (tested with class FlatPaintingStringTest on Windows 10 with font "Segoe UI")
if( scaleY <= 1.25 )
return -0.875f;
if( scaleY <= 1.5 )
return -0.625f;
if( scaleY <= 1.75 )
return -0.875f;
if( scaleY <= 2.0 )
return -0.75f;
if( scaleY <= 2.25 )
return -0.875f;
if( scaleY <= 3.5 )
return -0.75f;
return -0.875f;
private static float getUserScaleFactor() {
return !useDebugScaleFactor()
? UIScale.getUserScaleFactor()
: Float.parseFloat( System.getProperty( "FlatLaf.debug.HiDPIUtils.debugScaleFactor", "1" ) );
}

/**
Expand Down
2 changes: 2 additions & 0 deletions flatlaf-demo/build.gradle.kts
Expand Up @@ -22,6 +22,7 @@ plugins {
dependencies {
implementation( project( ":flatlaf-core" ) )
implementation( project( ":flatlaf-extras" ) )
implementation( project( ":flatlaf-fonts-inter" ) )
implementation( project( ":flatlaf-intellij-themes" ) )
implementation( "com.miglayout:miglayout-swing:5.3" )
implementation( "com.jgoodies:jgoodies-forms:1.9.0" )
Expand All @@ -32,6 +33,7 @@ tasks {
jar {
dependsOn( ":flatlaf-core:jar" )
dependsOn( ":flatlaf-extras:jar" )
dependsOn( ":flatlaf-fonts-inter:jar" )
dependsOn( ":flatlaf-intellij-themes:jar" )
// dependsOn( ":flatlaf-natives-jna:jar" )

Expand Down