Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image is broken if interpolate is set to false in generated pdf #358

Open
nikycube opened this issue Jun 10, 2019 · 11 comments
Open

Image is broken if interpolate is set to false in generated pdf #358

nikycube opened this issue Jun 10, 2019 · 11 comments

Comments

@nikycube
Copy link

nikycube commented Jun 10, 2019

Hi @danfickle I'm trying to render a pdf which contains images more exactly some barcodes.
Initially I tried to render with interpolate set to true and I got blurry image. See (Picture_with_interpolate_true.pdf)
After I did some research I found your post in which you recommended to set interpolate to be false (image-rendering: pixelated;) I entered in debug mode and I saw that in PdfBoxSlowOutputDevice.class:712 PDImageXObject is set to true.
As result the image is broken. See (Picture_with_pixelated.pdf)
I have attached two pdfs with both cases.
I used version: '0.0.1-RC20' from 'openhtmltopdf-pdfbox'.
The image was created with code bellow :

	private static String generateInterleafed25Image(String barcodeNumber) throws IOException {
        BarcodeInter25 barcode = new BarcodeInter25();
        barcode.setCode(barcodeNumber);
        Image awtImage = barcode.createAwtImage(Color.BLACK, Color.WHITE);

        BufferedImage newImage = new BufferedImage(
            awtImage.getWidth(null), awtImage.getHeight(null),
            BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = newImage.createGraphics();

        g.drawImage(awtImage, 0, 0, null);
        g.dispose();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(newImage, "png", baos);
        baos.flush();

        byte[] imageInByte = baos.toByteArray();
        baos.close();
        return Base64.getEncoder().encodeToString(imageInByte);
    }

Thank you!

Picture_with_interpolate_true.pdf
Picture_with_pixelated.pdf

@danfickle
Copy link
Owner

I will look into. @rototor might also have an idea. In the meantime things to try:
Try using an object drawer instead of an image. It is not yet documented but there is an example in the file below at the bottom:

https://github.com/danfickle/openhtmltopdf/blob/open-dev-v1/openhtmltopdf-examples/src/main/java/com/openhtmltopdf/testcases/TestcaseRunner.java

Also, please use the fast renderer builder.useFastMode in new projects.

@rototor
Copy link
Contributor

rototor commented Jun 11, 2019

The embedded image with interpolation=false misses the DecodeParams dictionary entry in the image (screenshot of the files in the PDFDebugger):

Working image:
image

Broken image:
image

Because of this the image is of course broken, as it can not be decoded correctly. The full html source of the broken case would be perfect to investigate that problem.

Beside this: Don't use images for barcodes. Interpolate is not respected by most PDF viewers / printers...

You have two ways fix that problem: Export the barcode as SVG and embed it as a vector SVG image. Or register a barcode object. I.e.

		objectDrawerFactory.registerDrawer("image/barcode",
				(e, x, y, width, height, outputDevice, ctx, dotsPerPixel) ->{
					String barcodeValue = e.getAttribute("datasrc");

					double realWidth = width / dotsPerPixel;
					double realHeight = height / dotsPerPixel;

					outputDevice.drawWithGraphics((float) x, (float) y, (float) realWidth, (float) realHeight,
							gfx -> {
								// Render the barcode somehow
                                                            
							
							});
					return null;
				});

inside the HTML:

		<object type="image/barcode"
				datasrc="MY-BARCODE-VALUE" style="width:6cm; height:1.0cm;float:right;margin-top:-0.2cm">
		</object>

This way you get the bars as vector fills and they will always be fine and not blurry with every printer / pdf viewer.

Don't use iText for anything, iText 2 is way outdated and I am pretty sure that you don't want to pay the premium you need for a iText 5 license. Just use barcode4j for this.

This is some code I use for a EAN128 barcode:

			SVGCanvasProvider provider = new SVGCanvasProvider(false, 0);
			DefaultConfiguration cfg = new DefaultConfiguration("cfg");
			cfg.addChild(new DefaultConfiguration("code128"));
			Code128 barcode = (Code128) BarcodeUtil.getInstance().createBarcodeGenerator(cfg);
			barcode.getCode128Bean().setBarHeight(7);
			barcode.getCode128Bean().setFontSize(UnitConv.pt2mm(5));

			if (!renderText)
				barcode.getCode128Bean().setMsgPosition(HumanReadablePlacement.HRP_NONE);

			barcode.generateBarcode(provider, message);
			Document svgDoc = provider.getDOM();

			Source source = new DOMSource(svgDoc);
			StringWriter outWriter = new StringWriter();
			Result output = new StreamResult(outWriter);
			Transformer transformer = TransformerFactory.newInstance().newTransformer();
			transformer.transform(source, output);

			String svgString = outWriter.toString();

You then only need to draw the SVG in the object drawer. Or you just inline the SVG. Which may likely be the easiest way.

@nikycube
Copy link
Author

Thank you for your responses @rototor @danfickle.
After I reviewed your responses, I tried following approaches:

  1. I drew image(barcode) with barcode4j and transformed in svg then added the result of svg inline in Html. What I got was only the bare code number. (see attachment Bar
    Barecode_with_svg.pdf
    code_with_svg.pdf)
    After I diged little bit I noticed that in line PdfBoxReplacedElementFactory.class:43 never reach because none
    (https://github.com/danfickle/openhtmltopdf/files/3284753/Barecode_with_svg.pdf)
    element has nodeName "svg" but I expected the node "svg" to be present.
    bellow you have the code of html:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">   <head>
    <style>
        div.header { position: running(header); }
        div.footer { position: running(footer); }
        @page { @top-center { content: element(header) }
                @bottom-center { content: element(footer) };
                margin-bottom: 3.3cm;
              }
        #pagenumber:before { content: counter(page) }
        #pagecount:before { content: counter(pages) }
    </style>   </head>
   <body>
       <div class="header">ss</div>       <div class="footer"><span id="pagenumber" ></span></div>       <div class="content"><svg xmlns="http://www.w3.org/2000/svg" height="8.7637mm" viewBox="0 0 32.55 8.7637" width="32.55mm"><g fill="black" stroke="none"><rect height="7" width="0.21" x="2.1" y="0"/><rect height="7" width="0.21" x="2.52" y="0"/><rect height="7" width="0.63" x="2.94" y="0"/><rect height="7" width="0.21" x="4.2" y="0"/><rect height="7" width="0.21" x="4.62" y="0"/><rect height="7" width="0.63" x="5.04" y="0"/><rect height="7" width="0.21" x="5.88" y="0"/><rect height="7" width="0.21" x="6.72" y="0"/><rect height="7" width="0.63" x="7.14" y="0"/><rect height="7" width="0.63" x="7.98" y="0"/><rect height="7" width="0.21" x="8.82" y="0"/><rect height="7" width="0.21" x="9.66" y="0"/><rect height="7" width="0.63" x="10.5" y="0"/><rect height="7" width="0.21" x="11.34" y="0"/><rect height="7" width="0.63" x="11.76" y="0"/><rect height="7" width="0.21" x="13.02" y="0"/><rect height="7" width="0.21" x="13.86" y="0"/><rect height="7" width="0.21" x="14.28" y="0"/><rect height="7" width="0.21" x="15.12" y="0"/><rect height="7" width="0.63" x="15.54" y="0"/><rect height="7" width="0.63" x="16.8" y="0"/><rect height="7" width="0.21" x="17.64" y="0"/><rect height="7" width="0.63" x="18.06" y="0"/><rect height="7" width="0.21" x="19.32" y="0"/><rect height="7" width="0.21" x="20.16" y="0"/><rect height="7" width="0.21" x="20.58" y="0"/><rect height="7" width="0.63" x="21" y="0"/><rect height="7" width="0.21" x="21.84" y="0"/><rect height="7" width="0.21" x="22.68" y="0"/><rect height="7" width="0.63" x="23.52" y="0"/><rect height="7" width="0.63" x="24.36" y="0"/><rect height="7" width="0.21" x="25.2" y="0"/><rect height="7" width="0.63" x="25.62" y="0"/><rect height="7" width="0.63" x="26.46" y="0"/><rect height="7" width="0.21" x="27.3" y="0"/><rect height="7" width="0.21" x="27.72" y="0"/><rect height="7" width="0.21" x="28.56" y="0"/><rect height="7" width="0.63" x="29.4" y="0"/><rect height="7" width="0.21" x="30.24" y="0"/><text font-family="Helvetica" font-size="1.7637" text-anchor="middle" x="16.275" y="8.6392">81675005130337</text></g></svg></div>   </body>   </html>
  1. I tried a barcode object as you mentioned but I was stuck on how to draw the svg inside into Graphics2D bellow you have the code:
static DefaultObjectDrawerFactory buildObjectDrawerFactory() {
        DefaultObjectDrawerFactory objectDrawerFactory = new DefaultObjectDrawerFactory();
        objectDrawerFactory.registerDrawer("image/barcode",
                (e, x, y, width, height, outputDevice, ctx, dotsPerPixel) ->{
                    String barcodeValue = e.getAttribute("datasrc");

                    double realWidth = width / dotsPerPixel;
                    double realHeight = height / dotsPerPixel;

                    outputDevice.drawWithGraphics((float) x, (float) y, (float) realWidth, (float) realHeight,
                            gfx -> {
                                SVGCanvasProvider provider = null;
                                try {
                                    provider = new SVGCanvasProvider(false, 0);
                                } catch (BarcodeCanvasSetupException e1) {
                                    e1.printStackTrace();
                                }
                                Interleaved2Of5Bean barcode = new Interleaved2Of5Bean();
                                barcode.setBarHeight(7);
                                barcode.setFontSize(UnitConv.pt2mm(5));

                                barcode.generateBarcode(provider, barcodeValue);
                                org.w3c.dom.Document svgDoc = provider.getDOM();
                              //   gfx = new SVGGraphics2D(SVGGeneratorContext.createDefault(svgDoc), false);
								// here I don't know how to draw
                                // gfx.draw();
                            });
                    return null;
                });
        return objectDrawerFactory;
    }

@rototor
Copy link
Contributor

rototor commented Jun 13, 2019

@nikycube To be honest I use some legacy lib here (jasperreports-3.7.4) to render the barcode on the canvas.

i.e.

	BatikRenderer barcode = new BatikRenderer(svgString, null);
	final int width = (int) ( dimension.getWidth());
	final int height = (int) ( dimension.getHeight());
	barcode.render(gfx, new Rectangle2D.Float(0, 0, width, height));

Doing this the right way needs some more code as you need to transcode the image using batik, see the classes in https://github.com/danfickle/openhtmltopdf/tree/open-dev-v1/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport

Stupid question: You did register the SVG drawer in the builder?

@nikycube
Copy link
Author

Yes, I did register the SVG drawer in the builder. If you are referring on my first approach. The thing is that
when I run in debugging mode, using that html I put above. During debugging this method is executed recursively com.openhtmltopdf.pdfboxout.PdfBoxReplacedElementFactory#createReplacedElement
but I don't see any element to have nodeName="svg" and the _svgImpl is not null because of registration of pdfBuilder.useSVGDrawer(new BatikSVGDrawer());
The bellow condition is never satisfied.

else if (nodeName.equals("svg") && this._svgImpl != null) {

I would rather take first approach with SVG embed it, if it works.

Thank you!

@danfickle
Copy link
Owner

Hi @nikycube, as a quick suggestion, try getting rid of the xmlns attribute on HTML element. I suspect this is an issue of node name vs tag name again.

danfickle added a commit that referenced this issue Jun 13, 2019
We can now use unit values such as mm in the width/height attribute of SVG images. With tests.
@danfickle
Copy link
Owner

Another issue you will find is that the width/height attributes only accept unit less values. So use css instead to size the svg element.

@nikycube
Copy link
Author

Hi again related to rendering svg image I got rid of xmlns but again I don't have a nodeName="svg" and the image isn't rendered

@danfickle
Copy link
Owner

Seems to work for me!

I used this builder code:

        try (OutputStream os = new FileOutputStream("/Users/me/Documents/pdf-issues/output/issue-358.pdf")) {
            PdfRendererBuilder builder = new PdfRendererBuilder();
            builder.withUri("file:///Users/me/Documents/pdf-issues/issue-358.htm");
            builder.toStream(os);
            builder.useSVGDrawer(new BatikSVGDrawer());
            builder.useFastMode();
            builder.run();
        }

and this html file:

<html lang="en">   <head>
    <style>
        div.header { position: running(header); }
        div.footer { position: running(footer); }
        @page { @top-center { content: element(header) }
                @bottom-center { content: element(footer) };
                margin-bottom: 3.3cm;
              }
        #pagenumber:before { content: counter(page) }
        #pagecount:before { content: counter(pages) }
    </style>   </head>
   <body>
       <div class="header">ss</div>       <div class="footer"><span id="pagenumber" ></span></div>       <div class="content"><svg xmlns="http://www.w3.org/2000/svg" height="8.7637mm" viewBox="0 0 32.55 8.7637" width="32.55mm"><g fill="black" stroke="none"><rect height="7" width="0.21" x="2.1" y="0"/><rect height="7" width="0.21" x="2.52" y="0"/><rect height="7" width="0.63" x="2.94" y="0"/><rect height="7" width="0.21" x="4.2" y="0"/><rect height="7" width="0.21" x="4.62" y="0"/><rect height="7" width="0.63" x="5.04" y="0"/><rect height="7" width="0.21" x="5.88" y="0"/><rect height="7" width="0.21" x="6.72" y="0"/><rect height="7" width="0.63" x="7.14" y="0"/><rect height="7" width="0.63" x="7.98" y="0"/><rect height="7" width="0.21" x="8.82" y="0"/><rect height="7" width="0.21" x="9.66" y="0"/><rect height="7" width="0.63" x="10.5" y="0"/><rect height="7" width="0.21" x="11.34" y="0"/><rect height="7" width="0.63" x="11.76" y="0"/><rect height="7" width="0.21" x="13.02" y="0"/><rect height="7" width="0.21" x="13.86" y="0"/><rect height="7" width="0.21" x="14.28" y="0"/><rect height="7" width="0.21" x="15.12" y="0"/><rect height="7" width="0.63" x="15.54" y="0"/><rect height="7" width="0.63" x="16.8" y="0"/><rect height="7" width="0.21" x="17.64" y="0"/><rect height="7" width="0.63" x="18.06" y="0"/><rect height="7" width="0.21" x="19.32" y="0"/><rect height="7" width="0.21" x="20.16" y="0"/><rect height="7" width="0.21" x="20.58" y="0"/><rect height="7" width="0.63" x="21" y="0"/><rect height="7" width="0.21" x="21.84" y="0"/><rect height="7" width="0.21" x="22.68" y="0"/><rect height="7" width="0.63" x="23.52" y="0"/><rect height="7" width="0.63" x="24.36" y="0"/><rect height="7" width="0.21" x="25.2" y="0"/><rect height="7" width="0.63" x="25.62" y="0"/><rect height="7" width="0.63" x="26.46" y="0"/><rect height="7" width="0.21" x="27.3" y="0"/><rect height="7" width="0.21" x="27.72" y="0"/><rect height="7" width="0.21" x="28.56" y="0"/><rect height="7" width="0.63" x="29.4" y="0"/><rect height="7" width="0.21" x="30.24" y="0"/><text font-family="Helvetica" font-size="1.7637" text-anchor="middle" x="16.275" y="8.6392">81675005130337</text></g></svg></div>   </body>   </html>

Note the xmlns attribute is removed from html element but not svg element where it is required.

Finally, this was the resulting PDF in the default branch:
issue-358.pdf

and this was the result in the replaced_sizing branch with better SVG sizing support.
issue-358.pdf

The only other thing I can think of is that replaced elements such as SVG must have display: block or display: inline-block, but you aren't overriding display property in your example.

@nikycube
Copy link
Author

Thank you! Finally I succeeded to render pdf with svg. I added in style .svg {display: inline-block}

danfickle added a commit that referenced this issue Jul 4, 2019
…mg tag.

This is mostly a revert of d9cdd6a

I forgot about external SVGs rather than inline SVGs where the width/height attribute are converted to CSS earlier in the render process.
@vipcxj
Copy link
Contributor

vipcxj commented Jul 16, 2019

I create my barcode in memory, and my server provider a url to this barcode image, then just use <img/> in the html to render the pdf.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants