Skip to content

Commit

Permalink
#23: PDFBox-Graphics2D based SVG rendering (#66)
Browse files Browse the repository at this point in the history
* #23: Allow drawing on the output device using a Graphics2D. This implements
the infrastructure, but does not yet use it to render SVGs.

* #23: Use pdfbox-graphics2d to render the SVG. But the positioning is not
right yet.
  • Loading branch information
rototor authored and danfickle committed Feb 11, 2017
1 parent a51e4dd commit 3ffb084
Show file tree
Hide file tree
Showing 14 changed files with 339 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,9 @@ public void paintBackground(

public boolean isSupportsCMYKColors();

/**
* Draw something using a Graphics2D at the given rectangle.
*/
public void drawWithGraphics(float x, float y, float width, float height, OutputDeviceGraphicsDrawer renderer);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.openhtmltopdf.extend;

import java.awt.*;

/**
* Render something on a Graphics2D on the OutputDevice.
*
* @FunctionalInterface
*/
public interface OutputDeviceGraphicsDrawer {

/**
* Draw something using the given graphics. For PDFs it will be converted to vector drawings.
* @param graphics2D the graphics you can use to draw
*/
public void render(Graphics2D graphics2D);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
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;
import org.w3c.dom.Element;

import java.util.List;

public interface SVGDrawer {
public void drawSVG(Element svgElement, OutputDevice outputDevice, RenderingContext ctx, double x, double y, float dotsPerInch);
public void drawSVG(Element svgElement, OutputDevice outputDevice, RenderingContext ctx, double x, double y, double width, double height, double dotsPerPixel);

public void importFontFaceRules(List<FontFaceRule> fontFaces, SharedContext shared);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@

import com.openhtmltopdf.css.parser.FSColor;
import com.openhtmltopdf.css.parser.FSRGBColor;
import com.openhtmltopdf.extend.FSGlyphVector;
import com.openhtmltopdf.extend.FSImage;
import com.openhtmltopdf.extend.OutputDevice;
import com.openhtmltopdf.extend.ReplacedElement;
import com.openhtmltopdf.extend.*;
import com.openhtmltopdf.render.*;

import javax.swing.*;
Expand Down Expand Up @@ -280,7 +277,14 @@ public boolean isSupportsCMYKColors() {
return true;
}

private Stack<AffineTransform> transformStack = new Stack<AffineTransform>();
@Override
public void drawWithGraphics(float x, float y, float width, float height, OutputDeviceGraphicsDrawer renderer) {
Graphics2D graphics = (Graphics2D) _graphics.create((int) x, (int) y, (int) width, (int) height);
renderer.render(graphics);
graphics.dispose();
}

private Stack<AffineTransform> transformStack = new Stack<AffineTransform>();
private Stack<Shape> clipStack= new Stack<Shape>();

@Override
Expand Down
5 changes: 5 additions & 0 deletions openhtmltopdf-examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
<artifactId>openhtmltopdf-rtl-support</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-svg-support</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import com.openhtmltopdf.bidi.support.ICUBidiSplitter;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder.TextDirection;
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;

Expand Down Expand Up @@ -50,6 +50,11 @@ public static void main(String[] args) throws Exception {
runTestCase("font-family-built-in");
runTestCase("form-controls");

/*
* SVG samples
*/
runTestCase("svg-inline");

/* Add additional test cases here. */
}

Expand Down Expand Up @@ -105,43 +110,38 @@ public void log(String where, Level level, String msg) {
delegate.log(where, level, msg);
}
});


renderPDF(html, outputStream);

if (!warnings.isEmpty() && !allowWarnings) {
throw warnings.get(0);
}
}

private static void renderPDF(String html, OutputStream outputStream) throws Exception {
try {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useUnicodeBidiSplitter(new ICUBidiSplitter.ICUBidiSplitterFactory());
builder.useUnicodeBidiReorderer(new ICUBidiReorderer());
builder.defaultTextDirection(TextDirection.LTR);
builder.useSVGDrawer(new BatikSVGDrawer());
builder.withHtmlContent(html, TestcaseRunner.class.getResource("/testcases/").toString());
builder.toStream(outputStream);
builder.run();
} finally {
outputStream.close();
}

if (!warnings.isEmpty() && !allowWarnings) {
throw warnings.get(0);
}
}

