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: lazy loading #615

Merged
merged 2 commits into from Nov 26, 2022
Merged
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
4 changes: 4 additions & 0 deletions flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java
Expand Up @@ -78,6 +78,7 @@
import com.formdev.flatlaf.ui.FlatRootPaneUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.FontUtils;
import com.formdev.flatlaf.util.GrayFilter;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.MultiResolutionImageSupport;
Expand Down Expand Up @@ -663,6 +664,9 @@ private void initDefaultFont( UIDefaults defaults ) {
}

static FontUIResource createCompositeFont( String family, int style, int size ) {
// load lazy font family
FontUtils.loadFontFamily( family );

// using StyleContext.getFont() here because it uses
// sun.font.FontUtilities.getCompositeFontUIResource()
// and creates a composite font that is able to display all Unicode characters
Expand Down
153 changes: 153 additions & 0 deletions flatlaf-core/src/main/java/com/formdev/flatlaf/util/FontUtils.java
@@ -0,0 +1,153 @@
/*
* Copyright 2022 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.formdev.flatlaf.util;

import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.GraphicsEnvironment;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.swing.plaf.UIResource;
import javax.swing.text.StyleContext;

/**
* Utility methods for fonts.
*
* @author Karl Tauber
* @since 3
*/
public class FontUtils
{
private static Map<String, Runnable> loadersMap;

/**
* Gets a composite font for the given family, style and size.
* A composite font that is able to display all Unicode characters.
* The font family is loaded if necessary via {@link #loadFontFamily(String)}.
* <p>
* To get fonts derived from returned fonts, it is recommended to use one of the
* {@link Font#deriveFont} methods instead of invoking this method.
*/
public static Font getCompositeFont( String family, int style, int size ) {
loadFontFamily( family );

// using StyleContext.getFont() here because it uses
// sun.font.FontUtilities.getCompositeFontUIResource()
// and creates a composite font that is able to display all Unicode characters
Font font = StyleContext.getDefaultStyleContext().getFont( family, style, size );

// always return non-UIResource font to avoid side effects when using font
// because Swing uninstalls UIResource fonts when switching L&F
// (StyleContext.getFont() may return a UIResource)
if( font instanceof UIResource )
font = font.deriveFont( font.getStyle() );

return font;
}

/**
* Registers a font family for lazy loading via {@link #loadFontFamily(String)}.
* <p>
* The given runnable is invoked when the given font family should be loaded.
* The runnable should invoke {@link #installFont(URL)} to load and register font(s)
* for the family.
* A family may consist of up to four font files for the supported font styles:
* regular (plain), italic, bold and bold-italic.
*/
public static void registerFontFamilyLoader( String family, Runnable loader ) {
if( loadersMap == null )
loadersMap = new HashMap<>();
loadersMap.put( family, loader );
}

/**
* Loads a font family previously registered via {@link #registerFontFamilyLoader(String, Runnable)}.
* If the family is already loaded or no londer is registered for that family, nothing happens.
*/
public static void loadFontFamily( String family ) {
if( !hasLoaders() )
return;

Runnable loader = loadersMap.remove( family );
if( loader != null )
loader.run();

if( loadersMap.isEmpty() )
loadersMap = null;
}

/**
* Loads a font file from the given url and registers it in the graphics environment.
* Uses {@link Font#createFont(int, InputStream)} and {@link GraphicsEnvironment#registerFont(Font)}.
*/
public static boolean installFont( URL url ) {
try( InputStream in = url.openStream() ) {
Font font = Font.createFont( Font.TRUETYPE_FONT, in );
return GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont( font );
} catch( FontFormatException | IOException ex ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to install font " + url, ex );
return false;
}
}

/**
* Returns all font familiy names available in the graphics environment.
* This invokes {@link GraphicsEnvironment#getAvailableFontFamilyNames()} and
* appends families registered for lazy loading via {@link #registerFontFamilyLoader(String, Runnable)}
* to the result.
*/
public static String[] getAvailableFontFamilyNames() {
String[] availableFontFamilyNames = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
if( !hasLoaders() )
return availableFontFamilyNames;

// append families that are not yet loaded
ArrayList<String> result = new ArrayList<>( availableFontFamilyNames.length + loadersMap.size() );
for( String name : availableFontFamilyNames )
result.add( name );
for( String name : loadersMap.keySet() ) {
if( !result.contains( name ) )
result.add( name );
}

return result.toArray( new String[result.size()] );
}

/**
* Returns all fonts available in the graphics environment.
* This first loads all families registered for lazy loading via {@link #registerFontFamilyLoader(String, Runnable)}
* and then invokes {@link GraphicsEnvironment#getAllFonts()}.
*/
public static Font[] getAllFonts() {
if( hasLoaders() ) {
// load all registered families
String[] families = loadersMap.keySet().toArray( new String[loadersMap.size()] );
for( String family : families )
loadFontFamily( family );
}

return GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
}

private static boolean hasLoaders() {
return loadersMap != null && !loadersMap.isEmpty();
}
}
2 changes: 2 additions & 0 deletions flatlaf-demo/build.gradle.kts
Expand Up @@ -23,6 +23,7 @@ dependencies {
implementation( project( ":flatlaf-core" ) )
implementation( project( ":flatlaf-extras" ) )
implementation( project( ":flatlaf-fonts-inter" ) )
implementation( project( ":flatlaf-fonts-jetbrains-mono" ) )
implementation( project( ":flatlaf-fonts-roboto" ) )
implementation( project( ":flatlaf-intellij-themes" ) )
implementation( "com.miglayout:miglayout-swing:5.3" )
Expand All @@ -35,6 +36,7 @@ tasks {
dependsOn( ":flatlaf-core:jar" )
dependsOn( ":flatlaf-extras:jar" )
dependsOn( ":flatlaf-fonts-inter:jar" )
dependsOn( ":flatlaf-fonts-jetbrains-mono:jar" )
dependsOn( ":flatlaf-fonts-roboto:jar" )
dependsOn( ":flatlaf-intellij-themes:jar" )
// dependsOn( ":flatlaf-natives-jna:jar" )
Expand Down
32 changes: 5 additions & 27 deletions flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java
Expand Up @@ -27,7 +27,6 @@
import java.util.prefs.Preferences;
import javax.swing.*;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.StyleContext;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatDarculaLaf;
import com.formdev.flatlaf.FlatDarkLaf;
Expand All @@ -43,15 +42,13 @@
import com.formdev.flatlaf.extras.FlatUIDefaultsInspector;
import com.formdev.flatlaf.extras.components.FlatButton;
import com.formdev.flatlaf.extras.components.FlatButton.ButtonType;
import com.formdev.flatlaf.fonts.inter.FlatInterFont;
import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont;
import com.formdev.flatlaf.icons.FlatAbstractIcon;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
import com.formdev.flatlaf.extras.FlatSVGUtils;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.ui.JBRCustomDecorations;
import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.FontUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
import net.miginfocom.layout.ConstraintParser;
Expand All @@ -66,15 +63,12 @@ class DemoFrame
extends JFrame
{
private final String[] availableFontFamilyNames;
private boolean interFontInstalled;
private boolean robotoFontInstalled;
private int initialFontMenuItemCount = -1;

DemoFrame() {
int tabIndex = DemoPrefs.getState().getInt( FlatLafDemo.KEY_TAB, 0 );

availableFontFamilyNames = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getAvailableFontFamilyNames().clone();
availableFontFamilyNames = FontUtils.getAvailableFontFamilyNames().clone();
Arrays.sort( availableFontFamilyNames );

initComponents();
Expand Down Expand Up @@ -284,24 +278,10 @@ private void showHintsChanged() {
private void fontFamilyChanged( ActionEvent e ) {
String fontFamily = e.getActionCommand();

// install Inter font on demand
if( fontFamily.equals( FlatInterFont.FAMILY ) && !interFontInstalled ) {
FlatInterFont.install();
interFontInstalled = true;
}

// install Roboto font on demand
if( fontFamily.equals( FlatRobotoFont.FAMILY ) && !robotoFontInstalled ) {
FlatRobotoFont.install();
robotoFontInstalled = true;
}

FlatAnimatedLafChange.showSnapshot();

Font font = UIManager.getFont( "defaultFont" );
Font newFont = StyleContext.getDefaultStyleContext().getFont( fontFamily, font.getStyle(), font.getSize() );
// StyleContext.getFont() may return a UIResource, which would cause loosing user scale factor on Windows
newFont = FlatUIUtils.nonUIResource( newFont );
Font newFont = FontUtils.getCompositeFont( fontFamily, font.getStyle(), font.getSize() );
UIManager.put( "defaultFont", newFont );

FlatLaf.updateUI();
Expand Down Expand Up @@ -368,10 +348,8 @@ void updateFontMenuItems() {

ButtonGroup familiesGroup = new ButtonGroup();
for( String family : families ) {
if( Arrays.binarySearch( availableFontFamilyNames, family ) < 0 &&
!family.equals( FlatInterFont.FAMILY ) &&
!family.equals( FlatRobotoFont.FAMILY ) )
continue; // not available
if( Arrays.binarySearch( availableFontFamilyNames, family ) < 0 )
continue; // not available

JCheckBoxMenuItem item = new JCheckBoxMenuItem( family );
item.setSelected( family.equals( currentFamily ) );
Expand Down
Expand Up @@ -23,6 +23,9 @@
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.extras.FlatInspector;
import com.formdev.flatlaf.extras.FlatUIDefaultsInspector;
import com.formdev.flatlaf.fonts.inter.FlatInterFont;
import com.formdev.flatlaf.fonts.jetbrains_mono.FlatJetBrainsMonoFont;
import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont;
import com.formdev.flatlaf.util.SystemInfo;

/**
Expand Down Expand Up @@ -68,6 +71,24 @@ public static void main( String[] args ) {
SwingUtilities.invokeLater( () -> {
DemoPrefs.init( PREFS_ROOT_PATH );

// install fonts for lazy loading
FlatInterFont.installLazy();
FlatJetBrainsMonoFont.installLazy();
FlatRobotoFont.installLazy();

// use Inter font by default
// FlatLaf.setPreferredFontFamily( FlatInterFont.FAMILY );
// FlatLaf.setPreferredLightFontFamily( FlatInterFont.FAMILY_LIGHT );
// FlatLaf.setPreferredSemiboldFontFamily( FlatInterFont.FAMILY_SEMIBOLD );

// use Roboto font by default
// FlatLaf.setPreferredFontFamily( FlatRobotoFont.FAMILY );
// FlatLaf.setPreferredLightFontFamily( FlatRobotoFont.FAMILY_LIGHT );
// FlatLaf.setPreferredSemiboldFontFamily( FlatRobotoFont.FAMILY_SEMIBOLD );

// use JetBrains Mono font
// FlatLaf.setPreferredMonospacedFontFamily( FlatJetBrainsMonoFont.FAMILY );

// application specific UI defaults
FlatLaf.registerCustomDefaultsSource( "com.formdev.flatlaf.demo" );

Expand Down
40 changes: 36 additions & 4 deletions flatlaf-fonts/flatlaf-fonts-inter/README.md
Expand Up @@ -18,26 +18,37 @@ License:
How to install?
---------------

Invoke the `install()` method once (e.g. in your `main()` method; on AWT
thread):
Invoke following once (e.g. in your `main()` method; on AWT thread).

For lazy loading use:

~~~java
FlatInterFont.installLazy();
~~~

Or load immediately with:

~~~java
FlatInterFont.install();
// or
FlatInterFont.installBasic();
FlatInterFont.installLight();
FlatInterFont.installSemiBold();
~~~


How to use?
-----------

Use as default font:
Use as application font (invoke before setting up FlatLaf):

~~~java
FlatLaf.setPreferredFontFamily( FlatInterFont.FAMILY );
FlatLaf.setPreferredLightFontFamily( FlatInterFont.FAMILY_LIGHT );
FlatLaf.setPreferredSemiboldFontFamily( FlatInterFont.FAMILY_SEMIBOLD );
~~~

Create fonts:
Create single fonts:

~~~java
// basic styles
Expand All @@ -55,6 +66,27 @@ new Font( FlatInterFont.FAMILY_SEMIBOLD, Font.PLAIN, 12 );
new Font( FlatInterFont.FAMILY_SEMIBOLD, Font.ITALIC, 12 );
~~~

If using lazy loading, invoke one of following before creating the font:

~~~java
FontUtils.loadFontFamily( FlatInterFont.FAMILY );
FontUtils.loadFontFamily( FlatInterFont.FAMILY_LIGHT );
FontUtils.loadFontFamily( FlatInterFont.FAMILY_SEMIBOLD );
~~~

E.g.:

~~~java
FontUtils.loadFontFamily( FlatInterFont.FAMILY );
Font font = new Font( FlatInterFont.FAMILY, Font.PLAIN, 12 );
~~~

Or use following:

~~~java
Font font = FontUtils.getCompositeFont( FlatInterFont.FAMILY, Font.PLAIN, 12 );
~~~


Download
--------
Expand Down
6 changes: 6 additions & 0 deletions flatlaf-fonts/flatlaf-fonts-inter/build.gradle.kts
Expand Up @@ -31,11 +31,17 @@ plugins {
}

dependencies {
implementation( project( ":flatlaf-core" ) )

testImplementation( "org.junit.jupiter:junit-jupiter-api:5.7.2" )
testImplementation( "org.junit.jupiter:junit-jupiter-params" )
testRuntimeOnly( "org.junit.jupiter:junit-jupiter-engine" )
}

flatlafModuleInfo {
dependsOn( ":flatlaf-core:jar" )
}

java {
withSourcesJar()
withJavadocJar()
Expand Down