Skip to content

Commit

Permalink
Early working prototype of using a external font face defined in CSS …
Browse files Browse the repository at this point in the history
…inside a SVG graphic.

There are several flaws stilled to be worked on:
- The font families are re-requested for each SVG image rather than
globally.
- Text color is not working for custom fonts.
  • Loading branch information
danfickle committed Jun 15, 2016
1 parent 01ea373 commit bb586a9
Show file tree
Hide file tree
Showing 11 changed files with 899 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package com.openhtmltopdf.extend;

import java.util.List;

import org.w3c.dom.Element;

import com.openhtmltopdf.css.sheet.FontFaceRule;
import com.openhtmltopdf.layout.SharedContext;
import com.openhtmltopdf.render.RenderingContext;

public interface SVGDrawer {
public void drawSVG(Element svgElement, OutputDevice outputDevice, RenderingContext ctx, double x, double y);
public void drawSVG(Element svgElement, OutputDevice outputDevice, RenderingContext ctx, double x, double y, SharedContext shared);

public void importFontFaceRules(List<FontFaceRule> fontFaces);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDFontFactory;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
Expand Down Expand Up @@ -1260,8 +1262,6 @@ public void setBidiReorderer(BidiReorderer reorderer) {

@Override
public void drawText(RenderingContext c, String text, float x, float y) {
// TODO Auto-generated method stub

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,15 @@ public class PdfBoxRenderer {
private PDFCreationListener _listener;

private OutputStream _os;
private SVGDrawer _svgImpl;

public PdfBoxRenderer(boolean testMode) {
this(DEFAULT_DOTS_PER_POINT, DEFAULT_DOTS_PER_PIXEL, true, testMode, null, null, null, null);
}

public PdfBoxRenderer(float dotsPerPoint, int dotsPerPixel, boolean useSubsets, boolean testMode, HttpStreamFactory factory, FSUriResolver _resolver, FSCache _cache, SVGDrawer svgImpl) {
_pdfDoc = new PDDocument();

_svgImpl = svgImpl;
_dotsPerPoint = dotsPerPoint;
_testMode = testMode;
_outputDevice = new PdfBoxOutputDevice(dotsPerPoint, testMode);
Expand Down Expand Up @@ -255,6 +256,10 @@ public void setDocument(Document doc, String url, NamespaceHandler nsh) {
_sharedContext.setNamespaceHandler(nsh);
_sharedContext.getCss().setDocumentContext(_sharedContext, _sharedContext.getNamespaceHandler(), doc, new NullUserInterface());
getFontResolver().importFontFaces(_sharedContext.getCss().getFontFaceRules());

if (_svgImpl != null) {
_svgImpl.importFontFaceRules(_sharedContext.getCss().getFontFaceRules());
}
}

public PDEncryption getPDFEncryption() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public int getBaseline() {

@Override
public void paint(RenderingContext c, PdfBoxOutputDevice outputDevice, BlockBox box) {
svg.drawSVG(e, outputDevice, null, point.getX(), point.getY());
svg.drawSVG(e, outputDevice, c, point.getX(), point.getY(), ((PdfBoxOutputDevice) outputDevice).getSharedContext());
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.openhtmltopdf.svgsupport;

import java.util.List;

import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
Expand All @@ -8,20 +10,29 @@
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.openhtmltopdf.css.sheet.FontFaceRule;
import com.openhtmltopdf.extend.OutputDevice;
import com.openhtmltopdf.extend.SVGDrawer;
import com.openhtmltopdf.layout.SharedContext;
import com.openhtmltopdf.render.RenderingContext;
import com.openhtmltopdf.util.XRLog;

public class BatikSVGDrawer implements SVGDrawer {

private static final String DEFAULT_VP_WIDTH = "400";
private static final String DEFAULT_VP_HEIGHT = "400";
private List<FontFaceRule> rules;

@Override
public void drawSVG(Element svgElement, OutputDevice outputDevice, RenderingContext ctx, double x, double y) {
PDFTranscoder transcoder = new PDFTranscoder(outputDevice, ctx, x, y);

public void importFontFaceRules(List<FontFaceRule> fontFaces) {
this.rules = fontFaces;
}

@Override
public void drawSVG(Element svgElement, OutputDevice outputDevice, RenderingContext ctx, double x, double y, SharedContext shared) {
PDFTranscoder transcoder = new PDFTranscoder(outputDevice, ctx, x, y, shared);
transcoder.fontResolver.importFontFaces(rules);

try {
DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
Document newDocument = impl.createDocument(SVGDOMImplementation.SVG_NAMESPACE_URI, "svg", null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package com.openhtmltopdf.svgsupport;

import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.text.CharacterIterator;

import org.apache.batik.gvt.font.GVTFont;
import org.apache.batik.gvt.font.GVTFontFamily;
import org.apache.batik.gvt.font.GVTGlyphVector;
import org.apache.batik.gvt.font.GVTLineMetrics;

/**
* An adapter around awt.Font to GVTFont.
* Code from: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/fop-core/src/main/java/org/apache/fop/svg/font/FOPGVTFont.java
*
*/
public class OpenHtmlGvtFont implements GVTFont {

private final Font baseFont;
private final GVTFontFamily fontFamily;
private final float size;

private static int toFontWeight(Float weight) {
if (weight == null) {
return Font.PLAIN;
}
else if (weight <= TextAttribute.WEIGHT_BOLD) {
return Font.PLAIN;
}
else {
return Font.BOLD;
}
}

private static int toStyle(Float posture) {
return ((posture != null) && (posture.floatValue() > 0.0))
? Font.ITALIC
: Font.PLAIN;
}


public OpenHtmlGvtFont(byte[] fontBytes, GVTFontFamily family, float size, Float fontWeight, Float fontStyle) throws FontFormatException {
Font font;

try {
font = Font.createFont(Font.TRUETYPE_FONT, new ByteArrayInputStream(fontBytes)).deriveFont(toFontWeight(fontWeight) | toStyle(fontStyle) , size);
} catch (IOException e) {
// Shouldn't happen
e.printStackTrace();
font = null;
}

this.baseFont = font;
this.fontFamily = family;
this.size = size;
}

private OpenHtmlGvtFont(Font font, GVTFontFamily family, float size) {
this.baseFont = font;
this.fontFamily = family;
this.size = size;
}

@Override
public boolean canDisplay(char c) {
return this.baseFont.canDisplay(c);
}

@Override
public int canDisplayUpTo(String str) {
for (int i = 0; i < str.length(); i++) {
if (!this.baseFont.canDisplay(str.charAt(i)))
return i;
}

return -1;
}

@Override
public int canDisplayUpTo(char[] str, int start, int limit) {
for (int i = start; i < limit; i++) {
if (!this.baseFont.canDisplay(str[i]))
return i;
}

return -1;
}

@Override
public int canDisplayUpTo(CharacterIterator iter, int start, int limit) {
for (char c = iter.setIndex(start); iter.getIndex() < limit; c = iter.next()) {
if (!canDisplay(c)) {
return iter.getIndex();
}
}

return -1;
}

@Override
public GVTGlyphVector createGlyphVector(FontRenderContext frc, char[] arg1) {
return createGlyphVector(frc, new String(arg1));
}

@Override
public GVTGlyphVector createGlyphVector(FontRenderContext frc,
CharacterIterator arg1) {
return new OpenHtmlGvtGlyphVector(this.baseFont.createGlyphVector(frc, arg1), this, frc);
}

@Override
public GVTGlyphVector createGlyphVector(FontRenderContext frc, String arg1) {
return new OpenHtmlGvtGlyphVector(this.baseFont.createGlyphVector(frc, arg1), this, frc);
}

@Override
public GVTGlyphVector createGlyphVector(FontRenderContext frc, int[] arg1,
CharacterIterator arg2) {
throw new UnsupportedOperationException();
}

@Override
public GVTFont deriveFont(float arg0) {
Font newFont = this.baseFont.deriveFont(arg0);
return new OpenHtmlGvtFont(newFont, this.fontFamily, arg0);
}

@Override
public String getFamilyName() {
return this.fontFamily.getFamilyName();
}

@Override
public float getHKern(int arg0, int arg1) {
return 0;
}

@Override
public GVTLineMetrics getLineMetrics(String arg0, FontRenderContext arg1) {
return new GVTLineMetrics(this.baseFont.getLineMetrics(arg0, arg1));
}

@Override
public GVTLineMetrics getLineMetrics(char[] arg0, int arg1, int arg2,
FontRenderContext arg3) {
return new GVTLineMetrics(this.baseFont.getLineMetrics(arg0, arg1, arg2, arg3));
}

@Override
public GVTLineMetrics getLineMetrics(CharacterIterator arg0, int arg1,
int arg2, FontRenderContext arg3) {
return new GVTLineMetrics(this.baseFont.getLineMetrics(arg0, arg1, arg2, arg3));
}

@Override
public GVTLineMetrics getLineMetrics(String arg0, int arg1, int arg2,
FontRenderContext arg3) {
return new GVTLineMetrics(this.baseFont.getLineMetrics(arg0, arg1, arg2, arg3));
}

@Override
public float getSize() {
return this.baseFont.getSize() / 1000f;
}

@Override
public float getVKern(int arg0, int arg1) {
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.openhtmltopdf.svgsupport;

import java.awt.FontFormatException;
import java.awt.font.TextAttribute;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.AttributedCharacterIterator;
import java.util.HashMap;
import java.util.Map;

import org.apache.batik.gvt.font.GVTFont;
import org.apache.batik.gvt.font.GVTFontFace;
import org.apache.batik.gvt.font.GVTFontFamily;

import com.openhtmltopdf.util.XRLog;

public class OpenHtmlGvtFontFamily implements GVTFontFamily {

private static class FontDescriptor {
Float size;
Float style;
Float weight;

private boolean eq(Object obj1, Object obj2)
{
if (obj1 == null && obj2 == null)
return true;
else if (obj1 == null)
return false;
else
return obj1.equals(obj2);

}

@Override
public boolean equals(Object obj) {
return (obj instanceof FontDescriptor &&
eq(((FontDescriptor) obj).style, this.style) &&
eq(((FontDescriptor) obj).weight, this.weight) &&
eq(((FontDescriptor) obj).size, this.size));
}

@Override
public int hashCode() {
return (size == null ? 0 : size.hashCode()) + (style == null ? 0 : style.hashCode()) + (weight == null ? 0 : weight.hashCode());

}
}

private final Map<FontDescriptor, OpenHtmlGvtFont> fonts = new HashMap<FontDescriptor, OpenHtmlGvtFont>(1);
private final String fontFamily;

public OpenHtmlGvtFontFamily(String family) {
this.fontFamily = family;
}

public void addFont(byte[] bytes, float size, Float fontWeight, Float fontStyle) throws FontFormatException {
FontDescriptor des = new FontDescriptor();
des.size = size;
des.style = fontStyle;
des.weight = fontWeight;

fonts.put(des, new OpenHtmlGvtFont(bytes, this, size, fontWeight, fontStyle));
}

@Override
public GVTFont deriveFont(float sz, AttributedCharacterIterator arg1) {
return deriveFont(sz, arg1.getAttributes());
}

@Override
public GVTFont deriveFont(float size, @SuppressWarnings("rawtypes") Map attrs) {
Float fontWeight = (Float) attrs.get(TextAttribute.WEIGHT);
Float fontStyle = (Float) attrs.get(TextAttribute.POSTURE);
Float sz = size;

FontDescriptor des = new FontDescriptor();
des.weight = fontWeight;
des.style = fontStyle;
des.size = sz;

if (fonts.containsKey(des)) {
return fonts.get(des);
}

return fonts.values().iterator().next().deriveFont(sz);
}

@Override
public String getFamilyName() {
return this.fontFamily;
}

@Override
public GVTFontFace getFontFace() {
return new GVTFontFace(this.fontFamily);
}

@Override
public boolean isComplex() {
return false;
}

}
Loading

0 comments on commit bb586a9

Please sign in to comment.