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; + } +}