Skip to content

Commit

Permalink
Implement ObjectDrawer for <object> tags.
Browse files Browse the repository at this point in the history
Implement a default factory for FSObjectDrawer, which allows to
register custom drawers.

The testrunner includes a simple example how to implement
a FSObjectDrawer.

Currently its only possible with the PdfBoxRendererBuilder to register
a FSObjectDrawerFactory. As soon as danfickle#77 is merged I will implement
the same for Java2D.

This is the final feature I need to migrate all my reports
to OpenHtmlToPdf, as we do some custom graphs in some reports.
  • Loading branch information
rototor committed Mar 12, 2017
1 parent e257ac3 commit c046c8e
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
* Handle the drawing of &lt;object&gt; tags
*/
public interface FSObjectDrawer {
void drawObject(Element e, float x, float y, float width, float height, OutputDevice outputDevice,
RenderingContext ctx);
void drawObject(Element e, double x, double y, double width, double height, OutputDevice outputDevice,
RenderingContext ctx, int dotsPerPixel);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.openhtmltopdf.render;

import com.openhtmltopdf.extend.FSObjectDrawer;
import com.openhtmltopdf.extend.FSObjectDrawerFactory;
import org.w3c.dom.Element;

import java.util.HashMap;
import java.util.Map;

/**
* Default FSObjectDrawer factory, which allows to register drawer for specified
* content type
*/
public class DefaultObjectDrawerFactory implements FSObjectDrawerFactory {

/**
* Maps content type => Drawer
*/
private final Map<String, FSObjectDrawer> drawerMap = new HashMap<String, FSObjectDrawer>();

@Override
public FSObjectDrawer createDrawer(Element e) {
return drawerMap.get(e.getAttribute("type"));
}

/**
* @param contentType the content type this drawer is for
* @param drawer Drawer
*/
public void registerDrawer(String contentType, FSObjectDrawer drawer) {
drawerMap.put(contentType,drawer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,28 @@

import com.openhtmltopdf.bidi.support.ICUBidiReorderer;
import com.openhtmltopdf.bidi.support.ICUBidiSplitter;
import com.openhtmltopdf.extend.FSObjectDrawer;
import com.openhtmltopdf.extend.OutputDevice;
import com.openhtmltopdf.extend.OutputDeviceGraphicsDrawer;
import com.openhtmltopdf.java2d.api.BufferedImagePageProcessor;
import com.openhtmltopdf.java2d.api.DefaultPageProcessor;
import com.openhtmltopdf.java2d.api.FSPageOutputStreamSupplier;
import com.openhtmltopdf.java2d.api.Java2DRendererBuilder;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder.TextDirection;
import com.openhtmltopdf.render.DefaultObjectDrawerFactory;
import com.openhtmltopdf.render.RenderingContext;
import com.openhtmltopdf.svgsupport.BatikSVGDrawer;
import com.openhtmltopdf.util.JDKXRLogger;
import com.openhtmltopdf.util.XRLog;
import com.openhtmltopdf.util.XRLogger;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.util.Charsets;
import org.w3c.dom.Element;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
Expand Down Expand Up @@ -69,6 +77,11 @@ public static void main(String[] args) throws Exception {
*/
runTestCase("moonbase");

/*
* Custom Objects
*/
runTestCase("custom-objects");

/* Add additional test cases here. */
}

Expand Down Expand Up @@ -139,6 +152,11 @@ private static void renderPDF(String html, OutputStream outputStream) throws Exc
builder.useUnicodeBidiReorderer(new ICUBidiReorderer());
builder.defaultTextDirection(TextDirection.LTR);
builder.useSVGDrawer(new BatikSVGDrawer());

DefaultObjectDrawerFactory objectDrawerFactory = new DefaultObjectDrawerFactory();
objectDrawerFactory.registerDrawer("custom/binary-tree", new SampleObjectDrawerBinaryTree());
builder.useObjectDrawerFactory(objectDrawerFactory);

builder.withHtmlContent(html, TestcaseRunner.class.getResource("/testcases/").toString());
builder.toStream(outputStream);
builder.run();
Expand Down Expand Up @@ -190,4 +208,45 @@ public static void runTestCase(String testCaseFile) throws Exception {

renderPNG(html, testCaseOutputPNGFile);
}

public static class SampleObjectDrawerBinaryTree implements FSObjectDrawer {
int fanout;
int angle;

@Override
public void drawObject(Element e, double x, double y, final double width, final double height,
OutputDevice outputDevice, RenderingContext ctx, final int dotsPerPixel) {
final int depth = Integer.parseInt(e.getAttribute("data-depth"));
fanout = Integer.parseInt(e.getAttribute("data-fanout"));
angle = Integer.parseInt(e.getAttribute("data-angle"));

outputDevice.drawWithGraphics((float) x, (float) y, (float) width / dotsPerPixel,
(float) height / dotsPerPixel, new OutputDeviceGraphicsDrawer() {
@Override
public void render(Graphics2D graphics2D) {
double realWidth = width / dotsPerPixel;
double realHeight = height / dotsPerPixel;

renderTree(graphics2D, realWidth / 2f, realHeight, realHeight / depth, -90, depth);
}
});
}

private void renderTree(Graphics2D gfx, double x, double y, double len, double angleDeg, int depth) {
double rad = angleDeg * Math.PI / 180f;
double xTarget = x + Math.cos(rad) * len;
double yTarget = y + Math.sin(rad) * len;
gfx.setStroke(new BasicStroke(2f));
gfx.setColor(new Color(255 / depth, 128, 128));
gfx.draw(new Line2D.Double(x, y, xTarget, yTarget));

if (depth > 1) {
double childAngle = angleDeg - (((fanout - 1) * angle) / 2f);
for (int i = 0; i < fanout; i++) {
renderTree(gfx, xTarget, yTarget, len * 0.95, childAngle, depth - 1);
childAngle += angle;
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<html>
<head>
<style>
object {
border: 2px solid black;
}
</style>
</head>
<body>

<h1>Some binary trees</h1>

<object type="custom/binary-tree" data-fanout="3" data-depth="5" data-angle="20"
style="width:200px; height:300px;">
<!-- This is a simple example for a custom object drawer -->
</object>

<object type="custom/binary-tree" data-fanout="2" data-depth="10" data-angle="30"
style="width:400px; height:300px;">
<!-- This is a simple example for a custom object drawer -->
</object>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.openhtmltopdf.pdfboxout;

import com.openhtmltopdf.extend.FSObjectDrawer;
import com.openhtmltopdf.layout.LayoutContext;
import com.openhtmltopdf.render.BlockBox;
import com.openhtmltopdf.render.RenderingContext;
import org.w3c.dom.Element;

import java.awt.*;

/**
* FSObjectDrawer Element for PDFBox
*/
public class PdfBoxObjectDrawerReplacedElement implements PdfBoxReplacedElement {
private final Element e;
private Point point = new Point(0, 0);
private final FSObjectDrawer drawer;
private final int width;
private final int height;
private final int dotsPerPixel;

public PdfBoxObjectDrawerReplacedElement(Element e, FSObjectDrawer drawer, int cssWidth, int cssHeight,
int dotsPerPixel) {
this.e = e;
this.drawer = drawer;
this.width = cssWidth;
this.height = cssHeight;
this.dotsPerPixel = dotsPerPixel;
}

@Override
public int getIntrinsicWidth() {
return this.width;
}

@Override
public int getIntrinsicHeight() {
return this.height;
}

@Override
public Point getLocation() {
return point;
}

@Override
public void setLocation(int x, int y) {
point.setLocation(x, y);
}

@Override
public void detach(LayoutContext c) {
}

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

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

@Override
public int getBaseline() {
return 0;
}

@Override
public void paint(RenderingContext c, PdfBoxOutputDevice outputDevice, BlockBox box) {
drawer.drawObject(e, point.getX(), point.getY(), getIntrinsicWidth(), getIntrinsicHeight(), outputDevice, c, dotsPerPixel);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,50 +19,14 @@
*/
package com.openhtmltopdf.pdfboxout;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Calendar;
import java.util.List;
import java.util.regex.Pattern;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.encryption.PDEncryption;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import com.openhtmltopdf.bidi.BidiReorderer;
import com.openhtmltopdf.bidi.BidiSplitter;
import com.openhtmltopdf.bidi.BidiSplitterFactory;
import com.openhtmltopdf.bidi.SimpleBidiReorderer;
import com.openhtmltopdf.context.StyleReference;
import com.openhtmltopdf.css.style.CalculatedStyle;
import com.openhtmltopdf.extend.FSCache;
import com.openhtmltopdf.extend.FSTextBreaker;
import com.openhtmltopdf.extend.FSTextTransformer;
import com.openhtmltopdf.extend.FSObjectDrawerFactory;
import com.openhtmltopdf.extend.FSUriResolver;
import com.openhtmltopdf.extend.HttpStreamFactory;
import com.openhtmltopdf.extend.NamespaceHandler;
Expand All @@ -83,6 +47,27 @@
import com.openhtmltopdf.simple.extend.XhtmlNamespaceHandler;
import com.openhtmltopdf.util.Configuration;
import com.openhtmltopdf.util.XRLog;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.encryption.PDEncryption;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.io.*;
import java.util.Calendar;
import java.util.List;
import java.util.regex.Pattern;

public class PdfBoxRenderer {
// See discussion of units at top of PdfBoxOutputDevice.
Expand Down Expand Up @@ -170,7 +155,7 @@ public PdfBoxRenderer(float dotsPerPoint, int dotsPerPixel, boolean useSubsets,
PdfBoxFontResolver fontResolver = new PdfBoxFontResolver(_sharedContext, _pdfDoc);
_sharedContext.setFontResolver(fontResolver);

PdfBoxReplacedElementFactory replacedElementFactory = new PdfBoxReplacedElementFactory(_outputDevice, svgImpl);
PdfBoxReplacedElementFactory replacedElementFactory = new PdfBoxReplacedElementFactory(_outputDevice, svgImpl, null);
_sharedContext.setReplacedElementFactory(replacedElementFactory);

_sharedContext.setTextRenderer(new PdfBoxTextRenderer());
Expand All @@ -186,11 +171,11 @@ public PdfBoxRenderer(float dotsPerPoint, int dotsPerPixel, boolean useSubsets,
PdfBoxRenderer(BaseDocument doc, UnicodeImplementation unicode,
HttpStreamFactory httpStreamFactory,
OutputStream os, FSUriResolver resolver, FSCache cache, SVGDrawer svgImpl,
PageDimensions pageSize, float pdfVersion, String replacementText, boolean testMode) {
PageDimensions pageSize, float pdfVersion, String replacementText, boolean testMode, FSObjectDrawerFactory objectDrawerFactory) {

_pdfDoc = new PDDocument();
_pdfDoc.setVersion(pdfVersion);

_svgImpl = svgImpl;
_dotsPerPoint = DEFAULT_DOTS_PER_POINT;
_testMode = testMode;
Expand Down Expand Up @@ -222,7 +207,7 @@ public PdfBoxRenderer(float dotsPerPoint, int dotsPerPixel, boolean useSubsets,
PdfBoxFontResolver fontResolver = new PdfBoxFontResolver(_sharedContext, _pdfDoc);
_sharedContext.setFontResolver(fontResolver);

PdfBoxReplacedElementFactory replacedElementFactory = new PdfBoxReplacedElementFactory(_outputDevice, svgImpl);
PdfBoxReplacedElementFactory replacedElementFactory = new PdfBoxReplacedElementFactory(_outputDevice, svgImpl, objectDrawerFactory);
_sharedContext.setReplacedElementFactory(replacedElementFactory);

_sharedContext.setTextRenderer(new PdfBoxTextRenderer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@
package com.openhtmltopdf.pdfboxout;

import com.openhtmltopdf.css.constants.CSSName;
import com.openhtmltopdf.extend.FSImage;
import com.openhtmltopdf.extend.ReplacedElement;
import com.openhtmltopdf.extend.ReplacedElementFactory;
import com.openhtmltopdf.extend.SVGDrawer;
import com.openhtmltopdf.extend.UserAgentCallback;
import com.openhtmltopdf.extend.*;
import com.openhtmltopdf.layout.LayoutContext;
import com.openhtmltopdf.render.BlockBox;
import com.openhtmltopdf.simple.extend.FormSubmissionListener;
Expand All @@ -33,10 +29,12 @@
public class PdfBoxReplacedElementFactory implements ReplacedElementFactory {
private final PdfBoxOutputDevice _outputDevice;
private final SVGDrawer _svgImpl;
private final FSObjectDrawerFactory _objectDrawerFactory;

public PdfBoxReplacedElementFactory(PdfBoxOutputDevice outputDevice, SVGDrawer svgImpl) {
public PdfBoxReplacedElementFactory(PdfBoxOutputDevice outputDevice, SVGDrawer svgImpl, FSObjectDrawerFactory objectDrawerFactory) {
_outputDevice = outputDevice;
_svgImpl = svgImpl;
_objectDrawerFactory = objectDrawerFactory;
}

public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box,
Expand Down Expand Up @@ -122,6 +120,11 @@ public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box,
result.setAnchorName(name);
}
return result;
} else if (nodeName.equals("object") && _objectDrawerFactory != null) {
FSObjectDrawer drawer = _objectDrawerFactory.createDrawer(e);
if (drawer != null)
return new PdfBoxObjectDrawerReplacedElement(e, drawer, cssWidth, cssHeight,
c.getSharedContext().getDotsPerPixel());
}

return null;
Expand Down
Loading

0 comments on commit c046c8e

Please sign in to comment.