public static void runTestCase(String testCaseFile) throws Exception {
byte[] htmlBytes = IOUtils.toByteArray(TestcaseRunner.class
.getResourceAsStream("/testcases/" + testCaseFile + ".html"));
String html = new String(htmlBytes, Charsets.UTF_8);
String outDir = System.getProperty("OUT_DIRECTORY", ".");
String testCaseOutputFile = outDir + "/" + testCaseFile + ".pdf";
FileOutputStream outputStream = new FileOutputStream(testCaseOutputFile);

try {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useUnicodeBidiSplitter(new ICUBidiSplitter.ICUBidiSplitterFactory());
builder.useUnicodeBidiReorderer(new ICUBidiReorderer());
builder.defaultTextDirection(TextDirection.LTR);
builder.withHtmlContent(html, TestcaseRunner.class.getResource("/testcases/").toString());
builder.toStream(outputStream);
builder.run();
} finally {
outputStream.close();
}

renderPDF(html, outputStream);
System.out.println("Wrote " + testCaseOutputFile);
}
}
143 changes: 143 additions & 0 deletions openhtmltopdf-examples/src/main/resources/testcases/svg-inline.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<html>
<head>
<style>
svg {
border: 2px solid black;
}
</style>
</head>
<body>

<h1>Some inline SVG examples</h1>

Some examples how inline SVG is rendered.

<h2>JSON-Symbol</h2>

<!-- Taken from https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/ -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 102" width="200" height="200">
<radialGradient id="jsongrad" cx="65" cy="90" r="100" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#EEF"/><stop offset="1"/></radialGradient>
<path d="M61,02 A 49,49 0,0,0 39,98 C 9,79 10,24 45,25 C 72,24 65,75 50,75 C 93,79 91,21 62,02" id="jsonswirl" fill="url(#jsongrad)"/>
<use xlink:href="#jsonswirl" transform="rotate(180 50,50)"/>
</svg>

<h2>Barchart</h2>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- ========================================================================= -->
<!-- Illustrates how SVG can be used for high quality graphs. -->
<!-- -->
<!-- @author vincent.hardy@eng.sun.com -->
<!-- @author neeme.praks@one.lv -->
<!-- @version $Id$ -->
<!-- ========================================================================= -->

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="body" width="450" height="500" xml:space="preserve" viewBox="0 0 450 500">
<title>Bar Chart</title>

<g id="barChart" transform="translate(40, 100)" fill-rule="evenodd" clip-rule="evenodd" stroke="none" class="legend"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" style="text-anchor:start">

<g id="GridAndLegend" style="stroke:none;">
<g stroke="black">

<!-- "floor" and "wall" -->
<path fill="lightgray" stroke="darkgray" d="M 27,240 l 15,-15 v -224 l -15,15" />
<path fill="lightgray" stroke="darkgray" d="M 41,225 v -224 h 316 v 224" />
<path fill="darkgray" stroke="none" d="M 27,240 l 15,-15 h 316 l -15,15" />

<!-- axis lines -->
<path d="M 27,240 h 316"/>
<path d="M 27,240 v -224"/>

<!-- value axis major gridlines -->
<g style="fill:none;">
<path d="M 27,202 l 15,-15 h 316" />
<path d="M 27,165 l 15,-15 h 316" />
<path d="M 27,127 l 15,-15 h 316" />
<path d="M 27, 90 l 15,-15 h 316" />
<path d="M 27, 53 l 15,-15 h 316" />
</g>

<!-- category axis major ticks -->
<path d="M 27,245 v -5"/>
<path d="M 106,245 v -5"/>
<path d="M 185,245 v -5"/>
<path d="M 264,245 v -5"/>

<!-- value axis minor ticks -->
<path d="M 22,240 h 5"/>
<path d="M 22,202 h 5"/>
<path d="M 22,165 h 5"/>
<path d="M 22,127 h 5"/>
<path d="M 22, 90 h 5"/>
<path d="M 22, 53 h 5"/>
<path d="M 22, 15 h 5"/>
</g>

<text transform="matrix(1 0 0 1 54 256)">Shoe</text>
<text transform="matrix(1 0 0 1 142 256)">Car</text>
<text transform="matrix(1 0 0 1 211 256)">Travel</text>
<text transform="matrix(1 0 0 1 285 256)">Computer</text>

