com.openhtmltopdf.java2d.Java2DFontResolver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openhtmltopdf-java2d Show documentation
Show all versions of openhtmltopdf-java2d Show documentation
Openhtmltopdf is a CSS 2.1 renderer written in Java. This artifact supports image output with the Java2D API.
/*
* {{{ header & license
* Copyright (c) 2004, 2005 Joshua Marinacci
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* }}}
*/
package com.openhtmltopdf.java2d;
import com.openhtmltopdf.css.constants.CSSName;
import com.openhtmltopdf.css.constants.IdentValue;
import com.openhtmltopdf.css.sheet.FontFaceRule;
import com.openhtmltopdf.css.style.CalculatedStyle;
import com.openhtmltopdf.css.style.FSDerivedValue;
import com.openhtmltopdf.css.value.FontSpecification;
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.render.FSFont;
import com.openhtmltopdf.util.LogMessageId;
import com.openhtmltopdf.util.XRLog;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.GraphicsEnvironment;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
/**
* REsolves an AWT font instance from a list of CSS font families and characteristics.
*
* @author Joshua Marinacci
*/
public class Java2DFontResolver implements FontResolver {
private static abstract class FontDescription implements MinimalFontDescription {
//protected static final String FAIL_MSG = "Couldn't load font. Please check that it is a valid truetype font.";
protected final int _weight;
protected final IdentValue _style;
protected Font _font;
private FontDescription(int weight, IdentValue style) {
this._weight = weight;
this._style = style;
}
@Override
public int getWeight() {
return _weight;
}
@Override
public IdentValue getStyle() {
return _style;
}
private Font getBaseFont() {
return _font;
}
protected abstract boolean realizeFont();
}
private static class InputStreamFontDescription extends FontDescription {
private FSSupplier _supplier;
private InputStreamFontDescription(FSSupplier supplier, int weight, IdentValue style) {
super(weight, style);
this._supplier = supplier;
}
@Override
protected boolean realizeFont() {
if (_font == null && _supplier != null) {
InputStream is = _supplier.supply();
_supplier = null; // We only try once.
if (is == null) {
return false;
}
try {
_font = Font.createFont(Font.TRUETYPE_FONT, is);
} catch (IOException|FontFormatException e) {
XRLog.log(Level.WARNING, LogMessageId.LogMessageId0Param.EXCEPTION_JAVA2D_COULD_NOT_LOAD_FONT, e);
return false;
} finally {
try {
is.close();
} catch (IOException e) {
}
}
}
return _font != null;
}
}
private static class FileFontDescription extends FontDescription {
private File _fontFile;
private FileFontDescription(File fontFile, int weight, IdentValue style) {
super(weight, style);
this._fontFile = fontFile;
}
@Override
protected boolean realizeFont() {
if (_font == null && _fontFile != null) {
try {
_font = Font.createFont(Font.TRUETYPE_FONT, _fontFile);
_fontFile = null;
} catch (IOException | FontFormatException e) {
XRLog.log(Level.WARNING, LogMessageId.LogMessageId0Param.EXCEPTION_JAVA2D_COULD_NOT_LOAD_FONT, e);
return false;
}
}
return _font != null;
}
}
/**
* Map of concrete instances of fonts including size, weight, etc.
*/
private final HashMap instanceHash = new HashMap<>();
/**
* Map of base fonts, from which we can derive a concrete instance at the correct size, weight, etc.
* Note: The value is initially null until we need the given base font.
*/
private final HashMap availableFontsHash = new HashMap<>();
private final SharedContext _sharedContext;
private final HashMap> _fontFamilies = new HashMap<>();
public Java2DFontResolver(SharedContext sharedCtx, boolean useEnvironmentFonts) {
_sharedContext = sharedCtx;
if (useEnvironmentFonts) {
init();
}
}
private void init() {
GraphicsEnvironment gfx = GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] availableFonts = gfx.getAvailableFontFamilyNames();
// preload the font map with the font names as keys
// don't add the actual font objects because that would be a waste of memory
// we will only add them once we need to use them
// put empty strings in instead
for (String fontName : availableFonts) {
availableFontsHash.put(fontName, null);
}
// preload sans, serif, and monospace into the available font hash
availableFontsHash.put("Serif", new Font("Serif", Font.PLAIN, 1));
availableFontsHash.put("SansSerif", new Font("SansSerif", Font.PLAIN, 1));
availableFontsHash.put("Monospaced", new Font("Monospaced", Font.PLAIN, 1));
}
@Deprecated
@Override
public void flushCache() {
instanceHash.clear();
availableFontsHash.clear();
_fontFamilies.clear();
init();
}
public void importFontFaces(List fontFaces) {
for (FontFaceRule rule : fontFaces) {
CalculatedStyle style = rule.getCalculatedStyle();
FSDerivedValue src = style.valueByName(CSSName.SRC);
if (src == IdentValue.NONE) {
continue;
}
String fontFamily = null;
IdentValue fontWeight = null;
IdentValue fontStyle = null;
if (rule.hasFontFamily()) {
fontFamily = style.valueByName(CSSName.FONT_FAMILY).asString();
} else {
XRLog.log(Level.WARNING, LogMessageId.LogMessageId0Param.CSS_PARSE_MUST_PROVIDE_AT_LEAST_A_FONT_FAMILY_AND_SRC_IN_FONT_FACE_RULE);
continue;
}
if (rule.hasFontWeight()) {
fontWeight = style.getIdent(CSSName.FONT_WEIGHT);
}
if (rule.hasFontStyle()) {
fontStyle = style.getIdent(CSSName.FONT_STYLE);
}
addFontFaceFont(fontFamily, fontWeight, fontStyle, src.asString());
}
}
public void addInputStreamFont(FSSupplier fontSupplier, String fontFamilyNameOverride,
Integer fontWeightOverride, IdentValue fontStyleOverride) {
FontFamily fontFamily = getFontFamily(fontFamilyNameOverride);
FontDescription descr = new InputStreamFontDescription(
fontSupplier,
fontWeightOverride != null ? fontWeightOverride : 400,
fontStyleOverride != null ? fontStyleOverride : IdentValue.NORMAL);
fontFamily.addFontDescription(descr);
}
private void addFontFaceFont(
String fontFamilyNameOverride, IdentValue fontWeightOverride, IdentValue fontStyleOverride,
String uri) {
FSSupplier fontSupplier = new FontFaceFontSupplier(_sharedContext, uri);
addInputStreamFont(
fontSupplier,
fontFamilyNameOverride,
fontWeightOverride != null ? FontResolverHelper.convertWeightToInt(fontWeightOverride) : 400,
fontStyleOverride);
}
/**
* Add a font using a existing file. Does not handle true type collections.
*/
public void addFontFile(File fontFile, String fontFamilyNameOverride, Integer fontWeightOverride,
final IdentValue fontStyleOverride) {
FontFamily fontFamily = getFontFamily(fontFamilyNameOverride);
FontDescription descr = new FileFontDescription(
fontFile,
fontWeightOverride != null ? fontWeightOverride : 400,
fontStyleOverride != null ? fontStyleOverride : IdentValue.NORMAL);
fontFamily.addFontDescription(descr);
}
private FontFamily getFontFamily(String fontFamilyName) {
FontFamily fontFamily = _fontFamilies.get(fontFamilyName);
if (fontFamily == null) {
fontFamily = new FontFamily<>();
_fontFamilies.put(fontFamilyName, fontFamily);
}
return fontFamily;
}
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;
}
private Font resolveFontFaceBaseFont(String normalizedFontFamily, float size, IdentValue weight, IdentValue style) {
FontFamily fontFamily = _fontFamilies.get(normalizedFontFamily);
if (fontFamily != null) {
FontDescription result = fontFamily.match(FontResolverHelper.convertWeightToInt(weight), style);
if (result != null) {
if (result.realizeFont()) {
return result.getBaseFont();
}
}
}
return null;
}
/**
* Resolves a list of font families.
* Search order for each family is:
* 1. Concrete fonts with correct size that have already been used.
* 2. Font face fonts.
* 3. System fonts.
*/
public FSFont resolveFont(SharedContext ctx, String[] families, float size, IdentValue weight, IdentValue style,
IdentValue variant) {
List fonts = new ArrayList<>(3);
if (families != null) {
for (int i = 0; i < families.length; i++) {
String normal = normalizeFontFamily(families[i]);
String fontInstanceName = getFontInstanceHashName(ctx, normal, size, weight, style, variant);
// check if the font instance exists in the hash table
if (instanceHash.containsKey(fontInstanceName)) {
// if so then add it and continue to next family.
fonts.add(instanceHash.get(fontInstanceName));
continue;
}
// Next we search the list of font-face rule fonts.
Font baseFont = resolveFontFaceBaseFont(normal, size, weight, style);
if (baseFont != null) {
// scale vs font scale value too
size *= ctx.getTextRenderer().getFontScale();
// We always use Font.PLAIN here as the provided font is already in the specifed
// weight and style.
Font derivedFont = baseFont.deriveFont(Font.PLAIN, size);
// add the font to the hash so we don't have to do this again
instanceHash.put(fontInstanceName, derivedFont);
// add it to the list of concrete fonts to be returned and continue with the
// next family.
fonts.add(derivedFont);
continue;
}
// Finally we search the system fonts.
if (availableFontsHash.containsKey(normal)) {
Font possiblyNullFont = availableFontsHash.get(normal);
// have we actually allocated the root font object yet?
Font rootFont = null;
if (possiblyNullFont != null) {
rootFont = possiblyNullFont;
} else {
rootFont = new Font(normal, Font.PLAIN, 1);
availableFontsHash.put(normal, rootFont);
}
// now that we have a root font, we need to create the correct version of it
Font fnt = createFont(ctx, rootFont, size, weight, style, variant);
// add the font to the hash so we don't have to do this again
instanceHash.put(fontInstanceName, fnt);
fonts.add(fnt);
continue;
}
}
}
if (availableFontsHash.containsKey("Serif")) {
// We add the default serif as last fallback font.
Font fnt = createFont(ctx, availableFontsHash.get("Serif"), size, weight, style, variant);
instanceHash.put(getFontInstanceHashName(ctx, "Serif", size, weight, style, variant), fnt);
fonts.add(fnt);
}
return new Java2DFont(fonts, size);
}
/**
* Sets the fontMapping attribute of the FontResolver object
*
* @param name The new fontMapping value
* @param font The new fontMapping value
*/
public void setFontMapping(String name, Font font) {
availableFontsHash.put(name, font.deriveFont(1f));
}
/**
* Creates a concrete instance of a font at specified size, weight, style and variant.
*/
protected static Font createFont(SharedContext ctx, Font rootFont, float size, IdentValue weight, IdentValue style, IdentValue variant) {
//Uu.p("creating font: " + root_font + " size = " + size +
// " weight = " + weight + " style = " + style + " variant = " + variant);
int fontConst = Font.PLAIN;
if (weight != null &&
(weight == IdentValue.BOLD ||
weight == IdentValue.FONT_WEIGHT_700 ||
weight == IdentValue.FONT_WEIGHT_800 ||
weight == IdentValue.FONT_WEIGHT_900)) {
fontConst = fontConst | Font.BOLD;
}
if (style != null && (style == IdentValue.ITALIC || style == IdentValue.OBLIQUE)) {
fontConst = fontConst | Font.ITALIC;
}
// scale vs font scale value too
size *= ctx.getTextRenderer().getFontScale();
Font fnt = rootFont.deriveFont(fontConst, size);
if (variant != null) {
if (variant == IdentValue.SMALL_CAPS) {
fnt = fnt.deriveFont((float) (((float) fnt.getSize()) * 0.6));
}
}
return fnt;
}
/**
* Gets the hash key for a concrete instance of a font.
* This incorporates size, weight, etc.
*/
protected static String getFontInstanceHashName(SharedContext ctx, String name, float size, IdentValue weight, IdentValue style, IdentValue variant) {
return name + "-" + (size * ctx.getTextRenderer().getFontScale()) + "-" + weight + "-" + style + "-" + variant;
}
@Override
public FSFont resolveFont(SharedContext renderingContext, FontSpecification spec) {
return resolveFont(renderingContext, spec.families, spec.size, spec.fontWeight, spec.fontStyle, spec.variant);
}
}