LibrePDF / OpenPDF

OpenPDF is a free Java library for creating and editing PDF files, with a LGPL and MPL open source license. OpenPDF is based on a fork of iText. We welcome contributions from other developers. Please feel free to submit pull-requests and bugreports to this GitHub repository.
Other
3.63k stars 598 forks source link

Can't fallback Font to Helvetica in PdfGraphics2D #1159

Open admannon opened 7 months ago

admannon commented 7 months ago

PLEASE FILL THIS TEMPLATE AS MUCH AS POSSIBLE. REMOVE IRRELEVANT PARTS.

Describe the bug

We can't generate PDF document from code below and got error:

Font creation failed for Helvetica.
java.lang.RuntimeException: Font creation failed for Helvetica.

To Reproduce

Code to reproduce the issue

Unit Test

```java import com.lowagie.text.Document; import com.lowagie.text.pdf.LayoutProcessor; import com.lowagie.text.pdf.PdfReader; import com.lowagie.text.pdf.PdfWriter; import org.junit.jupiter.api.Test; import java.awt.*; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; import java.awt.print.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; class PdfConverterTest { private static int generateContent(Graphics g, PageFormat pf, int i) { var g2 = (Graphics2D) g; float h = (float) pf.getHeight(); float w = (float) pf.getWidth(); Ellipse2D.Double circle = new Ellipse2D.Double(); Ellipse2D.Double oval = new Ellipse2D.Double(); Ellipse2D.Double leaf = new Ellipse2D.Double(); Ellipse2D.Double stem = new Ellipse2D.Double(); g2.setColor(Color.green); double eh = h / 2; double ew = w / 2; leaf.setFrame(ew - 16, eh - 29, 15.0, 15.0); Area leaf1 = new Area(leaf); leaf.setFrame(ew - 14, eh - 47, 30.0, 30.0); Area leaf2 = new Area(leaf); leaf1.intersect(leaf2); g2.fill(leaf1); // Creates the second leaf. leaf.setFrame(ew + 1, eh - 29, 15.0, 15.0); leaf1 = new Area(leaf); leaf2.intersect(leaf1); g2.fill(leaf2); g2.setColor(Color.black); // Creates the stem by filling the Area resulting from the subtraction of two Area objects created from an // ellipse. stem.setFrame(ew, eh - 42, 40.0, 40.0); Area st1 = new Area(stem); stem.setFrame(ew + 3, eh - 47, 50.0, 50.0); Area st2 = new Area(stem); st1.subtract(st2); g2.fill(st1); g2.setColor(Color.yellow); // Creates the pear itself by filling the Area resulting from the union of two Area objects created by two // different ellipses. circle.setFrame(ew - 25, eh, 50.0, 50.0); oval.setFrame(ew - 19, eh - 20, 40.0, 70.0); Area circ = new Area(); circ.add(new Area(circle)); circ.add(new Area(oval)); g2.fill(circ); g2.setColor(Color.black); g2.setFont(new Font("Arial", Font.PLAIN, 18)); String pear = "Pear"; FontMetrics metrics = g2.getFontMetrics(); int width = metrics.stringWidth(pear); g2.drawString(pear, (w - width) / 2, 20); return Printable.PAGE_EXISTS; } private static PageFormat getPageFormat(double width, double height, int orientation) { PageFormat pageFormat = new PageFormat(); Paper paper = pageFormat.getPaper(); paper.setSize(width, height); paper.setImageableArea(0, 0, paper.getWidth(), paper.getHeight()); pageFormat.setPaper(paper); pageFormat.setOrientation(orientation); return pageFormat; } @Test void generateFromPageable() throws IOException, PrinterException { Book book = new Book(); book.append( PdfConverterTest::generateContent, getPageFormat(595.2755905511812, 841.8897637795277, PageFormat.PORTRAIT)); LayoutProcessor.enable(); byte[] pdfContentByte; try (var outputStream = new ByteArrayOutputStream()) { try (var document = new Document()) { var writer = PdfWriter.getInstance(document, outputStream); document.open(); var contentByte = writer.getDirectContent(); var totalPages = book.getNumberOfPages(); for (var i = 0; i < totalPages; i++) { var pageFormat = book.getPageFormat(i); var printable = book.getPrintable(i); float pageW = (float) pageFormat.getWidth(); float pageH = (float) pageFormat.getHeight(); float marginL = (float) pageFormat.getImageableX(); float marginT = (float) pageFormat.getImageableY(); float marginR = (float) (pageW - pageFormat.getImageableWidth()); float marginB = (float) (pageH - pageFormat.getImageableHeight()); document.setPageSize(new com.lowagie.text.Rectangle(pageW, pageH)); document.setMargins(marginL, marginR, marginT, marginB); document.newPage(); Graphics2D g2 = contentByte.createGraphics(pageW, pageH); printable.print(g2, pageFormat, i); g2.dispose(); contentByte.sanityCheck(); } } pdfContentByte = outputStream.toByteArray(); } try (var inputStream = new ByteArrayInputStream(pdfContentByte)) { new PdfReader(inputStream); } } } ```

