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

Fixes #641 Support fallback fonts #669

Merged
merged 4 commits into from
Mar 19, 2021
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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