Skip to content

Commit

Permalink
Merge pull request #669 from danfickle/fix_641_fallback_font
Browse files Browse the repository at this point in the history
Fixes #641 Support fallback fonts
  • Loading branch information
danfickle authored Mar 19, 2021
2 parents 138b5b9 + 425c9b4 commit bde3106
Show file tree
Hide file tree
Showing 14 changed files with 957 additions and 389 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,4 @@

public interface FontResolver {
public FSFont resolveFont(SharedContext renderingContext, FontSpecification spec);

@Deprecated
public void flushCache();
}
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,35 @@ public TFinalClass useFont(FSSupplier<InputStream> supplier, String fontFamily,
return (TFinalClass) this;
}

/**
* <p>Add a font programmatically. If the font is NOT subset, it will be downloaded
* when the renderer is run, otherwise, assuming a font-metrics cache has been configured,
* the font will only be downloaded if required. Therefore, the user could add many fonts,
* confident that only those that are needed will be downloaded and processed.</p>
*
* <p>The InputStream returned by the supplier will be closed by the caller. Fonts
* should generally be subset (Java2D renderer ignores this argument),
* except when used in form controls. FSSupplier is a lambda compatible interface.</p>
*
* <p>Fonts can also be added using a font-face at-rule in the CSS (not
* recommended for Java2D usage).</p>
*
* <p><strong>IMPORTANT:</strong> This method is not recommended for use with Java2D.
* To add fonts for use by Java2D, SVG, etc see:
* {@link #useFont(File, String, Integer, FontStyle, boolean, Set)}</p>
*
* <p>For gotchas related to font handling please see:
* <a href="https://github.com/danfickle/openhtmltopdf/wiki/Fonts">Wiki: Fonts</a></p>
*
* @return this for method chaining
*/
public TFinalClass useFont(
FSSupplier<InputStream> supplier, String fontFamily, Integer fontWeight,
FontStyle fontStyle, boolean subset, Set<FSFontUseCase> useFontFlags) {
state._fonts.add(new AddedFont(supplier, null, fontWeight, fontFamily, subset, fontStyle, useFontFlags));
return (TFinalClass) this;
}

/**
* Simpler overload for
* {@link #useFont(FSSupplier, String, Integer, FontStyle, boolean)}
Expand Down Expand Up @@ -616,6 +645,17 @@ public enum FSFontUseCase {
/** Main document (PDF or Java2D) */
DOCUMENT,
SVG,
MATHML
MATHML,
/**
* Use as a fallback font after all supplied fonts have been tried but before
* the built-in fonts have been attempted.
*/
FALLBACK_PRE,
/**
* Use as a fallback fonts after all supplied fonts and the built-in fonts have been
* tried. The same font should not be registered with both <code>FALLBACK_PRE</code>
* and <code>FALLBACK_FINAL</code>.
*/
FALLBACK_FINAL;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,55 @@
import com.openhtmltopdf.css.constants.IdentValue;

