Skip to content

Commit bde3106

Browse files
authored
Merge pull request #669 from danfickle/fix_641_fallback_font
Fixes #641 Support fallback fonts
2 parents 138b5b9 + 425c9b4 commit bde3106

File tree

14 files changed

+957
-389
lines changed

14 files changed

+957
-389
lines changed

openhtmltopdf-core/src/main/java/com/openhtmltopdf/extend/FontResolver.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,4 @@
2626

2727
public interface FontResolver {
2828
public FSFont resolveFont(SharedContext renderingContext, FontSpecification spec);
29-
30-
@Deprecated
31-
public void flushCache();
3229
}

openhtmltopdf-core/src/main/java/com/openhtmltopdf/outputdevice/helper/BaseRendererBuilder.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,35 @@ public TFinalClass useFont(FSSupplier<InputStream> supplier, String fontFamily,
546546
return (TFinalClass) this;
547547
}
548548

549+
/**
550+
* <p>Add a font programmatically. If the font is NOT subset, it will be downloaded
551+
* when the renderer is run, otherwise, assuming a font-metrics cache has been configured,
552+
* the font will only be downloaded if required. Therefore, the user could add many fonts,
553+
* confident that only those that are needed will be downloaded and processed.</p>
554+
*
555+
* <p>The InputStream returned by the supplier will be closed by the caller. Fonts
556+
* should generally be subset (Java2D renderer ignores this argument),
557+
* except when used in form controls. FSSupplier is a lambda compatible interface.</p>
558+
*
559+
* <p>Fonts can also be added using a font-face at-rule in the CSS (not
560+
* recommended for Java2D usage).</p>
561+
*
562+
* <p><strong>IMPORTANT:</strong> This method is not recommended for use with Java2D.
563+
* To add fonts for use by Java2D, SVG, etc see:
564+
* {@link #useFont(File, String, Integer, FontStyle, boolean, Set)}</p>
565+
*
566+
* <p>For gotchas related to font handling please see:
567+
* <a href="https://github.com/danfickle/openhtmltopdf/wiki/Fonts">Wiki: Fonts</a></p>
568+
*
569+
* @return this for method chaining
570+
*/
571+
public TFinalClass useFont(
572+
FSSupplier<InputStream> supplier, String fontFamily, Integer fontWeight,
573+
FontStyle fontStyle, boolean subset, Set<FSFontUseCase> useFontFlags) {
574+
state._fonts.add(new AddedFont(supplier, null, fontWeight, fontFamily, subset, fontStyle, useFontFlags));
575+
return (TFinalClass) this;
576+
}
577+
549578
/**
550579
* Simpler overload for
551580
* {@link #useFont(FSSupplier, String, Integer, FontStyle, boolean)}
@@ -616,6 +645,17 @@ public enum FSFontUseCase {
616645
/** Main document (PDF or Java2D) */
617646
DOCUMENT,
618647
SVG,
619-
MATHML
648+
MATHML,
649+
/**
650+
* Use as a fallback font after all supplied fonts have been tried but before
651+
* the built-in fonts have been attempted.
652+
*/
653+
FALLBACK_PRE,
654+
/**
655+
* Use as a fallback fonts after all supplied fonts and the built-in fonts have been
656+
* tried. The same font should not be registered with both <code>FALLBACK_PRE</code>
657+
* and <code>FALLBACK_FINAL</code>.
658+
*/
659+
FALLBACK_FINAL;
620660
}
621661
}

openhtmltopdf-core/src/main/java/com/openhtmltopdf/outputdevice/helper/FontFamily.java

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,52 +8,55 @@
88
import com.openhtmltopdf.css.constants.IdentValue;
99