Expected behavior

This code should generate a PDF normally

System

(please complete the following information)

a-leithner commented 6 months ago

I'm observing the same problem. However, in my case, I can generate one PDF without problems, but subsequent PDFs fail with this exact exception, though I'm also getting

Caused by: java.io.IOException: Helvetica not found as file or resource.
    at com.lowagie.text.pdf.LayoutProcessor.loadFont(LayoutProcessor.java:307)

If that's related, I'm using the technique discussed by V-F in #1078 to render an SVG image.

a-leithner commented 6 months ago

Oh, and version 2.0.1 of OpenPDF works fine, only 2.0.2 breaks.

asturio commented 6 months ago

Ok, that is strange. Maybe there is a problem or difference resolving the font-resource. There were some changes so the tests also work on Windows.

Keeping the tests compatible in Windows, Unix and Mac is not easy, as I only have access to the first 2.

@admannon can you verify, that the error don't occur with 2.0.1?

asturio commented 6 months ago

It's also a strange behaviour, as helvetica may be a font provided by the OS, but there are helvetica-font-files also in the JAR.

a-leithner commented 6 months ago

Well, I'm on Linux and don't have any form of Helvetica installed. Could this influence something?

admannon commented 6 months ago

Ok, that is strange. Maybe there is a problem or difference resolving the font-resource. There were some changes so the tests also work on Windows.

Keeping the tests compatible in Windows, Unix and Mac is not easy, as I only have access to the first 2.

@admannon can you verify, that the error don't occur with 2.0.1?

Apologies for the delayed response. Yes, this error don't occur with OpenPDF v2.0.1. The attached PDF was generated using OpenPDF v2.0.1.

a-leithner commented 3 months ago

Are there any news on this? I'd be willing to spend some time on this if I could get a hint on where the culprit may be.

By the way, this issue is also present in OpenPDF 2.0.3

andreasrosdal commented 3 months ago

This bug was introduced in this change: https://github.com/LibrePDF/OpenPDF/pull/1114 (in OpenPDF 2.0.2 and later)

The test included in the first comment in this bugreport will fail for versions which include #1114 or later. Earlier commits do not have this problem. So I recommend to consider #1114 and how to fix this problem. Possibly revert #1114.

@vk-github18

vk-github18 commented 3 months ago

See https://github.com/LibrePDF/OpenPDF/wiki/Accents,-DIN-91379,-non-Latin-scripts

  1. Step: Provide an OpenType font

LayoutProcessor works only with OpenType fonts provided as file. It does not work with Type1-fonts. This is documented and no bug. However, other fonts should by silently ignored - this will be fixed.

So the fix for your example is:

a-leithner commented 3 months ago

@vk-github18 Thanks for the tips. However, while these may apply to the OP's situation, I can say for certain that no document I'm generating with OpenPDF uses any Type 1 fonts whatsoever.[^1] Take a look at #1209 for how I'm loading and registering my fonts. (Even when I'm generating headers and footers I'm using one of those fonts.)

Furthermore, not calling LayoutProcessor.enable or LayoutProcessor.enableKernLiga is not an option because I need support for RTL text and such.

