supplier, String fontFamily,
return (TFinalClass) this;
}
+ /**
+ * 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.
+ *
+ * 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.
+ *
+ * Fonts can also be added using a font-face at-rule in the CSS (not
+ * recommended for Java2D usage).
+ *
+ * IMPORTANT: 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)}
+ *
+ * For gotchas related to font handling please see:
+ * Wiki: Fonts
+ *
+ * @return this for method chaining
+ */
+ public TFinalClass useFont(
+ FSSupplier supplier, String fontFamily, Integer fontWeight,
+ FontStyle fontStyle, boolean subset, Set 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)}
@@ -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 FALLBACK_PRE
+ * and FALLBACK_FINAL
.
+ */
+ FALLBACK_FINAL;
}
}
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/outputdevice/helper/FontFamily.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/outputdevice/helper/FontFamily.java
index b123e4196..bcd8c928e 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/outputdevice/helper/FontFamily.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/outputdevice/helper/FontFamily.java
@@ -8,9 +8,15 @@
import com.openhtmltopdf.css.constants.IdentValue;
public class FontFamily {
- private List _fontDescriptions;
+ private final List _fontDescriptions = new ArrayList<>(4);
+ private final String _family;
- public FontFamily() {
+ public FontFamily(String family) {
+ this._family = family;
+ }
+
+ public String getFamily() {
+ return _family;
}
public List getFontDescriptions() {
@@ -18,42 +24,39 @@ public List getFontDescriptions() {
}
public void addFontDescription(T descr) {
- if (_fontDescriptions == null) {
- _fontDescriptions = new ArrayList<>();
- }
_fontDescriptions.add(descr);
- Collections.sort(_fontDescriptions,
- new Comparator() {
- 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 candidates = new ArrayList<>();
+ List 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);
@@ -69,6 +72,14 @@ public T match(int desiredWeight, IdentValue style) {
}
}
+ private void getStyleMatches(IdentValue style, List 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;
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/text/issue-641-font-fallback.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/text/issue-641-font-fallback.pdf
new file mode 100644
index 000000000..feb26da21
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/text/issue-641-font-fallback.pdf differ
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/text/issue-641-font-fallback.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/issue-641-font-fallback.html
new file mode 100644
index 000000000..701246bf4
--- /dev/null
+++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/issue-641-font-fallback.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+Text should look normal!
+
+
+
+
+Text should look bold!
+
+
+
+
diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java
index 210ae49c0..c1c37bb83 100644
--- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java
+++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java
@@ -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;
@@ -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;
@@ -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));
+ }));
+ }
}
diff --git a/openhtmltopdf-java2d/src/main/java/com/openhtmltopdf/java2d/Java2DFontResolver.java b/openhtmltopdf-java2d/src/main/java/com/openhtmltopdf/java2d/Java2DFontResolver.java
index 51617474b..12d058279 100644
--- a/openhtmltopdf-java2d/src/main/java/com/openhtmltopdf/java2d/Java2DFontResolver.java
+++ b/openhtmltopdf-java2d/src/main/java/com/openhtmltopdf/java2d/Java2DFontResolver.java
@@ -182,7 +182,6 @@ private void init() {
}
@Deprecated
- @Override
public void flushCache() {
instanceHash.clear();
availableFontsHash.clear();
@@ -266,7 +265,7 @@ public void addFontFile(File fontFile, String fontFamilyNameOverride, Integer fo
private FontFamily getFontFamily(String fontFamilyName) {
FontFamily fontFamily = _fontFamilies.get(fontFamilyName);
if (fontFamily == null) {
- fontFamily = new FontFamily<>();
+ fontFamily = new FontFamily<>(fontFamilyName);
_fontFamilies.put(fontFamilyName, fontFamily);
}
return fontFamily;
diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFontResolver.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFontResolver.java
index 5a987e3a3..b067789c5 100644
--- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFontResolver.java
+++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFontResolver.java
@@ -30,23 +30,21 @@
import com.openhtmltopdf.extend.FSSupplier;
import com.openhtmltopdf.extend.FontResolver;
import com.openhtmltopdf.layout.SharedContext;
-import com.openhtmltopdf.outputdevice.helper.FontFaceFontSupplier;
-import com.openhtmltopdf.outputdevice.helper.FontFamily;
-import com.openhtmltopdf.outputdevice.helper.FontResolverHelper;
import com.openhtmltopdf.outputdevice.helper.MinimalFontDescription;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder.PdfAConformance;
+import com.openhtmltopdf.pdfboxout.fontstore.AbstractFontStore;
+import com.openhtmltopdf.pdfboxout.fontstore.FallbackFontStore;
+import com.openhtmltopdf.pdfboxout.fontstore.FontUtil;
+import com.openhtmltopdf.pdfboxout.fontstore.MainFontStore;
import com.openhtmltopdf.render.FSFont;
import com.openhtmltopdf.util.LogMessageId;
import com.openhtmltopdf.util.XRLog;
import org.apache.fontbox.ttf.TrueTypeCollection;
-import org.apache.fontbox.ttf.TrueTypeCollection.TrueTypeFontProcessor;
-import org.apache.fontbox.ttf.TrueTypeFont;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
-import org.apache.pdfbox.pdmodel.font.PDType1Font;
import java.io.File;
import java.io.FilenameFilter;
@@ -56,77 +54,50 @@
import java.util.logging.Level;
/**
- * This class handles all font resolving for the PDF generation. Please note that at the moment only subsetting/embedding
- * of fonts work. So you should always set embedded/subset=true for now.
+ * This class handles all font resolving for the PDF generation.
*/
public class PdfBoxFontResolver implements FontResolver {
- private Map> _fontFamilies;
- private Map _fontCache = new HashMap<>();
+ public enum FontGroup {
+ MAIN,
+ PRE_BUILT_IN_FALLBACK,
+ FINAL_FALLBACK;
+ }
+
private final PDDocument _doc;
- private final SharedContext _sharedContext;
- private final List _collectionsToClose = new ArrayList<>();
- private final FSCacheEx _fontMetricsCache;
- private final PdfAConformance _pdfAConformance;
- private final boolean _pdfUaConform;
+ private final MainFontStore _suppliedFonts;
+ private final FallbackFontStore _preBuiltinFallbackFonts;
+ private final AbstractFontStore _builtinFonts;
+ private final FallbackFontStore _finalFallbackFonts;
public PdfBoxFontResolver(SharedContext sharedContext, PDDocument doc, FSCacheEx pdfMetricsCache, PdfAConformance pdfAConformance, boolean pdfUaConform) {
- _sharedContext = sharedContext;
- _doc = doc;
- _fontMetricsCache = pdfMetricsCache;
- _pdfAConformance = pdfAConformance;
- _pdfUaConform = pdfUaConform;
-
- // All fonts are required to be embedded in PDF/A documents, so we don't add the built-in fonts, if conformance is required.
- _fontFamilies = (_pdfAConformance == PdfAConformance.NONE && !pdfUaConform) ? createInitialFontMap() : new HashMap<>();
- }
+ this._doc = doc;
- @Override
- public FSFont resolveFont(SharedContext renderingContext, FontSpecification spec) {
- return resolveFont(renderingContext, spec.families, spec.size, spec.fontWeight, spec.fontStyle, spec.variant);
- }
+ this._suppliedFonts = new MainFontStore(sharedContext, doc, pdfMetricsCache);
- /**
- * Free all font resources (i.e. open files), the document should already be
- * closed.
- */
- public void close() {
- _fontCache.clear();
+ this._preBuiltinFallbackFonts = new FallbackFontStore(sharedContext, doc, pdfMetricsCache);
- // Close all still open TrueTypeCollections
- for (TrueTypeCollection collection : _collectionsToClose) {
- try {
- collection.close();
- } catch (IOException e) {
- //e.printStackTrace();
- }
- }
- _collectionsToClose.clear();
- }
+ // All fonts are required to be embedded in PDF/A documents, so we don't add
+ // the built-in fonts, if conformance is required.
+ this._builtinFonts = (pdfAConformance == PdfAConformance.NONE && !pdfUaConform) ?
+ new AbstractFontStore.BuiltinFontStore(doc) :
+ new AbstractFontStore.EmptyFontStore();
+
+ this._finalFallbackFonts = new FallbackFontStore(sharedContext, doc, pdfMetricsCache);
+ }
- @Deprecated
@Override
- public void flushCache() {
- _fontFamilies = createInitialFontMap();
- close();
- _fontCache = new HashMap<>();
+ public FSFont resolveFont(SharedContext renderingContext, FontSpecification spec) {
+ return resolveFont(renderingContext, spec.families, spec.size, spec.fontWeight, spec.fontStyle, spec.variant);
}
- @Deprecated
- public void flushFontFaceFonts() {
- _fontCache = new HashMap<>();
-
- for (Iterator> i = _fontFamilies.values().iterator(); i.hasNext(); ) {
- FontFamily family = i.next();
- for (Iterator j = family.getFontDescriptions().iterator(); j.hasNext(); ) {
- FontDescription d = j.next();
- if (d.isFromFontFace()) {
- j.remove();
- }
- }
- if (family.getFontDescriptions().size() == 0) {
- i.remove();
- }
- }
+ /**
+ * Free all font resources (i.e. open files), the document should already be
+ * closed.
+ */
+ public void close() {
+ FontUtil.tryClose(this._suppliedFonts);
+ FontUtil.tryClose(this._preBuiltinFallbackFonts);
+ FontUtil.tryClose(this._finalFallbackFonts);
}
public void importFontFaces(List fontFaces) {
@@ -139,8 +110,6 @@ public void importFontFaces(List fontFaces) {
}
boolean noSubset = style.isIdent(CSSName.FS_FONT_SUBSET, IdentValue.COMPLETE_FONT);
-// boolean embedded = style.isIdent(CSSName.FS_PDF_FONT_EMBED, IdentValue.EMBED);
-// String encoding = style.getStringProperty(CSSName.FS_PDF_FONT_ENCODING);
String fontFamily = null;
IdentValue fontWeight = null;
@@ -161,14 +130,22 @@ public void importFontFaces(List fontFaces) {
fontStyle = style.getIdent(CSSName.FONT_STYLE);
}
- addFontFaceFont(fontFamily, fontWeight, fontStyle, src.asString(), !noSubset);
+ this._suppliedFonts.addFontFaceFont(fontFamily, fontWeight, fontStyle, src.asString(), !noSubset);
}
}
/**
- * Add all fonts in the given directory
+ * @deprecated Use {@link #addFontDirectory(String, boolean, FontGroup)}
*/
+ @Deprecated
public void addFontDirectory(String dir, boolean embedded) throws IOException {
+ addFontDirectory(dir, embedded, FontGroup.MAIN);
+ }
+
+ /**
+ * Add all fonts in the given directory
+ */
+ public void addFontDirectory(String dir, boolean embedded, FontGroup fontGroup) throws IOException {
File f = new File(dir);
if (f.isDirectory()) {
File[] files = f.listFiles(new FilenameFilter() {
@@ -180,88 +157,116 @@ public boolean accept(File dir, String name) {
assert files != null;
for (File file : files) {
- addFont(file, file.getName(), 400, IdentValue.NORMAL, embedded);
+ addFont(file, file.getName(), 400, IdentValue.NORMAL, embedded, fontGroup);
}
}
}
/**
- * Add a font using a FontBox TrueTypeFont.
+ * Adds a font collection (.ttc in an input stream) to a
+ * specific font group.
*/
- private void addFont(TrueTypeFont trueTypeFont, String fontFamilyNameOverride,
- Integer fontWeightOverride, IdentValue fontStyleOverride, boolean subset) throws IOException {
-
-
- PDFont font = PDType0Font.load(_doc, trueTypeFont, subset);
-
- addFontLazy(new PDFontSupplier(font), fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
- }
-
- /**
- * Add a font with a lazy loaded PDFont
- */
- private void addFontLazy(FSSupplier font, String fontFamilyNameOverride, Integer fontWeightOverride, IdentValue fontStyleOverride, boolean subset) {
- FontFamily fontFamily = getFontFamily(fontFamilyNameOverride);
- FontDescription descr = new FontDescription(
- _doc,
- font,
- normalizeFontStyle(fontStyleOverride),
- normalizeFontWeight(fontWeightOverride),
- fontFamilyNameOverride,
- false, // isFromFontFace
- subset,
- _fontMetricsCache);
-
- if (!subset) {
- if (descr.realizeFont()) {
- fontFamily.addFontDescription(descr);
+ public void addFontCollection(
+ FSSupplier supplier,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset,
+ FontGroup fontGroup) throws IOException {
+
+ try (InputStream inputStream = supplier.supply()) {
+ TrueTypeCollection collection = new TrueTypeCollection(inputStream);
+
+ if (fontGroup == FontGroup.MAIN) {
+ this._suppliedFonts.addFontCollection(collection, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ } else {
+ getFallbackFontStore(fontGroup).addFontCollection(collection, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
}
- } else {
- fontFamily.addFontDescription(descr);
}
}
- /**
- * Add fonts using a FontBox TrueTypeCollection.
- */
- private void addFontCollection(TrueTypeCollection collection, final String fontFamilyNameOverride,
- final Integer fontWeightOverride, final IdentValue fontStyleOverride, final boolean subset)
- throws IOException {
- collection.processAllFonts(new TrueTypeFontProcessor() {
- @Override
- public void process(TrueTypeFont ttf) throws IOException {
- addFont(ttf, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
- }
- });
- _collectionsToClose.add(collection);
- }
-
/**
* Add fonts using a .ttc TrueTypeCollection
+ * @deprecated Use {@link #addFontCollection(FSSupplier, String, Integer, IdentValue, boolean, FontGroup)}
*/
+ @Deprecated
public void addFontCollection(FSSupplier supplier, final String fontFamilyNameOverride,
final Integer fontWeightOverride, final IdentValue fontStyleOverride, final boolean subset)
throws IOException {
try (InputStream inputStream = supplier.supply()){
TrueTypeCollection collection = new TrueTypeCollection(inputStream);
- addFontCollection(collection, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ this._suppliedFonts.addFontCollection(collection, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
}
}
+ /**
+ * Adds a font collection (.ttc file) to a specific group.
+ */
+ public void addFontCollection(
+ File file,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset,
+ FontGroup fontGroup) throws IOException {
+
+ TrueTypeCollection collection = new TrueTypeCollection(file);
+
+ if (fontGroup == FontGroup.MAIN) {
+ this._suppliedFonts.addFontCollection(collection, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ } else {
+ getFallbackFontStore(fontGroup).addFontCollection(collection, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ }
+ }
+
/**
* Add fonts using a .ttc TrueTypeCollection
+ * @deprecated Use {@link #addFontCollection(File, String, Integer, IdentValue, boolean, FontGroup)}
*/
+ @Deprecated
public void addFontCollection(File file, final String fontFamilyNameOverride,
final Integer fontWeightOverride, final IdentValue fontStyleOverride, final boolean subset)
throws IOException {
TrueTypeCollection collection = new TrueTypeCollection(file);
- addFontCollection(collection, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ this._suppliedFonts.addFontCollection(collection, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ }
+
+ /**
+ * Add a font file (truetype) to a specific font group.
+ */
+ public void addFont(
+ File fontFile,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset,
+ FontGroup fontGroup) throws IOException {
+
+ if (fontFile.getName().toLowerCase(Locale.US).endsWith(".ttc")) {
+ // Specialcase for TrueTypeCollections
+ addFontCollection(fontFile, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset, fontGroup);
+ } else if (fontGroup == FontGroup.MAIN) {
+ this._suppliedFonts.addFontLazy(new FilePDFontSupplier(fontFile, _doc), fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ } else {
+ getFallbackFontStore(fontGroup).addFontLazy(new FilePDFontSupplier(fontFile, _doc), fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ }
+ }
+
+ private FallbackFontStore getFallbackFontStore(FontGroup fontGroup) {
+ assert fontGroup == FontGroup.PRE_BUILT_IN_FALLBACK ||
+ fontGroup == FontGroup.FINAL_FALLBACK;
+
+ return fontGroup == FontGroup.PRE_BUILT_IN_FALLBACK ?
+ this._preBuiltinFallbackFonts :
+ this._finalFallbackFonts;
}
/**
* Add a font using a existing file. If the file is a TrueTypeCollection, it
* will be handled as such.
+ * @deprecated Use {@link #addFont(File, String, Integer, IdentValue, boolean, FontGroup)}
*/
+ @Deprecated
public void addFont(File fontFile, final String fontFamilyNameOverride, final Integer fontWeightOverride,
final IdentValue fontStyleOverride, final boolean subset) throws IOException {
/*
@@ -275,7 +280,7 @@ public void addFont(File fontFile, final String fontFamilyNameOverride, final In
/*
* We load the font using the file.
*/
- addFontLazy(new FilePDFontSupplier(fontFile, _doc), fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ this._suppliedFonts.addFontLazy(new FilePDFontSupplier(fontFile, _doc), fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
}
/**
@@ -301,262 +306,140 @@ public PDFont supply() {
}
}
- /**
- * Add a font using a InputStream. The given file must be a TrueType Font
- * (.ttf). If you know the underlying stream is a .ttc file you should use
- * {@link #addFontCollection(FSSupplier, String, Integer, IdentValue, boolean)}
- */
- public void addFont(FSSupplier supplier, String fontFamilyNameOverride, Integer fontWeightOverride,
- IdentValue fontStyleOverride, boolean subset) {
- FontFamily fontFamily = getFontFamily(fontFamilyNameOverride);
-
- FontDescription descr = new FontDescription(
- _doc,
- supplier,
- normalizeFontWeight(fontWeightOverride),
- normalizeFontStyle(fontStyleOverride),
- fontFamilyNameOverride,
- false, // isFromFontFace
- subset,
- _fontMetricsCache);
-
- if (!subset) {
- if (descr.realizeFont()) {
- fontFamily.addFontDescription(descr);
- }
- } else {
- fontFamily.addFontDescription(descr);
- }
- }
-
- /**
- * Add a font using a PDFontSupplier. Use this method if you need special rules for font-loading (like using a font-cache)
- * and subclass the {@link PDFontSupplier}.
- */
- public void addFont(PDFontSupplier supplier, String fontFamilyNameOverride, Integer fontWeightOverride,
- IdentValue fontStyleOverride, boolean subset) {
- // would have prefered to used FSSupplier but sadly that would give us an error
- // because the type-ereasure clashes with addFont(FSSupplier ...)
- FontFamily fontFamily = getFontFamily(fontFamilyNameOverride);
-
- FontDescription descr = new FontDescription(
- _doc,
- supplier,
- normalizeFontStyle(fontStyleOverride),
- normalizeFontWeight(fontWeightOverride),
- fontFamilyNameOverride,
- false, // isFromFontFace
- subset,
- _fontMetricsCache);
-
- if (!subset) {
- if (descr.realizeFont()) {
- fontFamily.addFontDescription(descr);
- }
- } else {
- fontFamily.addFontDescription(descr);
- }
- }
-
- private int normalizeFontWeight(IdentValue fontWeight) {
- return fontWeight != null ? FontResolverHelper.convertWeightToInt(fontWeight) : 400;
- }
-
- private int normalizeFontWeight(Integer fontWeight) {
- return fontWeight != null ? fontWeight : 400;
- }
-
- private IdentValue normalizeFontStyle(IdentValue fontStyle) {
- return fontStyle != null ? fontStyle : IdentValue.NORMAL;
- }
-
- private void addFontFaceFont(String fontFamilyName, IdentValue fontWeight, IdentValue fontStyle, String uri, boolean subset) {
- FSSupplier fontSupplier = new FontFaceFontSupplier(_sharedContext, uri);
- FontFamily fontFamily = getFontFamily(fontFamilyName);
-
- FontDescription description = new FontDescription(
- _doc,
- fontSupplier,
- normalizeFontWeight(fontWeight),
- normalizeFontStyle(fontStyle),
- fontFamilyName,
- true, // isFromFontFace
- subset,
- _fontMetricsCache);
-
- if (!subset) {
- if (description.realizeFont()) {
- fontFamily.addFontDescription(description);
- }
+ /**
+ * Adds a font specified by an input stream (truetype) to a specific font group.
+ */
+ public void addFont(
+ FSSupplier supplier,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset,
+ FontGroup fontGroup) {
+
+ if (fontGroup == FontGroup.MAIN) {
+ this._suppliedFonts.addFont(
+ supplier, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
} else {
- fontFamily.addFontDescription(description);
+ getFallbackFontStore(fontGroup).addFont(supplier, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
}
}
- private FontFamily getFontFamily(String fontFamilyName) {
- FontFamily fontFamily = _fontFamilies.get(fontFamilyName);
- if (fontFamily == null) {
- fontFamily = new FontFamily<>();
- _fontFamilies.put(fontFamilyName, fontFamily);
- }
- return fontFamily;
+ /**
+ * Add a font using a InputStream. The given file must be a TrueType Font
+ * (.ttf). If you know the underlying stream is a .ttc file you should use
+ * {@link #addFontCollection(FSSupplier, String, Integer, IdentValue, boolean)}
+ *
+ * @deprecated Use {@link #addFont(FSSupplier, String, Integer, IdentValue, boolean, FontGroup)}
+ */
+ @Deprecated
+ public void addFont(
+ FSSupplier supplier,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset) {
+
+ this._suppliedFonts.addFont(
+ supplier, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
}
- private FSFont resolveFont(SharedContext ctx, String[] families, float size, IdentValue weight, IdentValue style, IdentValue variant) {
- if (!(style == IdentValue.NORMAL || style == IdentValue.OBLIQUE || style == IdentValue.ITALIC)) {
- style = IdentValue.NORMAL;
- }
-
- List fonts = new ArrayList<>(3);
-
- if (families != null) {
- for (int i = 0; i < families.length; i++) {
- FontDescription font = resolveFont(ctx, families[i], size, weight, style, variant);
- if (font != null) {
- fonts.add(font);
- }
- }
- }
-
- if (_pdfAConformance == PdfAConformance.NONE &&
- !_pdfUaConform) {
- // We don't have a final fallback font for PDF/A documents as serif may not be available
- // unless the user has explicitly embedded it.
-
- // For now, we end up with "Serif" built-in font.
- // Q: Should this change?
- // Q: Should we have a final automatically added font?
- fonts.add(resolveFont(ctx, "Serif", size, weight, style, variant));
+ /**
+ * Add a font specified by a PDFontSupplier to a specific font group.
+ */
+ public void addFont(
+ PDFontSupplier supplier,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset,
+ FontGroup fontGroup) {
+
+ if (fontGroup == FontGroup.MAIN) {
+ this._suppliedFonts.addFont(supplier, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ } else {
+ getFallbackFontStore(fontGroup).addFont(supplier, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
}
-
- return new PdfBoxFSFont(fonts, size);
}
- private String normalizeFontFamily(String fontFamily) {
- String result = fontFamily;
- // strip off the "s if they are there
- if (result.startsWith("\"")) {
- result = result.substring(1);
- }
- if (result.endsWith("\"")) {
- result = result.substring(0, result.length() - 1);
- }
-
- // normalize the font name
- if (result.equalsIgnoreCase("serif")) {
- result = "Serif";
- }
- else if (result.equalsIgnoreCase("sans-serif")) {
- result = "SansSerif";
- }
- else if (result.equalsIgnoreCase("monospace")) {
- result = "Monospaced";
- }
-
- return result;
+ /**
+ * Add a font using a PDFontSupplier. Use this method if you need special rules for font-loading (like using a font-cache)
+ * and subclass the {@link PDFontSupplier}.
+ *
+ * @deprecated Use {@link #addFont(PDFontSupplier, String, Integer, IdentValue, boolean, FontGroup)}
+ */
+ @Deprecated
+ public void addFont(
+ PDFontSupplier supplier,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset) {
+
+ this._suppliedFonts.addFont(supplier, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
}
- private FontDescription resolveFont(SharedContext ctx, String fontFamily, float size, IdentValue weight, IdentValue style, IdentValue variant) {
- String normalizedFontFamily = normalizeFontFamily(fontFamily);
- String cacheKey = getHashName(normalizedFontFamily, weight, style);
- FontDescription result = _fontCache.get(cacheKey);
- if (result != null) {
- return result;
- }
- FontFamily family = _fontFamilies.get(normalizedFontFamily);
- if (family != null) {
- result = family.match(FontResolverHelper.convertWeightToInt(weight), style);
- if (result != null) {
- _fontCache.put(cacheKey, result);
- return result;
- }
+ private FSFont resolveFont(SharedContext ctx, String[] families, float size, IdentValue weight, IdentValue style, IdentValue variant) {
+ if (!(style == IdentValue.NORMAL || style == IdentValue.OBLIQUE || style == IdentValue.ITALIC)) {
+ style = IdentValue.NORMAL;
}
- return null;
- }
-
- protected static String getHashName(
- String name, IdentValue weight, IdentValue style) {
- return name + "-" + weight + "-" + style;
- }
-
- private static Map> createInitialFontMap() {
- HashMap> result = new HashMap<>();
- addCourier(result);
- addTimes(result);
- addHelvetica(result);
- addSymbol(result);
- addZapfDingbats(result);
- // Try and load the iTextAsian fonts
-// if(PdfBoxFontResolver.class.getClassLoader().getResource("com/lowagie/text/pdf/fonts/cjkfonts.properties") != null) {
-// addCJKFonts(result);
-// }
-
- return result;
- }
+ List fonts = new ArrayList<>(3);
- private static void addCourier(HashMap> result) {
- FontFamily courier = new FontFamily<>();
- courier.setName("Courier");
+ // Supplied fonts
+ if (families != null) {
+ resolveFamilyFont(ctx, families, size, weight, style, variant, fonts, _suppliedFonts);
+ }
- courier.addFontDescription(new FontDescription(PDType1Font.COURIER_BOLD_OBLIQUE, IdentValue.OBLIQUE, 700));
- courier.addFontDescription(new FontDescription(PDType1Font.COURIER_OBLIQUE, IdentValue.OBLIQUE, 400));
- courier.addFontDescription(new FontDescription(PDType1Font.COURIER_BOLD, IdentValue.NORMAL, 700));
- courier.addFontDescription(new FontDescription(PDType1Font.COURIER, IdentValue.NORMAL, 400));
+ // Pre-builtin fallback fonts.
+ fonts.addAll(_preBuiltinFallbackFonts.resolveFonts(ctx, families, size, weight, style, variant));
- result.put("DialogInput", courier);
- result.put("Monospaced", courier);
- result.put("Courier", courier);
- }
+ // Built-in fonts.
+ if (families != null) {
+ resolveFamilyFont(ctx, families, size, weight, style, variant, fonts, _builtinFonts);
- private static void addTimes(HashMap> result) {
- FontFamily times = new FontFamily<>();
- times.setName("Times");
+ FontDescription serif = _builtinFonts.resolveFont(ctx, "Serif", size, weight, style, variant);
+ if (serif != null) {
+ fonts.add(serif);
+ }
+ }
- times.addFontDescription(new FontDescription(PDType1Font.TIMES_BOLD_ITALIC, IdentValue.ITALIC, 700));
- times.addFontDescription(new FontDescription(PDType1Font.TIMES_ITALIC, IdentValue.ITALIC, 400));
- times.addFontDescription(new FontDescription(PDType1Font.TIMES_BOLD, IdentValue.NORMAL, 700));
- times.addFontDescription(new FontDescription(PDType1Font.TIMES_ROMAN, IdentValue.NORMAL, 400));
+ // Post built-in fallback fonts.
+ fonts.addAll(_finalFallbackFonts.resolveFonts(ctx, families, size, weight, style, variant));
- result.put("Serif", times);
- result.put("TimesRoman", times);
+ return new PdfBoxFSFont(fonts, size);
}
- private static void addHelvetica(HashMap> result) {
- FontFamily helvetica = new FontFamily<>();
- helvetica.setName("Helvetica");
-
- helvetica.addFontDescription(new FontDescription(PDType1Font.HELVETICA_BOLD_OBLIQUE, IdentValue.OBLIQUE, 700));
- helvetica.addFontDescription(new FontDescription(PDType1Font.HELVETICA_OBLIQUE, IdentValue.OBLIQUE, 400));
- helvetica.addFontDescription(new FontDescription(PDType1Font.HELVETICA_BOLD, IdentValue.NORMAL, 700));
- helvetica.addFontDescription(new FontDescription(PDType1Font.HELVETICA, IdentValue.NORMAL, 400));
-
- result.put("Dialog", helvetica);
- result.put("SansSerif", helvetica);
- result.put("Helvetica", helvetica);
+ private void resolveFamilyFont(
+ SharedContext ctx,
+ String[] families,
+ float size,
+ IdentValue weight,
+ IdentValue style,
+ IdentValue variant,
+ List fonts,
+ AbstractFontStore store) {
+
+ for (int i = 0; i < families.length; i++) {
+ FontDescription font = store.resolveFont(ctx, families[i], size, weight, style, variant);
+
+ if (font != null) {
+ fonts.add(font);
+ }
+ }
}
- private static void addSymbol(Map> result) {
- FontFamily fontFamily = new FontFamily<>();
- fontFamily.setName("Symbol");
+
- fontFamily.addFontDescription(new FontDescription(PDType1Font.SYMBOL, IdentValue.NORMAL, 400));
- result.put("Symbol", fontFamily);
- }
- private static void addZapfDingbats(Map> result) {
- FontFamily fontFamily = new FontFamily<>();
- fontFamily.setName("ZapfDingbats");
-
- fontFamily.addFontDescription(new FontDescription(PDType1Font.ZAPF_DINGBATS, IdentValue.NORMAL, 400));
+
- result.put("ZapfDingbats", fontFamily);
- }
+
/* TODO: CJK Fonts
// fontFamilyName, fontName, encoding
@@ -627,22 +510,23 @@ public static class FontDescription implements MinimalFontDescription {
private final boolean _isFromFontFace;
private final boolean _isSubset;
+
private PdfBoxRawPDFontMetrics _metrics;
private final FSCacheEx _metricsCache;
/**
* Create a font description from one of the PDF built-in fonts.
*/
- private FontDescription(PDFont font, IdentValue style, int weight) {
+ public FontDescription(PDFont font, IdentValue style, int weight) {
this(null, font, style, weight);
}
-
+
/**
* Create a font description from an input stream supplier.
* The input stream will only be accessed if {@link #getFont()} or
* {@link #getFontMetrics()} (and the font metrics were not available from cache) are called.
*/
- private FontDescription(
+ public FontDescription(
PDDocument doc, FSSupplier supplier,
int weight, IdentValue style, String family,
boolean isFromFontFace, boolean isSubset,
@@ -685,7 +569,7 @@ private FontDescription(PDDocument doc, PDFont font, IdentValue style, int weigh
* Creates a font description from a PDFont supplier. The supplier will only be called upon
* if {@link #getFont()} or {@link #getFontMetrics()} (and the font metrics were not available from cache) are called.
*/
- private FontDescription(
+ public FontDescription(
PDDocument doc, FSSupplier fontSupplier,
IdentValue style, int weight, String family,
boolean isFromFontFace, boolean isSubset,
@@ -702,6 +586,10 @@ private FontDescription(
_metrics = getFontMetricsFromCache(family, weight, style);
}
+ public String getFamily() {
+ return _family;
+ }
+
private String createFontMetricsCacheKey(String family, int weight, IdentValue style) {
return "font-metrics:" + family + ":" + weight + ":" + style.toString();
}
@@ -726,7 +614,7 @@ private boolean loadMetrics() {
}
}
- private boolean realizeFont() {
+ public boolean realizeFont() {
if (_font == null && _fontSupplier != null) {
XRLog.log(Level.INFO, LogMessageId.LogMessageId2Param.LOAD_LOADING_FONT_FROM_SUPPLIER, _family, "PDFont");
diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java
index 9e91e2c36..28ebfdb77 100644
--- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java
+++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java
@@ -316,8 +316,6 @@ private void setDocumentP(Document doc, String url, NamespaceHandler nsh) {
for (FSDOMMutator domMutator : _domMutators)
domMutator.mutateDocument(doc);
- getFontResolver().flushFontFaceFonts();
-
_sharedContext.setBaseURL(url);
_sharedContext.setNamespaceHandler(nsh);
_sharedContext.getCss().setDocumentContext(_sharedContext, _sharedContext.getNamespaceHandler(), doc, new NullUserInterface());
diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfRendererBuilder.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfRendererBuilder.java
index 78a7b4d7e..5d8c2f8d8 100644
--- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfRendererBuilder.java
+++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfRendererBuilder.java
@@ -8,6 +8,8 @@
import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder;
import com.openhtmltopdf.outputdevice.helper.PageDimensions;
import com.openhtmltopdf.outputdevice.helper.UnicodeImplementation;
+import com.openhtmltopdf.pdfboxout.PdfBoxFontResolver.FontGroup;
+import com.openhtmltopdf.render.FSFont;
import com.openhtmltopdf.util.LogMessageId;
import com.openhtmltopdf.util.XRLog;
@@ -93,7 +95,9 @@ public PdfBoxRenderer buildPdfRenderer(Closeable diagnosticConsumer) {
}
}
- if (font.usedFor.contains(FSFontUseCase.DOCUMENT)) {
+ if (font.usedFor.contains(FSFontUseCase.DOCUMENT) ||
+ font.usedFor.contains(FSFontUseCase.FALLBACK_PRE) ||
+ font.usedFor.contains(FSFontUseCase.FALLBACK_FINAL)) {
IdentValue fontStyle = null;
if (font.style != null) {
@@ -114,19 +118,27 @@ public PdfBoxRenderer buildPdfRenderer(Closeable diagnosticConsumer) {
}
}
+ FontGroup group;
+ if (font.usedFor.contains(FSFontUseCase.FALLBACK_PRE)) {
+ group = FontGroup.PRE_BUILT_IN_FALLBACK;
+ } else if (font.usedFor.contains(FSFontUseCase.FALLBACK_FINAL)) {
+ group = FontGroup.FINAL_FALLBACK;
+ } else {
+ group = FontGroup.MAIN;
+ }
// use InputStream supplier
if (font.supplier != null) {
- resolver.addFont(font.supplier, font.family, font.weight, fontStyle, font.subset);
+ resolver.addFont(font.supplier, font.family, font.weight, fontStyle, font.subset, group);
}
// use PDFont supplier
else if (font.pdfontSupplier != null) {
- resolver.addFont((PDFontSupplier) font.pdfontSupplier, font.family, font.weight, fontStyle, font.subset);
+ resolver.addFont((PDFontSupplier) font.pdfontSupplier, font.family, font.weight, fontStyle, font.subset, group);
}
// load via font File
else {
try {
- resolver.addFont(font.fontFile, font.family, font.weight, fontStyle, font.subset);
+ resolver.addFont(font.fontFile, font.family, font.weight, fontStyle, font.subset, group);
} catch (Exception e) {
XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.INIT_FONT_COULD_NOT_BE_LOADED, font.fontFile.getPath(), e);
}
diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/AbstractFontStore.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/AbstractFontStore.java
new file mode 100644
index 000000000..9901851b5
--- /dev/null
+++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/AbstractFontStore.java
@@ -0,0 +1,125 @@
+package com.openhtmltopdf.pdfboxout.fontstore;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+import com.openhtmltopdf.css.constants.IdentValue;
+import com.openhtmltopdf.layout.SharedContext;
+import com.openhtmltopdf.outputdevice.helper.FontFamily;
+import com.openhtmltopdf.outputdevice.helper.FontResolverHelper;
+import com.openhtmltopdf.pdfboxout.PdfBoxFontResolver;
+import com.openhtmltopdf.pdfboxout.PdfBoxFontResolver.FontDescription;
+
+public abstract class AbstractFontStore {
+ public abstract FontDescription resolveFont(
+ SharedContext ctx,
+ String fontFamily,
+ float size,
+ IdentValue weight,
+ IdentValue style,
+ IdentValue variant);
+
+ public static class EmptyFontStore extends AbstractFontStore {
+ @Override
+ public FontDescription resolveFont(
+ SharedContext ctx, String fontFamily, float size, IdentValue weight,
+ IdentValue style, IdentValue variant) {
+ return null;
+ }
+ }
+
+ public static class BuiltinFontStore extends AbstractFontStore {
+ final Map> _fontFamilies;
+
+ public BuiltinFontStore(PDDocument doc) {
+ this._fontFamilies = createInitialFontMap();
+ }
+
+ static Map> createInitialFontMap() {
+ HashMap> result = new HashMap<>();
+ addCourier(result);
+ addTimes(result);
+ addHelvetica(result);
+ addSymbol(result);
+ addZapfDingbats(result);
+
+ return result;
+ }
+
+ static void addCourier(HashMap> result) {
+ FontFamily courier = new FontFamily<>("Courier");
+
+ courier.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.COURIER_BOLD_OBLIQUE, IdentValue.OBLIQUE, 700));
+ courier.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.COURIER_OBLIQUE, IdentValue.OBLIQUE, 400));
+ courier.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.COURIER_BOLD, IdentValue.NORMAL, 700));
+ courier.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.COURIER, IdentValue.NORMAL, 400));
+
+ result.put("DialogInput", courier);
+ result.put("Monospaced", courier);
+ result.put("Courier", courier);
+ }
+
+ static void addTimes(HashMap> result) {
+ FontFamily times = new FontFamily<>("Times");
+
+ times.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.TIMES_BOLD_ITALIC, IdentValue.ITALIC, 700));
+ times.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.TIMES_ITALIC, IdentValue.ITALIC, 400));
+ times.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.TIMES_BOLD, IdentValue.NORMAL, 700));
+ times.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.TIMES_ROMAN, IdentValue.NORMAL, 400));
+
+ result.put("Serif", times);
+ result.put("TimesRoman", times);
+ }
+
+ static void addHelvetica(HashMap> result) {
+ FontFamily helvetica = new FontFamily<>("Helvetica");
+
+ helvetica.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.HELVETICA_BOLD_OBLIQUE, IdentValue.OBLIQUE, 700));
+ helvetica.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.HELVETICA_OBLIQUE, IdentValue.OBLIQUE, 400));
+ helvetica.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.HELVETICA_BOLD, IdentValue.NORMAL, 700));
+ helvetica.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.HELVETICA, IdentValue.NORMAL, 400));
+
+ result.put("Dialog", helvetica);
+ result.put("SansSerif", helvetica);
+ result.put("Helvetica", helvetica);
+ }
+
+ static void addSymbol(Map> result) {
+ FontFamily fontFamily = new FontFamily<>("Symbol");
+
+ fontFamily.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.SYMBOL, IdentValue.NORMAL, 400));
+
+ result.put("Symbol", fontFamily);
+ }
+
+ static void addZapfDingbats(Map> result) {
+ FontFamily fontFamily = new FontFamily<>("ZapfDingbats");
+
+ fontFamily.addFontDescription(new PdfBoxFontResolver.FontDescription(PDType1Font.ZAPF_DINGBATS, IdentValue.NORMAL, 400));
+
+ result.put("ZapfDingbats", fontFamily);
+ }
+
+ @Override
+ public PdfBoxFontResolver.FontDescription resolveFont(
+ SharedContext ctx,
+ String fontFamily,
+ float size,
+ IdentValue weight,
+ IdentValue style,
+ IdentValue variant) {
+
+ String normalizedFontFamily = FontUtil.normalizeFontFamily(fontFamily);
+ FontFamily family = _fontFamilies.get(normalizedFontFamily);
+
+ if (family != null) {
+ return family.match(FontResolverHelper.convertWeightToInt(weight), style);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/FallbackFontStore.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/FallbackFontStore.java
new file mode 100644
index 000000000..fa358098f
--- /dev/null
+++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/FallbackFontStore.java
@@ -0,0 +1,198 @@
+package com.openhtmltopdf.pdfboxout.fontstore;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.fontbox.ttf.TrueTypeCollection;
+import org.apache.fontbox.ttf.TrueTypeFont;
+import org.apache.fontbox.ttf.TrueTypeCollection.TrueTypeFontProcessor;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType0Font;
+
+import com.openhtmltopdf.css.constants.IdentValue;
+import com.openhtmltopdf.extend.FSCacheEx;
+import com.openhtmltopdf.extend.FSCacheValue;
+import com.openhtmltopdf.extend.FSSupplier;
+import com.openhtmltopdf.layout.SharedContext;
+import com.openhtmltopdf.outputdevice.helper.FontResolverHelper;
+import com.openhtmltopdf.pdfboxout.PDFontSupplier;
+import com.openhtmltopdf.pdfboxout.PdfBoxFontResolver.FontDescription;
+
+public class FallbackFontStore implements Closeable {
+ private final List fonts = new ArrayList<>();
+ private final List _collectionsToClose = new ArrayList<>();
+ private final PDDocument _doc;
+ private final FSCacheEx _fontMetricsCache;
+
+ public FallbackFontStore(
+ SharedContext sharedContext,
+ PDDocument doc,
+ FSCacheEx pdfMetricsCache) {
+ this._doc = doc;
+ this._fontMetricsCache = pdfMetricsCache;
+ }
+
+ private int getFontPriority(FontDescription font, String[] families, IdentValue weight, IdentValue desiredStyle, IdentValue variant) {
+ String fontFamily = font.getFamily();
+ int fontWeight = font.getWeight();
+ IdentValue fontStyle = font.getStyle();
+
+ List desiredFamilies = families != null ?
+ Arrays.asList(families) : Collections.emptyList();
+ int desiredWeight = FontResolverHelper.convertWeightToInt(weight);
+
+ if (fontWeight == desiredWeight &&
+ fontStyle == desiredStyle) {
+ // Exact match for weight and style.
+ return getFamilyPriority(fontFamily, desiredFamilies);
+ } else if (Math.abs(fontWeight - desiredWeight) < 200 &&
+ fontStyle == desiredStyle) {
+ // Near enough weight match, exact style match.
+ return 3 + getFamilyPriority(fontFamily, desiredFamilies);
+ } else if (fontStyle == desiredStyle) {
+ // No weight match, but style matches.
+ return 6 + getFamilyPriority(fontFamily, desiredFamilies);
+ } else {
+ // Neither weight nor style matches.
+ return 9 + getFamilyPriority(fontFamily, desiredFamilies);
+ }
+ }
+
+ private int getFamilyPriority(String fontFamily, List desiredFamilies) {
+ if (!desiredFamilies.isEmpty() &&
+ desiredFamilies.get(0).equals(fontFamily)) {
+ return 1;
+ } else if (desiredFamilies.contains(fontFamily)) {
+ return 2;
+ } else {
+ return 3;
+ }
+ }
+
+ public List resolveFonts(
+ SharedContext ctx, String[] families, float size, IdentValue weight, IdentValue style, IdentValue variant) {
+
+ if (fonts.size() <= 1) {
+ // No need to make a copy to sort.
+ return fonts;
+ }
+
+ List ret = new ArrayList<>(fonts);
+
+ Collections.sort(ret, Comparator.comparing(font -> getFontPriority(font, families, weight, style, variant)));
+
+ return ret;
+ }
+
+ public void addFont(
+ FSSupplier supplier,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset) {
+
+ FontDescription descr = new FontDescription(
+ _doc,
+ supplier,
+ FontUtil.normalizeFontWeight(fontWeightOverride),
+ FontUtil.normalizeFontStyle(fontStyleOverride),
+ fontFamilyNameOverride,
+ false, // isFromFontFace
+ subset,
+ _fontMetricsCache);
+
+ addFont(subset, descr);
+ }
+
+ public void addFont(
+ PDFontSupplier supplier,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset) {
+
+ FontDescription descr = new FontDescription(
+ _doc,
+ supplier,
+ FontUtil.normalizeFontStyle(fontStyleOverride),
+ FontUtil.normalizeFontWeight(fontWeightOverride),
+ fontFamilyNameOverride,
+ false, // isFromFontFace
+ subset,
+ _fontMetricsCache);
+
+ addFont(subset, descr);
+ }
+
+ /**
+ * Add a font with a lazy loaded PDFont
+ */
+ public void addFontLazy(FSSupplier font, String fontFamilyNameOverride, Integer fontWeightOverride, IdentValue fontStyleOverride, boolean subset) {
+ FontDescription descr = new FontDescription(
+ _doc,
+ font,
+ FontUtil.normalizeFontStyle(fontStyleOverride),
+ FontUtil.normalizeFontWeight(fontWeightOverride),
+ fontFamilyNameOverride,
+ false, // isFromFontFace
+ subset,
+ _fontMetricsCache);
+
+ addFont(subset, descr);
+ }
+
+ private void addFont(boolean subset, FontDescription descr) {
+ if (!subset) {
+ if (descr.realizeFont()) {
+ fonts.add(descr);
+ }
+ } else {
+ fonts.add(descr);
+ }
+ }
+
+ public void close() throws IOException {
+ for (TrueTypeCollection collection : _collectionsToClose) {
+ FontUtil.tryClose(collection);
+ }
+ _collectionsToClose.clear();
+ }
+
+ /**
+ * Add a font using a FontBox TrueTypeFont.
+ */
+ void addFont(
+ TrueTypeFont trueTypeFont,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset) throws IOException {
+
+ PDFont font = PDType0Font.load(_doc, trueTypeFont, subset);
+
+ addFontLazy(new PDFontSupplier(font), fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ }
+
+ public void addFontCollection(
+ TrueTypeCollection collection,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset) throws IOException {
+
+ collection.processAllFonts(new TrueTypeFontProcessor() {
+ @Override
+ public void process(TrueTypeFont ttf) throws IOException {
+ addFont(ttf, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ }
+ });
+ _collectionsToClose.add(collection);
+ }
+}
diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/FontUtil.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/FontUtil.java
new file mode 100644
index 000000000..1003484da
--- /dev/null
+++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/FontUtil.java
@@ -0,0 +1,52 @@
+package com.openhtmltopdf.pdfboxout.fontstore;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import com.openhtmltopdf.css.constants.IdentValue;
+import com.openhtmltopdf.outputdevice.helper.FontResolverHelper;
+
+public class FontUtil {
+ public static String normalizeFontFamily(String fontFamily) {
+ String result = fontFamily;
+ // strip off the "s if they are there
+ if (result.startsWith("\"")) {
+ result = result.substring(1);
+ }
+ if (result.endsWith("\"")) {
+ result = result.substring(0, result.length() - 1);
+ }
+
+ // normalize the font name
+ if (result.equalsIgnoreCase("serif")) {
+ result = "Serif";
+ }
+ else if (result.equalsIgnoreCase("sans-serif")) {
+ result = "SansSerif";
+ }
+ else if (result.equalsIgnoreCase("monospace")) {
+ result = "Monospaced";
+ }
+
+ return result;
+ }
+
+ public static int normalizeFontWeight(Integer fontWeight) {
+ return fontWeight != null ? fontWeight : 400;
+ }
+
+ public static IdentValue normalizeFontStyle(IdentValue fontStyle) {
+ return fontStyle != null ? fontStyle : IdentValue.NORMAL;
+ }
+
+ public static int normalizeFontWeight(IdentValue fontWeight) {
+ return fontWeight != null ? FontResolverHelper.convertWeightToInt(fontWeight) : 400;
+ }
+
+ public static void tryClose(Closeable obj) {
+ try {
+ obj.close();
+ } catch (IOException e) {
+ }
+ }
+}
diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/MainFontStore.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/MainFontStore.java
new file mode 100644
index 000000000..6f07c6c3c
--- /dev/null
+++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/fontstore/MainFontStore.java
@@ -0,0 +1,189 @@
+package com.openhtmltopdf.pdfboxout.fontstore;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fontbox.ttf.TrueTypeCollection;
+import org.apache.fontbox.ttf.TrueTypeFont;
+import org.apache.fontbox.ttf.TrueTypeCollection.TrueTypeFontProcessor;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType0Font;
+
+import com.openhtmltopdf.css.constants.IdentValue;
+import com.openhtmltopdf.extend.FSCacheEx;
+import com.openhtmltopdf.extend.FSCacheValue;
+import com.openhtmltopdf.extend.FSSupplier;
+import com.openhtmltopdf.layout.SharedContext;
+import com.openhtmltopdf.outputdevice.helper.FontFaceFontSupplier;
+import com.openhtmltopdf.outputdevice.helper.FontFamily;
+import com.openhtmltopdf.outputdevice.helper.FontResolverHelper;
+import com.openhtmltopdf.pdfboxout.PDFontSupplier;
+import com.openhtmltopdf.pdfboxout.PdfBoxFontResolver.FontDescription;
+
+public class MainFontStore extends AbstractFontStore implements Closeable {
+ private final Map> _fontFamilies = new HashMap<>();
+ private final FSCacheEx _fontMetricsCache;
+ private final PDDocument _doc;
+ private final SharedContext _sharedContext;
+ private final List _collectionsToClose = new ArrayList<>();
+
+ public MainFontStore(
+ SharedContext sharedContext,
+ PDDocument doc,
+ FSCacheEx pdfMetricsCache) {
+
+ this._sharedContext = sharedContext;
+ this._doc = doc;
+ this._fontMetricsCache = pdfMetricsCache;
+ }
+
+ public void close() throws IOException {
+ // Close all still open TrueTypeCollections
+ for (TrueTypeCollection collection : _collectionsToClose) {
+ FontUtil.tryClose(collection);
+ }
+ _collectionsToClose.clear();
+ }
+
+ /**
+ * Add a font using a FontBox TrueTypeFont.
+ */
+ void addFont(TrueTypeFont trueTypeFont, String fontFamilyNameOverride,
+ Integer fontWeightOverride, IdentValue fontStyleOverride, boolean subset) throws IOException {
+
+ PDFont font = PDType0Font.load(_doc, trueTypeFont, subset);
+
+ addFontLazy(new PDFontSupplier(font), fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ }
+
+ /**
+ * Add a font with a lazy loaded PDFont
+ */
+ public void addFontLazy(FSSupplier font, String fontFamilyNameOverride, Integer fontWeightOverride, IdentValue fontStyleOverride, boolean subset) {
+ FontFamily fontFamily = getFontFamily(fontFamilyNameOverride);
+ FontDescription descr = new FontDescription(
+ _doc,
+ font,
+ FontUtil.normalizeFontStyle(fontStyleOverride),
+ FontUtil.normalizeFontWeight(fontWeightOverride),
+ fontFamilyNameOverride,
+ false, // isFromFontFace
+ subset,
+ _fontMetricsCache);
+
+ addFontToFamily(subset, fontFamily, descr);
+ }
+
+ /**
+ * Add fonts using a FontBox TrueTypeCollection.
+ */
+ public void addFontCollection(TrueTypeCollection collection, final String fontFamilyNameOverride,
+ final Integer fontWeightOverride, final IdentValue fontStyleOverride, final boolean subset)
+ throws IOException {
+ collection.processAllFonts(new TrueTypeFontProcessor() {
+ @Override
+ public void process(TrueTypeFont ttf) throws IOException {
+ addFont(ttf, fontFamilyNameOverride, fontWeightOverride, fontStyleOverride, subset);
+ }
+ });
+ _collectionsToClose.add(collection);
+ }
+
+ public void addFontFaceFont(String fontFamilyName, IdentValue fontWeight, IdentValue fontStyle, String uri, boolean subset) {
+ FSSupplier fontSupplier = new FontFaceFontSupplier(_sharedContext, uri);
+ FontFamily fontFamily = getFontFamily(fontFamilyName);
+
+ FontDescription description = new FontDescription(
+ _doc,
+ fontSupplier,
+ FontUtil.normalizeFontWeight(fontWeight),
+ FontUtil.normalizeFontStyle(fontStyle),
+ fontFamilyName,
+ true, // isFromFontFace
+ subset,
+ _fontMetricsCache);
+
+ addFontToFamily(subset, fontFamily, description);
+ }
+
+ public void addFont(
+ FSSupplier supplier,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset) {
+
+ FontFamily fontFamily = getFontFamily(fontFamilyNameOverride);
+
+ FontDescription descr = new FontDescription(
+ _doc,
+ supplier,
+ FontUtil.normalizeFontWeight(fontWeightOverride),
+ FontUtil.normalizeFontStyle(fontStyleOverride),
+ fontFamilyNameOverride,
+ false, // isFromFontFace
+ subset,
+ _fontMetricsCache);
+
+ addFontToFamily(subset, fontFamily, descr);
+ }
+
+ public void addFont(
+ PDFontSupplier supplier,
+ String fontFamilyNameOverride,
+ Integer fontWeightOverride,
+ IdentValue fontStyleOverride,
+ boolean subset) {
+
+ // would have prefered to used FSSupplier but sadly that would give us an error
+ // because the type-ereasure clashes with addFont(FSSupplier ...)
+ FontFamily fontFamily = getFontFamily(fontFamilyNameOverride);
+
+ FontDescription descr = new FontDescription(
+ _doc,
+ supplier,
+ FontUtil.normalizeFontStyle(fontStyleOverride),
+ FontUtil.normalizeFontWeight(fontWeightOverride),
+ fontFamilyNameOverride,
+ false, // isFromFontFace
+ subset,
+ _fontMetricsCache);
+
+ addFontToFamily(subset, fontFamily, descr);
+ }
+
+ @Override
+ public FontDescription resolveFont(
+ SharedContext ctx, String fontFamily, float size, IdentValue weight, IdentValue style, IdentValue variant) {
+
+ String normalizedFontFamily = FontUtil.normalizeFontFamily(fontFamily);
+ FontFamily family = _fontFamilies.get(normalizedFontFamily);
+
+ if (family != null) {
+ return family.match(FontResolverHelper.convertWeightToInt(weight), style);
+ }
+
+ return null;
+ }
+
+ private void addFontToFamily(boolean subset, FontFamily fontFamily, FontDescription descr) {
+ if (!subset) {
+ if (descr.realizeFont()) {
+ fontFamily.addFontDescription(descr);
+ }
+ } else {
+ fontFamily.addFontDescription(descr);
+ }
+ }
+
+ private FontFamily getFontFamily(String fontFamilyName) {
+ return _fontFamilies.computeIfAbsent(fontFamilyName, name -> new FontFamily<>(fontFamilyName));
+ }
+
+}