Skip to content

Commit c76dbbf

Browse files
authored
Merge pull request #533 from syjer/barcode-extension
add zxing object drawer for barcode generation fix #532
2 parents 8f1bf01 + d5d14ca commit c76dbbf

File tree

11 files changed

+197
-0
lines changed

11 files changed

+197
-0
lines changed

openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ enum LogMessageId2Param implements LogMessageId {
209209
"algorithm for break-word! Start-substring=[{}], end={}"),
210210
GENERAL_EXPECTING_BOX_CHILDREN_OF_TYPE_BUT_GOT(XRLog.GENERAL, "Expecting box children to be of type ({}) but got ({})."),
211211
GENERAL_PDF_FOUND_ELEMENT_WITHOUT_ATTRIBUTE_NAME(XRLog.GENERAL, "found a <{} {}> element without attribute name, the element will not work without this attribute"),
212+
GENERAL_UNABLE_TO_PARSE_VALUE_AS(XRLog.GENERAL, "Unable to parse value '{}' as {}"),
212213

213214
EXCEPTION_SVG_EXTERNAL_RESOURCE_NOT_ALLOWED(XRLog.EXCEPTION, "Tried to fetch external resource from SVG. Refusing. Details: {}, {}"),
214215
EXCEPTION_DEFAULT_USERAGENT_IS_NOT_ABLE_TO_RESOLVE_URL_WITH_BASE_URL(XRLog.EXCEPTION, "The default NaiveUserAgent cannot resolve the URL {} with base URL {}");

openhtmltopdf-examples/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@
9696
<artifactId>jfreechart</artifactId>
9797
<version>1.5.0</version>
9898
</dependency>
99+
<dependency>
100+
<groupId>com.google.zxing</groupId>
101+
<artifactId>javase</artifactId>
102+
<version>3.4.0</version>
103+
</dependency>
99104
<dependency>
100105
<groupId>org.freemarker</groupId>
101106
<artifactId>freemarker</artifactId>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<html>
2+
<head>
3+
<title>a</title>
4+
<style>
5+
@page {
6+
size: 100px 80px;
7+
margin: 0;
8+
}
9+
</style>
10+
</head>
11+
<body>
12+
before
13+
<object type="image/barcode" style="width:50px;height:20px;"
14+
value="123"
15+
format="CODE_39"
16+
on-color="0xFF0000FF"
17+
off-color="0xFFFFFFFF">
18+
</object>
19+
after
20+
</body>
21+
</html>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<html>
2+
<head>
3+
<title>a</title>
4+
<style>
5+
@page {
6+
size: 120px 60px;
7+
margin: 0;
8+
}
9+
</style>
10+
</head>
11+
<body>
12+
<object type="image/barcode" style="width:100px;height:40px;border:1px solid red;" format="DATA_MATRIX" value="hello world">
13+
<encode-hint name="DATA_MATRIX_SHAPE" value="FORCE_RECTANGLE"></encode-hint>
14+
</object>
15+
</body>
16+
</html>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<html>
2+
<head>
3+
<title>a</title>
4+
<style>
5+
@page {
6+
size: 80px 80px;
7+
margin: 0;
8+
}
9+
</style>
10+
</head>
11+
<body>
12+
<object type="image/barcode" style="width:60px;height:60px;border:1px solid red;" value="hello world">
13+
</object>
14+
</body>
15+
</html>

openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.Collections;
99

1010
import com.openhtmltopdf.extend.FSStream;
11+
import com.openhtmltopdf.objects.zxing.ZXingObjectDrawer;
1112
import org.junit.Before;
1213
import org.junit.Ignore;
1314
import org.junit.Test;
@@ -1216,6 +1217,21 @@ public void testIssue474NpeImageDecoding() throws IOException {
12161217
assertTrue(vt.runTest("issue-474-npe-image-decoding"));
12171218
}
12181219

1220+
@Test
1221+
public void testBarcode() throws IOException {
1222+
1223+
VisualTester.BuilderConfig c = builder -> {
1224+
DefaultObjectDrawerFactory factory = new DefaultObjectDrawerFactory();
1225+
factory.registerDrawer("image/barcode", new ZXingObjectDrawer());
1226+
builder.useObjectDrawerFactory(factory);
1227+
};
1228+
1229+
assertTrue(vt.runTest("zxing-qrcode-default", c));
1230+
assertTrue(vt.runTest("zxing-barcode-custom-color", c));
1231+
assertTrue(vt.runTest("zxing-datamatrix-encode-hint", c));
1232+
1233+
}
1234+
12191235
// TODO:
12201236
// + Elements that appear just on generated overflow pages.
12211237
// + content property (page counters, etc)

openhtmltopdf-objects/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@
6060
<version>1.5.0</version>
6161
<optional>true</optional>
6262
</dependency>
63+
<dependency>
64+
<groupId>com.google.zxing</groupId>
65+
<artifactId>javase</artifactId>
66+
<version>3.4.0</version>
67+
<optional>true</optional>
68+
</dependency>
6369
</dependencies>
6470

