diff --git a/generator/src/main/java/net/codecrete/qrbill/generator/BillLayout.java b/generator/src/main/java/net/codecrete/qrbill/generator/BillLayout.java index e826191..4d3a7d3 100644 --- a/generator/src/main/java/net/codecrete/qrbill/generator/BillLayout.java +++ b/generator/src/main/java/net/codecrete/qrbill/generator/BillLayout.java @@ -62,6 +62,7 @@ class BillLayout { private double textAscender; private double lineSpacing; private double extraSpacing; + private final double paymentPartHoriOffset; BillLayout(Bill bill, Canvas graphics) { @@ -71,6 +72,7 @@ class BillLayout { this.formatter = new BillTextFormatter(bill, true); this.additionalLeftMargin = Math.min(Math.max(bill.getFormat().getMarginLeft(), 5.0), 12.0) - MARGIN; this.additionalRightMargin = Math.min(Math.max(bill.getFormat().getMarginRight(), 5.0), 12.0) - MARGIN; + this.paymentPartHoriOffset = bill.getFormat().getOutputSize() == OutputSize.PAYMENT_PART_ONLY ? 0 : RECEIPT_WIDTH; } void draw() throws IOException { @@ -97,6 +99,9 @@ void draw() throws IOException { } drawPaymentPart(); + if (bill.getFormat().getOutputSize() == OutputSize.PAYMENT_PART_ONLY) + return; + // receipt final int RC_LABEL_PREF_FONT_SIZE = 6; // pt @@ -128,12 +133,12 @@ private void drawPaymentPart() throws IOException { final double QR_CODE_BOTTOM = 42; // mm // title section - graphics.setTransformation(RECEIPT_WIDTH + MARGIN, 0, 0, 1, 1); + graphics.setTransformation(paymentPartHoriOffset + MARGIN, 0, 0, 1, 1); yPos = SLIP_HEIGHT - MARGIN - graphics.getAscender(FONT_SIZE_TITLE); graphics.putText(getText(MultilingualText.KEY_PAYMENT_PART), 0, yPos, FONT_SIZE_TITLE, true); // Swiss QR code section - qrCode.draw(graphics, RECEIPT_WIDTH + MARGIN, QR_CODE_BOTTOM); + qrCode.draw(graphics, paymentPartHoriOffset + MARGIN, QR_CODE_BOTTOM); // amount section drawPaymentPartAmountSection(); @@ -151,7 +156,7 @@ private void drawPaymentPartAmountSection() throws IOException { final double AMOUNT_BOX_WIDTH_PP = 40; // mm final double AMOUNT_BOX_HEIGHT_PP = 15; // mm - graphics.setTransformation(RECEIPT_WIDTH + MARGIN, 0, 0, 1, 1); + graphics.setTransformation(paymentPartHoriOffset + MARGIN, 0, 0, 1, 1); // currency double y = AMOUNT_SECTION_TOP - labelAscender; @@ -177,7 +182,7 @@ private void drawPaymentPartAmountSection() throws IOException { private void drawPaymentPartInformationSection() throws IOException { - graphics.setTransformation(SLIP_WIDTH - PP_INFO_SECTION_WIDTH - MARGIN, 0, 0, 1, 1); + graphics.setTransformation(paymentPartHoriOffset + PP_AMOUNT_SECTION_WIDTH + 2 * MARGIN, 0, 0, 1, 1); yPos = SLIP_HEIGHT - MARGIN - labelAscender; // account and creditor @@ -211,7 +216,7 @@ private void drawFurtherInformationSection() throws IOException { if (bill.getAlternativeSchemes() == null || bill.getAlternativeSchemes().length == 0) return; - graphics.setTransformation(RECEIPT_WIDTH + MARGIN, 0, 0, 1, 1); + graphics.setTransformation(paymentPartHoriOffset + MARGIN, 0, 0, 1, 1); double y = FURTHER_INFORMATION_SECTION_TOP - graphics.getAscender(FONT_SIZE); double maxWidth = PAYMENT_PART_WIDTH - 2 * MARGIN - additionalRightMargin; @@ -441,6 +446,7 @@ private void drawScissors(double x, double y, double angle) throws IOException { drawScissorsBlade(x, y, 3, angle, true); } + @SuppressWarnings("SameParameterValue") private void drawScissorsBlade(double x, double y, double size, double angle, boolean mirrored) throws IOException { double scale = size / 476.0; double xOffset = 0.36 * size; @@ -482,6 +488,7 @@ private void drawLabel(String labelKey) throws IOException { // Draws a label and a single line of text at (0, yPos) and advances vertically. // yPos is taken as the baseline for the text. + @SuppressWarnings("SameParameterValue") private void drawLabelAndText(String labelKey, String text) throws IOException { drawLabel(labelKey); graphics.putText(text, 0, yPos, textFontSize, false); @@ -550,6 +557,7 @@ private void drawCorners(double x, double y, double width, double height) throws graphics.strokePath(CORNER_STROKE_WIDTH, 0, Canvas.LineStyle.Solid, false); } + @SuppressWarnings("SameParameterValue") private String truncateText(String text, double maxWidth, int fontSize) { final double ELLIPSIS_WIDTH = 0.3528; // mm * font size diff --git a/generator/src/main/java/net/codecrete/qrbill/generator/OutputSize.java b/generator/src/main/java/net/codecrete/qrbill/generator/OutputSize.java index f1bc7b0..21d40c7 100644 --- a/generator/src/main/java/net/codecrete/qrbill/generator/OutputSize.java +++ b/generator/src/main/java/net/codecrete/qrbill/generator/OutputSize.java @@ -17,9 +17,9 @@ public enum OutputSize { */ A4_PORTRAIT_SHEET, /** - * QR bill only (about 105 by 210 mm). + * QR bill only (about 210 by 105 mm). *
- * This size is suitable if the QR bill has not horizontal line. + * This size is suitable if the QR bill has no horizontal line. * If the horizontal line is needed and the A4 sheet size is not * suitable, use {@link #QR_BILL_EXTRA_SPACE} instead. *
@@ -44,5 +44,14 @@ public enum OutputSize { * This format applies a white background (as opposed to a transparent one). * */ - QR_CODE_WITH_QUIET_ZONE + QR_CODE_WITH_QUIET_ZONE, + /** + * Payment part only (about 148 by 105 mm). + *+ * This size does not include separator lines. It is suitable for displaying the QR bill in online channels. + * See Implementation Guidelines QR Bill v2.3, ch. 3.8 Layout rules for the online use of the QR-bill + * for additional requirements when using this size. + *
+ */ + PAYMENT_PART_ONLY, } diff --git a/generator/src/main/java/net/codecrete/qrbill/generator/QRBill.java b/generator/src/main/java/net/codecrete/qrbill/generator/QRBill.java index f69c807..af82e57 100644 --- a/generator/src/main/java/net/codecrete/qrbill/generator/QRBill.java +++ b/generator/src/main/java/net/codecrete/qrbill/generator/QRBill.java @@ -90,6 +90,21 @@ public class QRBill { */ public static final double QR_CODE_WITH_QUIET_ZONE_HEIGHT = 56; + /** + * The width of the payment part, in mm + * + * @see OutputSize#PAYMENT_PART_ONLY + */ + public static final double PAYMENT_PART_WDITH = 148; + + /** + * The height of the payment part, in mm + * + * @see OutputSize#PAYMENT_PART_ONLY + */ + public static final double PAYMENT_PART_HEIGHT = 105; + + private QRBill() { // do not instantiate } @@ -292,6 +307,10 @@ private static Canvas createCanvas(Bill bill) throws IOException { drawingWidth = QR_BILL_WITH_HORI_LINE_WIDTH; drawingHeight = QR_BILL_WITH_HORI_LINE_HEIGHT; break; + case PAYMENT_PART_ONLY: + drawingWidth = PAYMENT_PART_WDITH; + drawingHeight = PAYMENT_PART_HEIGHT; + break; case QR_CODE_ONLY: drawingWidth = QR_CODE_WIDTH; drawingHeight = QR_CODE_HEIGHT; diff --git a/generator/src/test/java/net/codecrete/qrbill/generator/PaymentPartTest.java b/generator/src/test/java/net/codecrete/qrbill/generator/PaymentPartTest.java new file mode 100644 index 0000000..ac20f48 --- /dev/null +++ b/generator/src/test/java/net/codecrete/qrbill/generator/PaymentPartTest.java @@ -0,0 +1,49 @@ +// +// Swiss QR Bill Generator +// Copyright (c) 2024 Manuel Bleichenbacher +// Licensed under MIT License +// https://opensource.org/licenses/MIT +// +package net.codecrete.qrbill.generator; + +import net.codecrete.qrbill.testhelper.FileComparison; +import net.codecrete.qrbill.testhelper.SampleData; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for generation of payment part only (PDF, PNG and SVG) + *+ * Resulting output is compared byte by byte. + *
+ */ +@DisplayName("Generation of payment part only") +class PaymentPartTest { + + @Test + void createQRBill1() { + Bill bill = SampleData.getExample1(); + bill.getFormat().setOutputSize(OutputSize.PAYMENT_PART_ONLY); + bill.getFormat().setGraphicsFormat(GraphicsFormat.SVG); + byte[] svg = QRBill.generate(bill); + FileComparison.assertFileContentsEqual(svg, "payment_part1.svg"); + } + + @Test + void createQRBill2() { + Bill bill = SampleData.getExample2(); + bill.getFormat().setOutputSize(OutputSize.PAYMENT_PART_ONLY); + bill.getFormat().setGraphicsFormat(GraphicsFormat.PDF); + byte[] pdf = QRBill.generate(bill); + FileComparison.assertFileContentsEqual(pdf, "payment_part2.pdf"); + } + + @Test + void createQRBill3() { + Bill bill = SampleData.getExample3(); + bill.getFormat().setOutputSize(OutputSize.PAYMENT_PART_ONLY); + bill.getFormat().setGraphicsFormat(GraphicsFormat.PNG); + byte[] png = QRBill.generate(bill); + FileComparison.assertFileContentsEqual(png, "payment_part2.png"); + } +} diff --git a/generator/src/test/resources/payment_part1.svg b/generator/src/test/resources/payment_part1.svg new file mode 100644 index 0000000..be6474f --- /dev/null +++ b/generator/src/test/resources/payment_part1.svg @@ -0,0 +1,259 @@ + + + diff --git a/generator/src/test/resources/payment_part2.pdf b/generator/src/test/resources/payment_part2.pdf new file mode 100644 index 0000000..afaa0a6 Binary files /dev/null and b/generator/src/test/resources/payment_part2.pdf differ diff --git a/generator/src/test/resources/payment_part2.png b/generator/src/test/resources/payment_part2.png new file mode 100644 index 0000000..d10feee Binary files /dev/null and b/generator/src/test/resources/payment_part2.png differ