manuelbl / SwissQRBill

Java library for Swiss QR bill payment slips (aka QR-Rechnung)
https://www.codecrete.net/qrbill
MIT License
155 stars 37 forks source link

QR Bill not compatible with PDF/A-2b #61

Closed cristalp closed 2 years ago

cristalp commented 2 years ago

We always create PDF/A documents, to be precise: PDF/A-2b.

I'm validating them with veraPDF, and they all pass fine, including the ones with a Swiss QR Bill.

However, I recently also validated with a couple of online PDF validators. And PDF Online shows this error:

    Validating file "result.pdf" for conformance level pdfa-2b
        Graphics operator q is not allowed in text object.
        Graphics operator n is not allowed in text object.
        Graphics operator Q is not allowed in text object.
    The document does not conform to the requested standard.
    The document doesn't conform to the PDF reference (missing required entries, wrong value types, etc.).
    The document does not conform to the PDF/A-2b standard.
    Done.

Other validators show the PDF to be valid PDF/A-2b. I nailed down the error to just the QR Bill, so it has to be the reason.

I just wanted to point this out. I'm by no means a PDF expert, I hope you are :-)

And thanks a lot for your work, we've saved a lot of time using your software!

manuelbl commented 2 years ago

We always create PDF/A documents, to be precise: PDF/A-2b.

This library does not create PDF files compliant with PDF/A. So you must be using some sort of PDF post-processing. How exactly are you doing it?

However, I recently also validated with a couple of online PDF validators. And PDF Online shows this error:

What PDF have you validated? A PDF generated by this library, or one with additional content or post processing?

Can you provide a sample that doesn't validate? I've generated multiple PDFs with the library and they all validated at PDF Online without error.

cristalp commented 2 years ago

Well, we're using iText 7.1.9 and adding the QR code to a PdfADocument that's created with PdfAConformanceLevel.PDF_A_2B.

So the QR code is embedded in an invoice letter. Is it possible to send you the PDF directly without posting it here?

manuelbl commented 2 years ago

If you are using iText, then you must have implemented your own Canvas based on iText instead of the standard PDFCanvas based on PDFBox.

If so, the bug must be either in your Canvas implementation (e.g. forgetting to call endText()), or in iText. It cannot be in this library as you are not using any of the PDF related features of this library.

cristalp commented 2 years ago

Well, the code looks like this, docbeing the iText Document:

    final BillFormat format = new BillFormat();
    format.setGraphicsFormat(GraphicsFormat.SVG);
    format.setFontFamily(ARIAL_FONT_FAMILY);
    format.setLanguage(Language.valueOf(data.getSprache().getISOCode().toUpperCase(Locale.ENGLISH)));
    bill.setFormat(format);

    final FontProvider fontProvider = getFonts().getFontProvider();
    final SvgConverterProperties converterProperties = new SvgConverterProperties();
    converterProperties.setFontProvider(fontProvider);

    SvgConverter.drawOnPage(new String(QRBill.generate(bill), StandardCharsets.UTF_8),
        doc.getPdfDocument().getLastPage(), QR_CODE_X, QR_CODE_Y, converterProperties);
manuelbl commented 2 years ago

Thanks for providing the code. It shows that no custom canvas is involved. Instead, an SVG image is created with the Swiss QR bill library and then iText is used to convert and integrate the SVG image into a PDF document.

My conclusion is that the problem is likely in iText (e.g. in its SvgConverter) and with less likelihood in your code. It certainly cannot be in the Swiss QR bill as - the way it is used here - it generates SVG and not PDF. SVG is a technology independent from PDF and cannot lead to this kind of low-level PDF errors.

Thus, you don't need to send me a sample PDF file. If you are certain the bug is not in your code, you have to send it to iText instead.

cristalp commented 2 years ago

Ok, thanks a lot for the analysis. I'll close the issue and try to contact the iText team.

manuelbl commented 2 years ago

A last remark regarding the validation error. It indicates that graphics (lines, rectangles and other shapes) and text operations are mixed, which would be invalid. In iText, you have to call beginText() before using text operations and endText() after using text operations. Most likely, this is violated, i.e. beginText() is called, some text operations are executed but then graphics operations are used without calling endText().

This could be the case in the SvgConverter class, or in your code. Before submitting a bug description to iText, you might want to check that you have called endText() before calling SvgConverter.drawOnPage().

AbraxasCari commented 2 years ago

Hello cristalp, May be you can avoid the SVG convertion with iText and just incorporate the PDF generated by Swiss QR bill generator. Something like this :

com.lowagie.text.pdf.PdfContentByte content;
// Generate your PdfContent
[...]
net.codecrete.qrbill.generator.Bill bill = new net.codecrete.qrbill.generator.Bill();
// Initialise your QR bill
[...]
byte[] bytes = net.codecrete.qrbill.generator.QRBill.generate(bill);
com.lowagie.text.pdf.PdfReader pdfReader = new com.lowagie.text.pdf.PdfReader(bytes);
com.lowagie.text.pdf.PdfWriter pdfWriter = content.getPdfWriter();
com.lowagie.text.pdf.PdfImportedPage importedPage = pdfWriter.getImportedPage(pdfReader, 1);
content.addTemplate(importedPage, 0, 0);
cristalp commented 2 years ago

@AbraxasCari Thanks, but it seems like we're not talking about the same iText version. Do you use iText 5? Because I can't find e.g. PdfContentByte in https://api.itextpdf.com/iText7/java/7.1.9/

cristalp commented 2 years ago

@manuelbl Ok, but beginText() and endText() seems to be pretty low-level, right? We're only using the high-level Document. And I'm by no means an iText expert :-) But looking at their code in SvgConverter.drawOnPage, a new PdfCanvas is created from the current page. The SVG is converted to a PdfFormXObject and then added to the new Canvas.

AbraxasCari commented 2 years ago

@AbraxasCari Thanks, but it seems like we're not talking about the same iText version. Do you use iText 5? Because I can't find e.g. PdfContentByte in https://api.itextpdf.com/iText7/java/7.1.9/

Yes, sorry, I took the code from an opensource iText version I have : openpdf 1.3.23. It should probably be possible to do the same thing, unfortunately I don't know the new API. What I can add in my previous example is where the "PdfContentByte" object can be obtained from -> from a PdfWriter (class that still exists, but no longer its getDirectContent() method that is used hereafter....):

PdfContentByte content = pdfWriter.getDirectContent()

manuelbl commented 2 years ago

@cristalp Yes, they are low-level functions. If you are not using them, then the "bug" is unlikely to be in your software.