6571
<build>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.openhtmltopdf.objects.zxing;
2+
3+
import com.google.zxing.BarcodeFormat;
4+
import com.google.zxing.EncodeHintType;
5+
import com.google.zxing.MultiFormatWriter;
6+
import com.google.zxing.WriterException;
7+
import com.google.zxing.client.j2se.MatrixToImageConfig;
8+
import com.google.zxing.client.j2se.MatrixToImageWriter;
9+
import com.google.zxing.common.BitMatrix;
10+
import com.google.zxing.datamatrix.encoder.SymbolShapeHint;
11+
import com.google.zxing.pdf417.encoder.Dimensions;
12+
import com.openhtmltopdf.extend.FSObjectDrawer;
13+
import com.openhtmltopdf.extend.OutputDevice;
14+
import com.openhtmltopdf.render.RenderingContext;
15+
import com.openhtmltopdf.util.LogMessageId;
16+
import com.openhtmltopdf.util.XRLog;
17+
import org.w3c.dom.Element;
18+
import org.w3c.dom.Node;
19+
import org.w3c.dom.NodeList;
20+
21+
import java.awt.*;
22+
import java.util.*;
23+
import java.util.logging.Level;
24+
25+
public class ZXingObjectDrawer implements FSObjectDrawer {
26+
27+
28+
private static Object handleValueForHint(EncodeHintType type, String value) {
29+
switch (type) {
30+
case DATA_MATRIX_SHAPE:
31+
return safeSymbolShapeHint(value);
32+
case PDF417_DIMENSIONS: {
33+
try {
34+
int[] dim = Arrays.stream(value.trim().split(",")).mapToInt(Integer::parseInt).toArray();
35+
if (dim.length == 4) {
36+
return new Dimensions(dim[0], dim[1], dim[2], dim[3]);
37+
}
38+
} catch (NumberFormatException nfe) {
39+
}
40+
XRLog.log(Level.WARNING, LogMessageId.LogMessageId2Param.GENERAL_UNABLE_TO_PARSE_VALUE_AS, value, Dimensions.class.getCanonicalName());
41+
return null;
42+
}
43+
default:
44+
return value;
45+
}
46+
}
47+
48+
private static SymbolShapeHint safeSymbolShapeHint(String value) {
49+
try {
50+
return SymbolShapeHint.valueOf(value);
51+
} catch (IllegalArgumentException e) {
52+
XRLog.log(Level.WARNING, LogMessageId.LogMessageId2Param.GENERAL_UNABLE_TO_PARSE_VALUE_AS, value, SymbolShapeHint.class.getCanonicalName(), e);
53+
return null;
54+
}
55+
}
56+
57+
private static EncodeHintType safeEncodeHintTypeValueOf(String value) {
58+
try {
59+
return EncodeHintType.valueOf(value);
60+
} catch (IllegalArgumentException e) {
61+
XRLog.log(Level.WARNING, LogMessageId.LogMessageId2Param.GENERAL_UNABLE_TO_PARSE_VALUE_AS, value, EncodeHintType.class.getCanonicalName(), e);
62+
return null;
63+
}
64+
}
65+
66+
private static int parseInt(String value, int defaultColor) {
67+
try {
68+
return Long.decode(value.toLowerCase(Locale.ROOT)).intValue();
69+
} catch (NumberFormatException nfe) {
70+
XRLog.log(Level.WARNING, LogMessageId.LogMessageId2Param.GENERAL_UNABLE_TO_PARSE_VALUE_AS, value, "integer", nfe);
71+
return defaultColor;
72+
}
73+
}
74+
75+
@Override
76+
public Map<Shape, String> drawObject(Element e, double x, double y, double width, double height, OutputDevice outputDevice, RenderingContext ctx, int dotsPerPixel) {
77+
MultiFormatWriter mfw = new MultiFormatWriter();
78+
int onColor = e.hasAttribute("on-color") ? parseInt(e.getAttribute("on-color"), MatrixToImageConfig.BLACK) : MatrixToImageConfig.BLACK;
79+
int offColor = e.hasAttribute("off-color") ? parseInt(e.getAttribute("off-color"), MatrixToImageConfig.WHITE) : MatrixToImageConfig.WHITE;
80+
81+
Map<EncodeHintType, Object> encodeHints = new EnumMap<>(EncodeHintType.class);
82+
encodeHints.put(EncodeHintType.MARGIN, 0); // default
83+
NodeList childNodes = e.getChildNodes();
84+
int childNodesCount = childNodes.getLength();
85+
for (int i = 0; i < childNodesCount; i++) {
86+
Node n = childNodes.item(i);
87+
if (!(n instanceof Element))
88+
continue;
89+
Element eChild = (Element) n;
90+
if (!"encode-hint".equals(eChild.getTagName())) {
91+
continue;
92+
}
93+
EncodeHintType encodeHintType = safeEncodeHintTypeValueOf(eChild.getAttribute("name"));
94+
Object value = encodeHintType != null ? handleValueForHint(encodeHintType, eChild.getAttribute("value")) : null;
95+
if (encodeHintType != null && value != null) {
96+
encodeHints.put(encodeHintType, value);
97+
}
98+
}
99+
100+
String value = e.getAttribute("value");
101+
BarcodeFormat barcodeFormat = e.hasAttribute("format") ? BarcodeFormat.valueOf(e.getAttribute("format")) : BarcodeFormat.QR_CODE;
102+
103+
int finalWidth = (int) (width/dotsPerPixel);
104+
int finalHeight = (int) (height/dotsPerPixel);
105+
try {
106+
BitMatrix bitMatrix = mfw.encode(value, barcodeFormat, finalWidth, finalHeight, encodeHints);
107+
108+
outputDevice.drawWithGraphics((float) x, (float) y, (float) width, (float) height, graphics2D -> {
109+
//generating a vector from the bitmatrix don't seems to be straightforward, thus a bitmap image...
110+
graphics2D.drawImage(MatrixToImageWriter.toBufferedImage(bitMatrix, new MatrixToImageConfig(onColor, offColor)), 0, 0, finalWidth, finalHeight, null);
111+
});
112+
} catch (WriterException we) {
113+
XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.GENERAL_MESSAGE, "Error while generating the barcode", we);
114+
}
115+
return null;
116+
}
117+
}

0 commit comments

Comments
 (0)