diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java
index cfc4892d4..24c935959 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java
@@ -220,6 +220,7 @@ enum LogMessageId2Param implements LogMessageId {
"algorithm for break-word! Start-substring=[{}], end={}"),
GENERAL_EXPECTING_BOX_CHILDREN_OF_TYPE_BUT_GOT(XRLog.GENERAL, "Expecting box children to be of type ({}) but got ({})."),
GENERAL_PDF_FOUND_ELEMENT_WITHOUT_ATTRIBUTE_NAME(XRLog.GENERAL, "found a <{} {}> element without attribute name, the element will not work without this attribute"),
+ GENERAL_UNABLE_TO_PARSE_VALUE_AS(XRLog.GENERAL, "Unable to parse value '{}' as {}"),
EXCEPTION_CANT_OPEN_STREAM_FOR_URI(XRLog.EXCEPTION, "Can't open stream for URI '{}': {}"),
EXCEPTION_SVG_EXTERNAL_RESOURCE_NOT_ALLOWED(XRLog.EXCEPTION, "Tried to fetch external resource from SVG. Refusing. Details: {}, {}"),
diff --git a/openhtmltopdf-examples/pom.xml b/openhtmltopdf-examples/pom.xml
index b1f68493c..e18d37da6 100644
--- a/openhtmltopdf-examples/pom.xml
+++ b/openhtmltopdf-examples/pom.xml
@@ -96,6 +96,11 @@
jfreechart
1.5.0
+
+ com.google.zxing
+ javase
+ 3.4.0
+
org.freemarker
freemarker
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/zxing-barcode-custom-color.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/zxing-barcode-custom-color.pdf
new file mode 100644
index 000000000..0735be569
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/zxing-barcode-custom-color.pdf differ
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/zxing-datamatrix-encode-hint.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/zxing-datamatrix-encode-hint.pdf
new file mode 100644
index 000000000..0b5065d0b
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/zxing-datamatrix-encode-hint.pdf differ
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/zxing-qrcode-default.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/zxing-qrcode-default.pdf
new file mode 100644
index 000000000..44d676f81
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/zxing-qrcode-default.pdf differ
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/zxing-barcode-custom-color.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/zxing-barcode-custom-color.html
new file mode 100644
index 000000000..33be0a47d
--- /dev/null
+++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/zxing-barcode-custom-color.html
@@ -0,0 +1,21 @@
+
+
+ a
+
+
+
+before
+
+after
+
+
\ No newline at end of file
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/zxing-datamatrix-encode-hint.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/zxing-datamatrix-encode-hint.html
new file mode 100644
index 000000000..3e78d9a0d
--- /dev/null
+++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/zxing-datamatrix-encode-hint.html
@@ -0,0 +1,16 @@
+
+
+ a
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/zxing-qrcode-default.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/zxing-qrcode-default.html
new file mode 100644
index 000000000..e56ccb286
--- /dev/null
+++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/zxing-qrcode-default.html
@@ -0,0 +1,15 @@
+
+
+ a
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java
index b4f4549de..ff80efb9a 100644
--- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java
+++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java
@@ -8,6 +8,7 @@
import java.util.Collections;
import com.openhtmltopdf.extend.FSStream;
+import com.openhtmltopdf.objects.zxing.ZXingObjectDrawer;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -1216,6 +1217,21 @@ public void testIssue474NpeImageDecoding() throws IOException {
assertTrue(vt.runTest("issue-474-npe-image-decoding"));
}
+ @Test
+ public void testBarcode() throws IOException {
+
+ VisualTester.BuilderConfig c = builder -> {
+ DefaultObjectDrawerFactory factory = new DefaultObjectDrawerFactory();
+ factory.registerDrawer("image/barcode", new ZXingObjectDrawer());
+ builder.useObjectDrawerFactory(factory);
+ };
+
+ assertTrue(vt.runTest("zxing-qrcode-default", c));
+ assertTrue(vt.runTest("zxing-barcode-custom-color", c));
+ assertTrue(vt.runTest("zxing-datamatrix-encode-hint", c));
+
+ }
+
// TODO:
// + Elements that appear just on generated overflow pages.
// + content property (page counters, etc)
diff --git a/openhtmltopdf-objects/pom.xml b/openhtmltopdf-objects/pom.xml
index dbfca818c..dd4c38582 100644
--- a/openhtmltopdf-objects/pom.xml
+++ b/openhtmltopdf-objects/pom.xml
@@ -60,6 +60,12 @@
1.5.0
true
+
+ com.google.zxing
+ javase
+ 3.4.0
+ true
+
diff --git a/openhtmltopdf-objects/src/main/java/com/openhtmltopdf/objects/zxing/ZXingObjectDrawer.java b/openhtmltopdf-objects/src/main/java/com/openhtmltopdf/objects/zxing/ZXingObjectDrawer.java
new file mode 100644
index 000000000..8523413c7
--- /dev/null
+++ b/openhtmltopdf-objects/src/main/java/com/openhtmltopdf/objects/zxing/ZXingObjectDrawer.java
@@ -0,0 +1,117 @@
+package com.openhtmltopdf.objects.zxing;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.client.j2se.MatrixToImageConfig;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.datamatrix.encoder.SymbolShapeHint;
+import com.google.zxing.pdf417.encoder.Dimensions;
+import com.openhtmltopdf.extend.FSObjectDrawer;
+import com.openhtmltopdf.extend.OutputDevice;
+import com.openhtmltopdf.render.RenderingContext;
+import com.openhtmltopdf.util.LogMessageId;
+import com.openhtmltopdf.util.XRLog;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.awt.*;
+import java.util.*;
+import java.util.logging.Level;
+
+public class ZXingObjectDrawer implements FSObjectDrawer {
+
+
+ private static Object handleValueForHint(EncodeHintType type, String value) {
+ switch (type) {
+ case DATA_MATRIX_SHAPE:
+ return safeSymbolShapeHint(value);
+ case PDF417_DIMENSIONS: {
+ try {
+ int[] dim = Arrays.stream(value.trim().split(",")).mapToInt(Integer::parseInt).toArray();
+ if (dim.length == 4) {
+ return new Dimensions(dim[0], dim[1], dim[2], dim[3]);
+ }
+ } catch (NumberFormatException nfe) {
+ }
+ XRLog.log(Level.WARNING, LogMessageId.LogMessageId2Param.GENERAL_UNABLE_TO_PARSE_VALUE_AS, value, Dimensions.class.getCanonicalName());
+ return null;
+ }
+ default:
+ return value;
+ }
+ }
+
+ private static SymbolShapeHint safeSymbolShapeHint(String value) {
+ try {
+ return SymbolShapeHint.valueOf(value);
+ } catch (IllegalArgumentException e) {
+ XRLog.log(Level.WARNING, LogMessageId.LogMessageId2Param.GENERAL_UNABLE_TO_PARSE_VALUE_AS, value, SymbolShapeHint.class.getCanonicalName(), e);
+ return null;
+ }
+ }
+
+ private static EncodeHintType safeEncodeHintTypeValueOf(String value) {
+ try {
+ return EncodeHintType.valueOf(value);
+ } catch (IllegalArgumentException e) {
+ XRLog.log(Level.WARNING, LogMessageId.LogMessageId2Param.GENERAL_UNABLE_TO_PARSE_VALUE_AS, value, EncodeHintType.class.getCanonicalName(), e);
+ return null;
+ }
+ }
+
+ private static int parseInt(String value, int defaultColor) {
+ try {
+ return Long.decode(value.toLowerCase(Locale.ROOT)).intValue();
+ } catch (NumberFormatException nfe) {
+ XRLog.log(Level.WARNING, LogMessageId.LogMessageId2Param.GENERAL_UNABLE_TO_PARSE_VALUE_AS, value, "integer", nfe);
+ return defaultColor;
+ }
+ }
+
+ @Override
+ public Map drawObject(Element e, double x, double y, double width, double height, OutputDevice outputDevice, RenderingContext ctx, int dotsPerPixel) {
+ MultiFormatWriter mfw = new MultiFormatWriter();
+ int onColor = e.hasAttribute("on-color") ? parseInt(e.getAttribute("on-color"), MatrixToImageConfig.BLACK) : MatrixToImageConfig.BLACK;
+ int offColor = e.hasAttribute("off-color") ? parseInt(e.getAttribute("off-color"), MatrixToImageConfig.WHITE) : MatrixToImageConfig.WHITE;
+
+ Map encodeHints = new EnumMap<>(EncodeHintType.class);
+ encodeHints.put(EncodeHintType.MARGIN, 0); // default
+ NodeList childNodes = e.getChildNodes();
+ int childNodesCount = childNodes.getLength();
+ for (int i = 0; i < childNodesCount; i++) {
+ Node n = childNodes.item(i);
+ if (!(n instanceof Element))
+ continue;
+ Element eChild = (Element) n;
+ if (!"encode-hint".equals(eChild.getTagName())) {
+ continue;
+ }
+ EncodeHintType encodeHintType = safeEncodeHintTypeValueOf(eChild.getAttribute("name"));
+ Object value = encodeHintType != null ? handleValueForHint(encodeHintType, eChild.getAttribute("value")) : null;
+ if (encodeHintType != null && value != null) {
+ encodeHints.put(encodeHintType, value);
+ }
+ }
+
+ String value = e.getAttribute("value");
+ BarcodeFormat barcodeFormat = e.hasAttribute("format") ? BarcodeFormat.valueOf(e.getAttribute("format")) : BarcodeFormat.QR_CODE;
+
+ int finalWidth = (int) (width/dotsPerPixel);
+ int finalHeight = (int) (height/dotsPerPixel);
+ try {
+ BitMatrix bitMatrix = mfw.encode(value, barcodeFormat, finalWidth, finalHeight, encodeHints);
+
+ outputDevice.drawWithGraphics((float) x, (float) y, (float) width, (float) height, graphics2D -> {
+ //generating a vector from the bitmatrix don't seems to be straightforward, thus a bitmap image...
+ graphics2D.drawImage(MatrixToImageWriter.toBufferedImage(bitMatrix, new MatrixToImageConfig(onColor, offColor)), 0, 0, finalWidth, finalHeight, null);
+ });
+ } catch (WriterException we) {
+ XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.GENERAL_MESSAGE, "Error while generating the barcode", we);
+ }
+ return null;
+ }
+}