public class FontFamily<T extends MinimalFontDescription> {
private List<T> _fontDescriptions;
private final List<T> _fontDescriptions = new ArrayList<>(4);
private final String _family;

public FontFamily() {
public FontFamily(String family) {
this._family = family;
}

public String getFamily() {
return _family;
}

public List<T> getFontDescriptions() {
return _fontDescriptions;
}

public void addFontDescription(T descr) {
if (_fontDescriptions == null) {
_fontDescriptions = new ArrayList<>();
}
_fontDescriptions.add(descr);
Collections.sort(_fontDescriptions,
new Comparator<T>() {
public int compare(T o1, T o2) {
return o1.getWeight() - o2.getWeight();
}
});
}

public void setName(String fontFamilyName) {
Collections.sort(_fontDescriptions, Comparator.comparing(T::getWeight));
}

public T match(int desiredWeight, IdentValue style) {
if (_fontDescriptions == null) {
throw new RuntimeException("fontDescriptions is null");
if (_fontDescriptions.isEmpty()) {
return null;
} else if (_fontDescriptions.size() == 1) {
return _fontDescriptions.get(0);
}

List<T> candidates = new ArrayList<>();
List<T> candidates = new ArrayList<>(_fontDescriptions.size());

for (T description : _fontDescriptions) {
if (description.getStyle() == style) {
candidates.add(description);
}
// First try only matching style.
getStyleMatches(style, candidates);

// Then try changing italic to oblique.
if (candidates.isEmpty() && style == IdentValue.ITALIC) {
getStyleMatches(IdentValue.OBLIQUE, candidates);
}

if (candidates.size() == 0) {
if (style == IdentValue.ITALIC) {
return match(desiredWeight, IdentValue.OBLIQUE);
} else if (style == IdentValue.OBLIQUE) {
return match(desiredWeight, IdentValue.NORMAL);
} else {
candidates.addAll(_fontDescriptions);
}
// Then try changing oblique to normal.
if (candidates.isEmpty() && style == IdentValue.OBLIQUE) {
getStyleMatches(IdentValue.NORMAL, candidates);
}

// Still nothing!
if (candidates.isEmpty()) {
candidates.addAll(_fontDescriptions);
}

if (candidates.size() == 1) {
return candidates.get(0);
}

T result = findByWeight(candidates, desiredWeight, SM_EXACT);
Expand All @@ -69,6 +72,14 @@ public T match(int desiredWeight, IdentValue style) {
}
}

private void getStyleMatches(IdentValue style, List<T> candidates) {
for (T description : _fontDescriptions) {
if (description.getStyle() == style) {
candidates.add(description);
}
}
}

private static final int SM_EXACT = 1;
private static final int SM_LIGHTER_OR_DARKER = 2;
private static final int SM_DARKER_OR_LIGHTER = 3;
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<html>
<head>
<style>
@page {
size: 200px 200px;
margin: 10px;
}
</style>
</head>
<body style="font-family: 'no-exist'; font-weight: 400;">

<div style="font-family: 'SourceSans'; font-weight: 400;">
Text should look normal!
</div>

<!-- Using fallback font -->
<div style="font-family: 'no-exist'; font-weight: 400;">
Text should look bold!
</div>

</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.EnumSet;
import java.util.Map;

import static org.junit.Assert.assertTrue;
Expand All @@ -22,6 +24,7 @@
import com.openhtmltopdf.extend.FSObjectDrawer;
import com.openhtmltopdf.extend.FSObjectDrawerFactory;
import com.openhtmltopdf.extend.OutputDevice;
import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.FSFontUseCase;
import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.FontStyle;
import com.openhtmltopdf.render.RenderingContext;
import com.openhtmltopdf.visualtest.TestSupport;
Expand Down Expand Up @@ -682,4 +685,38 @@ public void testIssue472AddSemiTransparentWatermark() throws IOException {
builder.useObjectDrawerFactory(new WatermarkDrawerFactory());
}));
}

/**
* Tests the ability to use fallback fonts.
*/
@Test
public void testIssue641FontFallback() throws IOException {
assertTrue(vtester.runTest("issue-641-font-fallback", builder -> {
builder.useFont(
new File("target/test/visual-tests/SourceSansPro-Regular.ttf"),
"SourceSans", 400, FontStyle.NORMAL, true,
EnumSet.of(FSFontUseCase.DOCUMENT));
builder.useFont(
new File("target/test/visual-tests/Karla-Bold.ttf"),
"Karla", 700, FontStyle.NORMAL, true,
EnumSet.of(FSFontUseCase.FALLBACK_PRE));
}));
}

/**
* Tests the ability to use fallback fonts via input streams.
*/
@Test
public void testIssue641FontFallbackInputStream() throws IOException {
assertTrue(vtester.runTest("issue-641-font-fallback", builder -> {
builder.useFont(
() -> TextVisualRegressionTest.class.getResourceAsStream("/visualtest/html/fonts/SourceSansPro-Regular.ttf"),
"SourceSans", 400, FontStyle.NORMAL, true,
EnumSet.of(FSFontUseCase.DOCUMENT));
builder.useFont(
() -> TextVisualRegressionTest.class.getResourceAsStream("/visualtest/html/fonts/Karla-Bold.ttf"),
"Karla", 700, FontStyle.NORMAL, true,
EnumSet.of(FSFontUseCase.FALLBACK_PRE));
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ private void init() {
}

@Deprecated
@Override
public void flushCache() {
instanceHash.clear();
availableFontsHash.clear();
Expand Down Expand Up @@ -266,7 +265,7 @@ public void addFontFile(File fontFile, String fontFamilyNameOverride, Integer fo
private FontFamily<FontDescription> getFontFamily(String fontFamilyName) {
FontFamily<FontDescription> fontFamily = _fontFamilies.get(fontFamilyName);
if (fontFamily == null) {
fontFamily = new FontFamily<>();
fontFamily = new FontFamily<>(fontFamilyName);
_fontFamilies.put(fontFamilyName, fontFamily);
}
return fontFamily;
Expand Down
Loading

0 comments on commit bde3106

Please sign in to comment.