Please note that I still only observe the issue upon the second document I'm trying to generate. Running OpenPDF only once works perfectly fine.

Is what I'm observing a separate issue then?

[^1]: Side note: In the past, I've had some documents produced by OpenPDF that contained Helvetica in their fonts table though they didn't use it. I've gone over all my generating code since and I always provide one of the fonts I'm loading as TTF files whenever I instantiate a new Element. Could this spurious reference of Helvetica without using be somehow related to this?

a-leithner commented 3 months ago

I can provide some more context: For rendering an SVG image, I'm using this code (mostly by V-F from #1078):

Image produceLogoImage (PdfWriter writer) {
    try {
        SAXSVGDocumentFactory svgFactory = new SAXSVGDocumentFactory (XMLResourceDescriptor.getXMLParserClassName ());
        SVGDocument logoDocument = svgFactory.createSVGDocument (null, Thread.currentThread ().getContextClassLoader ().getResourceAsStream (/*some SVG*/));

        UserAgent ua = new UserAgentAdapter ();
        DocumentLoader loader = new DocumentLoader (ua);
        BridgeContext context = new BridgeContext (ua, loader);
        context.setDynamicState (BridgeContext.DYNAMIC);

        GVTBuilder builder = new GVTBuilder ();
        GraphicsNode rootGraphicsNode = builder.build (context, logoDocument);

        PdfTemplate template = PdfTemplate.createTemplate (writer, 110, 37);
        Graphics2D logoG2d = template.createGraphics (template.getWidth (), template.getHeight ());
        try {
            rootGraphicsNode.paint (logoG2d);
        } catch (Exception e) {
            e.printStackTrace ();
            return null;
        } finally {
            logoG2d.dispose ();
        }

        return new ImgTemplate (template);
    } catch (IOException e) {
        e.printStackTrace ();
        return null;
    }
}

It fails (upon running the second time for a new PdfWriter) on the line

Graphics2D logoG2d = template.createGraphics (template.getWidth (), template.getHeight ());

with the following exception:

java.lang.RuntimeException: Font creation failed for Helvetica.
    at com.lowagie.text.pdf.LayoutProcessor.loadFont(LayoutProcessor.java:318)
    at com.lowagie.text.pdf.BaseFont.createFont(BaseFont.java:727)
    at com.lowagie.text.pdf.BaseFont.createFont(BaseFont.java:650)
    at com.lowagie.text.pdf.BaseFont.createFont(BaseFont.java:486)
    at com.lowagie.text.pdf.DefaultFontMapper.awtToPdf(DefaultFontMapper.java:145)
    at com.lowagie.text.pdf.PdfGraphics2D.getCachedBaseFont(PdfGraphics2D.java:1070)
    at com.lowagie.text.pdf.PdfGraphics2D.setFont(PdfGraphics2D.java:1063)
    at com.lowagie.text.pdf.PdfGraphics2D.<init>(PdfGraphics2D.java:269)
    at com.lowagie.text.pdf.PdfContentByte.createGraphics(PdfContentByte.java:3185)
    // further stack only includes my classes
Caused by: java.io.IOException: Helvetica not found as file or resource.
    at com.lowagie.text.pdf.LayoutProcessor.loadFont(LayoutProcessor.java:307)

Do I need to set a font here too? If so, how?

vk-github18 commented 3 months ago

See https://github.com/LibrePDF/OpenPDF/pull/1211

vk-github18 commented 3 months ago

@a-leithner As PdfTemplate extends PdfContentBytePdfTemplate you could call template.setFontAndSize(BaseFont bf, float size)

However with https://github.com/LibrePDF/OpenPDF/pull/1211 this exception should not occure any more. But LayoutProcessor does nothing without an OpenType font.

a-leithner commented 3 months ago

@vk-github18 Great, thanks a lot! I'll try the changes from that PR locally and'll report back.

I've tried calling template.setFontAndSize but that unfortunately didn't help.

a-leithner commented 3 months ago

@vk-github18 Thanks again for that fix! I can confirm it works in my scenario and I'm no longer getting this exception.