<text transform="matrix(1 0 0 1 13 247)"><tspan x="0" y="0">0</tspan></text>
<text transform="matrix(1 0 0 1 6 209)"><tspan x="0" y="0">10</tspan></text>
<text transform="matrix(1 0 0 1 6 171)"><tspan x="0" y="0">20</tspan></text>
<text transform="matrix(1 0 0 1 6 134)"><tspan x="0" y="0">30</tspan></text>
<text transform="matrix(1 0 0 1 6 96)"><tspan x="0" y="0">40</tspan></text>
<text transform="matrix(1 0 0 1 6 60)"><tspan x="0" y="0">50</tspan></text>
<text transform="matrix(1 0 0 1 6 22)"><tspan x="0" y="0">60</tspan></text>
</g>

<g id="ShoeBar">
<path style="fill:#8686E0;" d="M 86,240 v -37 l 15 -15 v 37 l -15,15 z"/>
<path style="fill:#5B5B97;" d="M 86,203 h -39 l 15 -15 h 39 l -15,15 z"/>
<path style="fill:#7575C3;" d="M 47,203 v 37 h 39 v -37 H 47 z"/>
</g>
<g id="CarBar">
<path style="fill:#8686E0;" d="M 165,240 v -74 l 15 -15 v 74 l -15,15 z"/>
<path style="fill:#5B5B97;" d="M 165,166 h -39 l 15 -15 h 39 l -15,15 z"/>
<path style="fill:#7575C3;" d="M 126,166 v 74 h 39 v -74 h -39 z"/>
</g>
<g id="TravelBar">
<path style="fill:#8686E0;" d="M 244,240 v -37 l 15 -15 v 37 l -15,15 z"/>
<path style="fill:#5B5B97;" d="M 244,203 h -39 l 15 -15 h 39 l -15,15 z"/>
<path style="fill:#7575C3;" d="M 205,203 v 37 h 39 v -37 h -39 z"/>
</g>
<g id="ComputerBar">
<path style="fill:#8686E0;" d="M 323,240 v -224 l 15 -15 v 224 l -15,15 z"/>
<path style="fill:#5B5B97;" d="M 323, 16 h -39 l 15 -15 h 39 l -15,15 z"/>
<path style="fill:#7575C3;" d="M 284, 16 v 224 h 39 v -224 h -39 z"/>
</g>

</g>

<!-- ============================================================= -->
<!-- Batik sample mark -->
<!-- ============================================================= -->
<!--
<use xlink:href="batikLogo.svg#Batik_Tag_Box" />
-->

</svg>

<b>End of SVGs</b>

</body>
</html>
5 changes: 5 additions & 0 deletions openhtmltopdf-pdfbox/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
<artifactId>openhtmltopdf-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>de.rototor.pdfbox</groupId>
<artifactId>graphics2d</artifactId>
<version>0.1</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@
import com.openhtmltopdf.css.style.CssContext;
import com.openhtmltopdf.extend.FSImage;
import com.openhtmltopdf.extend.OutputDevice;
import com.openhtmltopdf.extend.OutputDeviceGraphicsDrawer;
import com.openhtmltopdf.layout.SharedContext;
import com.openhtmltopdf.pdfboxout.PdfBoxFontResolver.FontDescription;
import com.openhtmltopdf.pdfboxout.PdfBoxForm.CheckboxStyle;
import com.openhtmltopdf.render.*;
import com.openhtmltopdf.util.Configuration;
import com.openhtmltopdf.util.XRLog;

import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
Expand All @@ -59,7 +60,6 @@
import org.w3c.dom.Node;

import javax.imageio.ImageIO;

import java.awt.*;
import java.awt.RenderingHints.Key;
import java.awt.geom.*;
Expand Down Expand Up @@ -1315,6 +1315,33 @@ public boolean isSupportsCMYKColors() {
return true;
}

@Override
public void drawWithGraphics(float x, float y, float width, float height, OutputDeviceGraphicsDrawer renderer) {
try {
PdfBoxGraphics2D pdfBoxGraphics2D = new PdfBoxGraphics2D(_writer, (int) width, (int) height);
/*
* We *could* customize the PDF mapping here. But for now the default is enough.
*/

/*
* Do rendering
*/
renderer.render(pdfBoxGraphics2D);
/*
* Dispose to close the XStream
*/
pdfBoxGraphics2D.dispose();

/*
* And then stamp it
*/
_cp.placeXForm(x,y,pdfBoxGraphics2D.getXFormObject());
}
catch(IOException e){
throw new RuntimeException("Error while drawing on Graphics2D", e);
}
}

public List findPagePositionsByID(CssContext c, Pattern pattern) {
Map idMap = _sharedContext.getIdMap();
if (idMap == null) {
Expand Down
Loading

0 comments on commit 3ffb084

Please sign in to comment.