|
|
@@ -0,0 +1,158 @@
|
|
|
+package net.sf.jasperreports.components.barcode4j;
|
|
|
+
|
|
|
+import java.awt.Color;
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+import javax.xml.transform.Result;
|
|
|
+import javax.xml.transform.Source;
|
|
|
+import javax.xml.transform.Transformer;
|
|
|
+import javax.xml.transform.TransformerException;
|
|
|
+import javax.xml.transform.TransformerFactory;
|
|
|
+import javax.xml.transform.dom.DOMSource;
|
|
|
+import javax.xml.transform.stream.StreamResult;
|
|
|
+
|
|
|
+import org.krysalis.barcode4j.output.BarcodeCanvasSetupException;
|
|
|
+import org.krysalis.barcode4j.output.svg.SVGCanvasProvider;
|
|
|
+import org.w3c.dom.Document;
|
|
|
+import org.w3c.dom.Element;
|
|
|
+
|
|
|
+import com.google.zxing.EncodeHintType;
|
|
|
+import com.google.zxing.WriterException;
|
|
|
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
|
|
+import com.google.zxing.qrcode.encoder.ByteMatrix;
|
|
|
+import com.google.zxing.qrcode.encoder.Encoder;
|
|
|
+import com.google.zxing.qrcode.encoder.QRCode;
|
|
|
+
|
|
|
+import net.sf.jasperreports.engine.JRComponentElement;
|
|
|
+import net.sf.jasperreports.engine.JRPropertiesUtil;
|
|
|
+import net.sf.jasperreports.engine.JRRuntimeException;
|
|
|
+import net.sf.jasperreports.engine.JRStyle;
|
|
|
+import net.sf.jasperreports.engine.JasperReportsContext;
|
|
|
+import net.sf.jasperreports.engine.util.JRColorUtil;
|
|
|
+import net.sf.jasperreports.renderers.Renderable;
|
|
|
+import net.sf.jasperreports.renderers.SimpleRenderToImageAwareDataRenderer;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author: guq
|
|
|
+ * @create: 2018-12-05 18:08
|
|
|
+ **/
|
|
|
+public class QRCodeSVGImageProducer implements QRCodeImageProducer {
|
|
|
+
|
|
|
+ public static final int DEFAULT_MARGIN = 4;//same as zxing's QRCodeWriter.QUIET_ZONE_SIZE
|
|
|
+ public static final String PROPERTY_DEFAULT_ENCODING = "utf-8";
|
|
|
+ @Override
|
|
|
+ public Renderable createImage(
|
|
|
+ JasperReportsContext jasperReportsContext,
|
|
|
+ JRComponentElement componentElement,
|
|
|
+ QRCodeBean qrCodeBean,
|
|
|
+ String message
|
|
|
+ )
|
|
|
+ {
|
|
|
+ Map<EncodeHintType,Object> hints = new HashMap<EncodeHintType,Object>();
|
|
|
+ hints.put(EncodeHintType.CHARACTER_SET, PROPERTY_DEFAULT_ENCODING);
|
|
|
+ ErrorCorrectionLevel errorCorrectionLevel = qrCodeBean.getErrorCorrectionLevel().getErrorCorrectionLevel();
|
|
|
+ hints.put(EncodeHintType.ERROR_CORRECTION, errorCorrectionLevel);
|
|
|
+
|
|
|
+ ByteMatrix matrix = null;
|
|
|
+ SVGCanvasProvider provider = null;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ QRCode qrCode = Encoder.encode(message, errorCorrectionLevel, hints);
|
|
|
+ matrix = qrCode.getMatrix();
|
|
|
+
|
|
|
+ provider = new SVGCanvasProvider(false, OrientationEnum.UP.getValue());
|
|
|
+ }
|
|
|
+ catch (WriterException e)
|
|
|
+ {
|
|
|
+ throw new JRRuntimeException(e);
|
|
|
+ }
|
|
|
+ catch (BarcodeCanvasSetupException e)
|
|
|
+ {
|
|
|
+ throw new JRRuntimeException(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ Document svgDoc = provider.getDOM();
|
|
|
+ Element svg = svgDoc.getDocumentElement();
|
|
|
+ int codeWidth = matrix.getWidth();
|
|
|
+ int codeHeight = matrix.getHeight();
|
|
|
+
|
|
|
+ JRStyle elementStyle = componentElement.getStyle();
|
|
|
+ int elementWidth = componentElement.getWidth() - (elementStyle == null ? 0
|
|
|
+ : (elementStyle.getLineBox().getLeftPadding() + elementStyle.getLineBox().getRightPadding()));
|
|
|
+ int elementHeight = componentElement.getHeight() - (elementStyle == null ? 0
|
|
|
+ : (elementStyle.getLineBox().getTopPadding() + elementStyle.getLineBox().getBottomPadding()));
|
|
|
+
|
|
|
+ int margin = qrCodeBean.getMargin() == null ? DEFAULT_MARGIN : qrCodeBean.getMargin();
|
|
|
+ int matrixWidth = codeWidth + 2 * margin;
|
|
|
+ int matrixHeight = codeHeight + 2 * margin;
|
|
|
+
|
|
|
+ // scaling to match the image size as closely as possible so that it looks good in html.
|
|
|
+ // the resolution is taken into account because the html exporter rasterizes to a png
|
|
|
+ // that has the same size as the svg.
|
|
|
+ int resolution = JRPropertiesUtil.getInstance(jasperReportsContext).getIntegerProperty(
|
|
|
+ componentElement, BarcodeRasterizedImageProducer.PROPERTY_RESOLUTION, 300);
|
|
|
+ int imageWidth = (int) Math.ceil(elementWidth * (resolution / 72d));
|
|
|
+ int imageHeight = (int) Math.ceil(elementHeight * (resolution / 72d));
|
|
|
+
|
|
|
+ double horizontalScale = ((double) imageWidth) / matrixWidth;
|
|
|
+ double verticalScale = ((double) imageHeight) / matrixHeight;
|
|
|
+
|
|
|
+ // we are scaling with integer units, not considering fractional coordinates for now
|
|
|
+ int scale = Math.max(1, (int) Math.min(Math.ceil(horizontalScale), Math.ceil(verticalScale)));
|
|
|
+ int qrWidth = scale * matrixWidth;
|
|
|
+ int qrHeight = scale * matrixHeight;
|
|
|
+
|
|
|
+ // scaling again because of the integer units
|
|
|
+ double qrScale = Math.max(1d, Math.max(((double) qrWidth) / imageWidth, ((double) qrHeight) / imageHeight));
|
|
|
+ int svgWidth = (int) Math.ceil(qrScale * imageWidth);
|
|
|
+ int svgHeight = (int) Math.ceil(qrScale * imageHeight);
|
|
|
+ svg.setAttribute("width", String.valueOf(svgWidth));
|
|
|
+ svg.setAttribute("height",String.valueOf(svgHeight));
|
|
|
+ svg.setAttribute("viewBox", "0 0 " + svgWidth + " " + svgHeight);
|
|
|
+
|
|
|
+ int xOffset = Math.max(0, (svgWidth - qrWidth) / 2);
|
|
|
+ int yOffset = Math.max(0, (svgHeight - qrHeight) / 2);
|
|
|
+
|
|
|
+ Color color = componentElement.getForecolor();
|
|
|
+ String fill = "#" + JRColorUtil.getColorHexa(color);
|
|
|
+ String fillOpacity = color.getAlpha() < 255 ? Float.toString(((float) color.getAlpha()) / 255) : null;
|
|
|
+ String rectangleSize = Integer.toString(scale);
|
|
|
+ for (int x = 0; x < codeWidth; x++) {
|
|
|
+ for (int y = 0; y < codeHeight; y++) {
|
|
|
+ if (matrix.get(x,y) == 1) {
|
|
|
+ Element element = svgDoc.createElementNS(svg.getNamespaceURI(), "rect");
|
|
|
+ element.setAttribute("x", String.valueOf(xOffset + scale * (x + margin)));
|
|
|
+ element.setAttribute("y", String.valueOf(yOffset + scale * (y + margin)));
|
|
|
+ element.setAttribute("width", rectangleSize);
|
|
|
+ element.setAttribute("height", rectangleSize);
|
|
|
+ element.setAttribute("fill", fill);
|
|
|
+ if (fillOpacity != null) {
|
|
|
+ element.setAttribute("fill-opacity", fillOpacity);
|
|
|
+ }
|
|
|
+ svgDoc.getFirstChild().appendChild(element);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Source source = new DOMSource(svgDoc);
|
|
|
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
+ Result output = new StreamResult(baos);
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ Transformer transformer = TransformerFactory.newInstance()
|
|
|
+ .newTransformer();
|
|
|
+ transformer.transform(source, output);
|
|
|
+
|
|
|
+ }
|
|
|
+ catch (TransformerException e)
|
|
|
+ {
|
|
|
+ throw new JRRuntimeException(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return SimpleRenderToImageAwareDataRenderer.getInstance(baos.toByteArray());
|
|
|
+ }
|
|
|
+
|
|
|
+}
|