Skip to content

Commit

Permalink
Merge PR #615: Fonts: lazy loading
Browse files Browse the repository at this point in the history
  • Loading branch information
DevCharly committed Nov 26, 2022
2 parents 6afc747 + adf7753 commit 95b4366
Show file tree
Hide file tree
Showing 25 changed files with 535 additions and 124 deletions.
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

0 comments on commit 95b4366

Please sign in to comment.