1010
public class FontFamily<T extends MinimalFontDescription> {
11-
private List<T> _fontDescriptions;
11+
private final List<T> _fontDescriptions = new ArrayList<>(4);
12+
private final String _family;
1213

13-
public FontFamily() {
14+
public FontFamily(String family) {
15+
this._family = family;
16+
}
17+
18+
public String getFamily() {
19+
return _family;
1420
}
1521

1622
public List<T> getFontDescriptions() {
1723
return _fontDescriptions;
1824
}
1925

2026
public void addFontDescription(T descr) {
21-
if (_fontDescriptions == null) {
22-
_fontDescriptions = new ArrayList<>();
23-
}
2427
_fontDescriptions.add(descr);
25-
Collections.sort(_fontDescriptions,
26-
new Comparator<T>() {
27-
public int compare(T o1, T o2) {
28-
return o1.getWeight() - o2.getWeight();
29-
}
30-
});
31-
}
32-
33-
public void setName(String fontFamilyName) {
28+
Collections.sort(_fontDescriptions, Comparator.comparing(T::getWeight));
3429
}
3530

3631
public T match(int desiredWeight, IdentValue style) {
37-
if (_fontDescriptions == null) {
38-
throw new RuntimeException("fontDescriptions is null");
32+
if (_fontDescriptions.isEmpty()) {
33+
return null;
34+
} else if (_fontDescriptions.size() == 1) {
35+
return _fontDescriptions.get(0);
3936
}
4037

41-
List<T> candidates = new ArrayList<>();
38+
List<T> candidates = new ArrayList<>(_fontDescriptions.size());
4239

43-
for (T description : _fontDescriptions) {
44-
if (description.getStyle() == style) {
45-
candidates.add(description);
46-
}
40+
// First try only matching style.
41+
getStyleMatches(style, candidates);
42+
43+
// Then try changing italic to oblique.
44+
if (candidates.isEmpty() && style == IdentValue.ITALIC) {
45+
getStyleMatches(IdentValue.OBLIQUE, candidates);
4746
}
4847

49-
if (candidates.size() == 0) {
50-
if (style == IdentValue.ITALIC) {
51-
return match(desiredWeight, IdentValue.OBLIQUE);
52-
} else if (style == IdentValue.OBLIQUE) {
53-
return match(desiredWeight, IdentValue.NORMAL);
54-
} else {
55-
candidates.addAll(_fontDescriptions);
56-
}
48+
// Then try changing oblique to normal.
49+
if (candidates.isEmpty() && style == IdentValue.OBLIQUE) {
50+
getStyleMatches(IdentValue.NORMAL, candidates);
51+
}
52+
53+
// Still nothing!
54+
if (candidates.isEmpty()) {
55+
candidates.addAll(_fontDescriptions);
56+
}
57+
58+
if (candidates.size() == 1) {
59+
return candidates.get(0);
5760
}
5861

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

75+
private void getStyleMatches(IdentValue style, List<T> candidates) {
76+
for (T description : _fontDescriptions) {
77+
if (description.getStyle() == style) {
78+
candidates.add(description);
79+
}
80+
}
81+
}
82+
7283
private static final int SM_EXACT = 1;
7384
private static final int SM_LIGHTER_OR_DARKER = 2;
7485
private static final int SM_DARKER_OR_LIGHTER = 3;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<html>
2+
<head>
3+
<style>
4+
@page {
5+
size: 200px 200px;
6+
margin: 10px;
7+
}
8+
</style>
9+
</head>
10+
<body style="font-family: 'no-exist'; font-weight: 400;">
11+
12+
<div style="font-family: 'SourceSans'; font-weight: 400;">
13+
Text should look normal!
14+
</div>
15+
16+
<!-- Using fallback font -->
17+
<div style="font-family: 'no-exist'; font-weight: 400;">
18+
Text should look bold!
19+
</div>
20+
21+
</body>
22+
</html>

openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import java.awt.Shape;
99
import java.awt.geom.Rectangle2D;
1010
import java.io.File;
11+
import java.io.FileInputStream;
1112
import java.io.IOException;
13+
import java.util.EnumSet;
1214
import java.util.Map;
1315

1416
import static org.junit.Assert.assertTrue;
@@ -22,6 +24,7 @@
2224
import com.openhtmltopdf.extend.FSObjectDrawer;
2325
import com.openhtmltopdf.extend.FSObjectDrawerFactory;
2426
import com.openhtmltopdf.extend.OutputDevice;
27+
import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.FSFontUseCase;
2528
import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.FontStyle;
2629
import com.openhtmltopdf.render.RenderingContext;
2730
import com.openhtmltopdf.visualtest.TestSupport;
@@ -682,4 +685,38 @@ public void testIssue472AddSemiTransparentWatermark() throws IOException {
682685
builder.useObjectDrawerFactory(new WatermarkDrawerFactory());
683686
}));
684687
}
688+
689+
/**
690+
* Tests the ability to use fallback fonts.
691+
*/
692+
@Test
693+
public void testIssue641FontFallback() throws IOException {
694+
assertTrue(vtester.runTest("issue-641-font-fallback", builder -> {
695+
builder.useFont(
696+
new File("target/test/visual-tests/SourceSansPro-Regular.ttf"),
697+
"SourceSans", 400, FontStyle.NORMAL, true,
698+
EnumSet.of(FSFontUseCase.DOCUMENT));
699+
builder.useFont(
700+
new File("target/test/visual-tests/Karla-Bold.ttf"),
701+
"Karla", 700, FontStyle.NORMAL, true,
702+
EnumSet.of(FSFontUseCase.FALLBACK_PRE));
703+
}));
704+
}
705+
706+
/**
707+
* Tests the ability to use fallback fonts via input streams.
708+
*/
709+
@Test
710+
public void testIssue641FontFallbackInputStream() throws IOException {
711+
assertTrue(vtester.runTest("issue-641-font-fallback", builder -> {
712+
builder.useFont(
713+
() -> TextVisualRegressionTest.class.getResourceAsStream("/visualtest/html/fonts/SourceSansPro-Regular.ttf"),
714+
"SourceSans", 400, FontStyle.NORMAL, true,
715+
EnumSet.of(FSFontUseCase.DOCUMENT));
716+
builder.useFont(
717+
() -> TextVisualRegressionTest.class.getResourceAsStream("/visualtest/html/fonts/Karla-Bold.ttf"),
718+
"Karla", 700, FontStyle.NORMAL, true,
719+
EnumSet.of(FSFontUseCase.FALLBACK_PRE));
720+
}));
721+
}
685722
}

openhtmltopdf-java2d/src/main/java/com/openhtmltopdf/java2d/Java2DFontResolver.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,6 @@ private void init() {
182182
}
183183

184184
@Deprecated
185-
@Override
186185
public void flushCache() {
187186
instanceHash.clear();
188187
availableFontsHash.clear();
@@ -266,7 +265,7 @@ public void addFontFile(File fontFile, String fontFamilyNameOverride, Integer fo
266265
private FontFamily<FontDescription> getFontFamily(String fontFamilyName) {
267266
FontFamily<FontDescription> fontFamily = _fontFamilies.get(fontFamilyName);
268267
if (fontFamily == null) {
269-
fontFamily = new FontFamily<>();
268+
fontFamily = new FontFamily<>(fontFamilyName);
270269
_fontFamilies.put(fontFamilyName, fontFamily);
271270
}
272271
return fontFamily;

0 commit comments

Comments
 (0)