diff --git a/Core/BillLayout.cs b/Core/BillLayout.cs index da2376d..a4eb495 100644 --- a/Core/BillLayout.cs +++ b/Core/BillLayout.cs @@ -65,6 +65,7 @@ internal class BillLayout private double _textAscender; private double _lineSpacing; private double _extraSpacing; + private readonly double _paymentPartHoriOffset; internal BillLayout(Bill bill, ICanvas graphics) @@ -76,6 +77,7 @@ internal BillLayout(Bill bill, ICanvas graphics) _formatter = new BillTextFormatter(bill, true); _additionalLeftMargin = Math.Min(Math.Max(bill.Format.MarginLeft, 5.0), 12.0) - Margin; _additionalRightMargin = Math.Min(Math.Max(bill.Format.MarginRight, 5.0), 12.0) - Margin; + _paymentPartHoriOffset = bill.Format.OutputSize == OutputSize.PaymentPartOnly ? 0 : ReceiptWidth; } internal void Draw() @@ -106,6 +108,11 @@ internal void Draw() } DrawPaymentPart(); + if (_bill.Format.OutputSize == OutputSize.PaymentPartOnly) + { + return; + } + // receipt const int rcLabelPrefFontSize = 6; // pt @@ -139,12 +146,12 @@ private void DrawPaymentPart() const double qrCodeBottom = 42; // mm // title section - _graphics.SetTransformation(ReceiptWidth + Margin, 0, 0, 1, 1); + _graphics.SetTransformation(_paymentPartHoriOffset + Margin, 0, 0, 1, 1); _yPos = SlipHeight - Margin - _graphics.Ascender(FontSizeTitle); _graphics.PutText(GetText(MultilingualText.KeyPaymentPart), 0, _yPos, FontSizeTitle, true); // Swiss QR code section - _qrCode.Draw(_graphics, ReceiptWidth + Margin, qrCodeBottom); + _qrCode.Draw(_graphics, _paymentPartHoriOffset + Margin, qrCodeBottom); // amount section DrawPaymentPartAmountSection(); @@ -162,7 +169,7 @@ private void DrawPaymentPartAmountSection() const double amountBoxWidthPp = 40; // mm const double amountBoxHeightPp = 15; // mm - _graphics.SetTransformation(ReceiptWidth + Margin, 0, 0, 1, 1); + _graphics.SetTransformation(_paymentPartHoriOffset + Margin, 0, 0, 1, 1); // currency var y = AmountSectionTop - _labelAscender; @@ -191,7 +198,7 @@ private void DrawPaymentPartAmountSection() private void DrawPaymentPartInformationSection() { - _graphics.SetTransformation(SlipWidth - PpInfoSectionWidth - Margin, 0, 0, 1, 1); + _graphics.SetTransformation(_paymentPartHoriOffset + PpAmountSectionWidth + 2 * Margin, 0, 0, 1, 1); _yPos = SlipHeight - Margin - _labelAscender; // account and creditor @@ -234,7 +241,7 @@ private void DrawFurtherInformationSection() return; } - _graphics.SetTransformation(ReceiptWidth + Margin, 0, 0, 1, 1); + _graphics.SetTransformation(_paymentPartHoriOffset + Margin, 0, 0, 1, 1); var y = furtherInformationSectionTop - _graphics.Ascender(fontSize); var maxWidth = PaymentPartWidth - 2 * Margin - _additionalRightMargin; diff --git a/Core/OutputSize.cs b/Core/OutputSize.cs index e72c4c1..ecc323e 100644 --- a/Core/OutputSize.cs +++ b/Core/OutputSize.cs @@ -5,6 +5,8 @@ // https://opensource.org/licenses/MIT // +using System.Drawing; + namespace Codecrete.SwissQRBill.Generator { /// @@ -14,7 +16,7 @@ namespace Codecrete.SwissQRBill.Generator public enum OutputSize { /// - /// QR bill only (105 by 210 mm). + /// QR bill only (210 by 105 mm). /// /// 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 @@ -31,7 +33,7 @@ public enum OutputSize /// QrCodeOnly, /// - /// QR bill only with additional space at the top for the horizontal line (about 110 by 210 mm). + /// QR bill only with additional space at the top for the horizontal line (about 210 by 110 mm). /// /// The extra 5 mm at the top create space for the horizontal line and /// optionally for the scissors. @@ -44,6 +46,15 @@ public enum OutputSize /// This format applies a white background (as opposed to a transparent one). /// /// - QrCodeWithQuietZone + QrCodeWithQuietZone, + /// + /// 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. + /// + /// + PaymentPartOnly } } diff --git a/Core/Payments.cs b/Core/Payments.cs index af86fe3..29bae12 100644 --- a/Core/Payments.cs +++ b/Core/Payments.cs @@ -6,9 +6,7 @@ // using System; -using System.ComponentModel; using System.Text; -using static System.Net.Mime.MediaTypeNames; namespace Codecrete.SwissQRBill.Generator { diff --git a/Core/QRBill.cs b/Core/QRBill.cs index 20ffd01..5e2b035 100644 --- a/Core/QRBill.cs +++ b/Core/QRBill.cs @@ -90,6 +90,20 @@ public static class QRBill /// The height, in mm. public const double QrCodeWithQuietZoneHeight = 56; + /// + /// The width of the payment part, in mm. + /// + /// + /// The width, in mm. + public const double PaymentPartWidth = 148; + + /// + /// The height of the payment part, in mm. + /// + /// + /// The height, in mm. + public const double PaymentPartHeight = 105; + /// /// Validates and cleans the bill data. @@ -317,6 +331,10 @@ private static ICanvas CreateCanvas(BillFormat format) drawingWidth = QrBillWithHoriLineWidth; drawingHeight = QrBillWithHoriLineHeight; break; + case OutputSize.PaymentPartOnly: + drawingWidth = PaymentPartWidth; + drawingHeight = PaymentPartHeight; + break; case OutputSize.QrCodeOnly: drawingWidth = QrCodeWidth; drawingHeight = QrCodeHeight; diff --git a/CoreTest/PaymentCharacterSetTest.cs b/CoreTest/PaymentCharacterSetTest.cs index a96dcc9..8cdcba2 100644 --- a/CoreTest/PaymentCharacterSetTest.cs +++ b/CoreTest/PaymentCharacterSetTest.cs @@ -81,11 +81,33 @@ public void DecomposedAccents_AreComposed() [InlineData("xƉx", "x.x")] [InlineData("x\uD83C\uDDE8\uD83C\uDDEDx", "x.x")] [InlineData("DŽ", "DZ")] - public void InvalidCharacters_AreReplaced(string text, string expectedResult) + public void InvalidExtendedLatinCharacters_AreReplaced(string text, string expectedResult) { Assert.Equal(expectedResult, Payments.CleanedAndTrimmedText(text, SpsCharacterSet.ExtendedLatin)); } + [Theory] + [InlineData("ab\nc", "ab c")] + [InlineData("ÿÝ", "yY")] + [InlineData("€", "E")] + [InlineData("¥", "Y")] + [InlineData("ȆȇȈȉ", "EeIi")] + [InlineData("Ǒ", "O")] + [InlineData("ljnjffi", "ljnjffi")] + [InlineData("Ǽ", "AE")] + [InlineData("ʷ", "w")] + [InlineData("⁵", "5")] + [InlineData("℁", "a/s")] + [InlineData("Ⅶ", "VII")] + [InlineData("③", "3")] + [InlineData("xƉx", "x.x")] + [InlineData("x\uD83C\uDDE8\uD83C\uDDEDx", "x.x")] + [InlineData("DŽ", "DZ")] + public void InvalidLatin1SubsetCharacters_AreReplaced(string text, string expectedResult) + { + Assert.Equal(expectedResult, Payments.CleanedAndTrimmedText(text, SpsCharacterSet.Latin1Subset)); + } + [Fact] public void CleanedNull_ReturnsNull() { @@ -131,6 +153,34 @@ public void AllChars_HaveGoodReplacement(char ch) Assert.True(Payments.IsValidText(cleaned, SpsCharacterSet.ExtendedLatin)); } + [Fact] + public void CleanValueWithValidChars_ReturnsFalse() + { + Payments.CleanValue("def", out var cleaningResult); + Assert.False(cleaningResult.ReplacedUnsupportedChars); + Assert.Equal("def", cleaningResult.CleanedString); + } + + [Theory] + [InlineData("ab\nc", "ab c")] + [InlineData("ȆȇȈȉ", "EeIi")] + [InlineData("Ǒ", "O")] + [InlineData("Ǽ", "AE")] + public void CleanValueWithInvalidChars_ReturnsTrue(string text, string expectedResult) + { + Payments.CleanValue(text, out var cleaningResult); + Assert.True(cleaningResult.ReplacedUnsupportedChars); + Assert.Equal(expectedResult, cleaningResult.CleanedString); + } + + [Fact] + public void CleanValuleWithEmpty_ReturnsNull() + { + Payments.CleanValue("", out var cleaningResult); + Assert.False(cleaningResult.ReplacedUnsupportedChars); + Assert.Null(cleaningResult.CleanedString); + } + public class ExtendedLatinCharsProvider : IEnumerable { public IEnumerator GetEnumerator() diff --git a/CoreTest/PaymentPartTest.cs b/CoreTest/PaymentPartTest.cs new file mode 100644 index 0000000..a637920 --- /dev/null +++ b/CoreTest/PaymentPartTest.cs @@ -0,0 +1,36 @@ +// +// Swiss QR Bill Generator for .NET +// Copyright (c) 2024 Manuel Bleichenbacher +// Licensed under MIT License +// https://opensource.org/licenses/MIT +// + +using Codecrete.SwissQRBill.Generator; +using System.Threading.Tasks; +using Xunit; + +namespace Codecrete.SwissQRBill.CoreTest +{ + public class PaymentPartTest + { + [Fact] + public Task CreateQrBill1() + { + Bill bill = SampleData.CreateExample1(); + bill.Format.OutputSize = OutputSize.PaymentPartOnly; + bill.Format.GraphicsFormat = GraphicsFormat.SVG; + byte[] svg = QRBill.Generate(bill); + return VerifyImages.VerifySvg(svg); + } + + [Fact] + public Task CreateQrBill2() + { + Bill bill = SampleData.CreateExample2(); + bill.Format.OutputSize = OutputSize.PaymentPartOnly; + bill.Format.GraphicsFormat = GraphicsFormat.PDF; + byte[] pdf = QRBill.Generate(bill); + return VerifyImages.VerifyPdf(pdf); + } + } +} diff --git a/CoreTest/QRBillTest.cs b/CoreTest/QRBillTest.cs index 3793fd9..c0ac9f5 100644 --- a/CoreTest/QRBillTest.cs +++ b/CoreTest/QRBillTest.cs @@ -7,7 +7,6 @@ using Codecrete.SwissQRBill.Generator; using System.Threading.Tasks; -using VerifyXunit; using Xunit; diff --git a/CoreTest/ReferenceFiles/PaymentPartTest.CreateQrBill1.verified.svg b/CoreTest/ReferenceFiles/PaymentPartTest.CreateQrBill1.verified.svg new file mode 100644 index 0000000..05167a6 --- /dev/null +++ b/CoreTest/ReferenceFiles/PaymentPartTest.CreateQrBill1.verified.svg @@ -0,0 +1,259 @@ + + + + +Swiss QR Bill + +Payment part + + + + + + + + + +Currency +CHF +Amount +123 949.75 + + +Account / Payable to +CH44 3199 9123 0008 8901 2 +Robert Schneider AG +Rue du Lac 1268/2/22 +2501 Biel +Reference +21 00000 00003 13947 14300 09017 +Additional information +Instruction of 15.09.2019 +//S1/10/10201409/11/190512/20/1400.000-53/30/10601 +7086/31/180508/32/7.7/40/2:10;0:30 +Payable by +Pia-Maria Rutschmann-Schnyder +Grosse Marktgasse 28 +9400 Rorschach + + +Ultraviolet: +UV;UltraPay005;12345 +Xing Yong: +XY;XYService;54321 + + + diff --git a/CoreTest/ReferenceFiles/PaymentPartTest.CreateQrBill2.verified.png b/CoreTest/ReferenceFiles/PaymentPartTest.CreateQrBill2.verified.png new file mode 100644 index 0000000..8fd5acb Binary files /dev/null and b/CoreTest/ReferenceFiles/PaymentPartTest.CreateQrBill2.verified.png differ