15

I would like to draw a vector image on a PDF with Apache PDFBox.

This is the code I use to draw regular images

PDPage page = (PDPage) document.getDocumentCatalog().getAllPages().get(1);
PDPageContentStream contentStream = new PDPageContentStream(document, page, true, true);

BufferedImage _prevImage = ImageIO.read(new FileInputStream("path/to/image.png"));
PDPixelMap prevImage = new PDPixelMap(document, _prevImage);
contentStream.drawXObject(prevImage, prevX, prevY, imageWidth, imageHeight);

If I use a svg or wmf image instead of png, the resulting PDF document comes corrupted.

The main reason I want the image to be a vector image is that with PNG or JPG the image looks horrible, I think it gets somehow compressed so it looks bad. With vector images this shouldn't happen (well, when I export svg paths as PDF in Inkscape it doesn't happen, vector paths are preserved).

Is there a way to draw a svg or wmf (or other vector) to PDF using Apache PDFBox?

I'm currently using PDFBox 1.8, if that matters.

BackSlash
  • 21,927
  • 22
  • 96
  • 136
  • Quick tip, bitmaps (.png, .jpg, etc.) are completely different to vector images (.svg) and would most likely be implemented quite different in the PDFBox library. – insidesin Jul 30 '15 at 08:36
  • @insidesin Thank you for the comment. I think the implementation is different too, as vector images cannot be read as bitmaps... But I can't seem to find someone who succeeded in loading vector images and drawing them to PDF. I did it successfully with iText, there must be an way to do this in PDFBox too... – BackSlash Jul 30 '15 at 08:50
  • Yeah I've used `iText` quite a bit, it's a lot easier to read than `PDFBox` given my quick glances just now at the API... and I thought `iText` was hard. – insidesin Jul 30 '15 at 08:51
  • 1
    The problem with "loading vector images and drawing them" is that *just like with bitmap images* you need to interpret the data (of which you do not be totally aware – apparently PDFBox conveniently translates the bitmap images into correct PDF syntax where necessary). So to add an SVG image, you need to write code to translate *all SVG commands* into its PDF equivalents (including expanding 'meta' stuff such as filters, shadows, and arrows into primitives). For WMF, the same. For EPS, the same. For AI ... for CorelDraw ... well, I guess you get the idea. – Jongware Jul 30 '15 at 08:56
  • 2
    .. that said, some searching around suggests using Batik as an intermediate to create a separate PDF first, and then that PDF could be inserted into yours. – Jongware Jul 30 '15 at 09:11
  • The best is the solution mentioned by Jongware. The second best is to convert them to PNG and use PDPixelMap. This will not make them look bad like with PDJpeg. (I suspect you used PDJpeg wih a BufferedImage that was created from a PNG, that is indeed a bad idea for images with sharp edges) – Tilman Hausherr Jul 30 '15 at 12:28
  • @TilmanHausherr Nope, the code is like what I posted, I exported a png image from an SVG, which I imported in the PDF with `PDPixelMap`, and it looks horrible. It's blurry and I can see pixels, maybe I'm missing something. Now I exported the SVG to PDF, I'm looking for examples on how to import the generated PDF as `PDXObject` and draw it in the main PDF – BackSlash Jul 30 '15 at 12:32
  • https://stackoverflow.com/questions/28295853/add-page-as-layer-from-separate-pdfdifferent-page-size-using-pdfbox and https://stackoverflow.com/questions/17782153/pdfbox-layerutility-importing-layers-into-existing-pdf – Tilman Hausherr Jul 30 '15 at 12:37
  • @TilmanHausherr I didn't know it was called "Super imposing", that's why I found nothing about it! That helped a lot, thank you! – BackSlash Jul 30 '15 at 12:59
  • @BackSlash were you able to insert an SVG in a PDF with PDFBox? If you did, post the solution :) – IvanRF Mar 24 '16 at 00:18
  • @IvanRF No, unfortunately I didn't find a working solution. This question is a bit old, not many days ago Apache released PDFBox 2.0, maybe it can do this now... If you find a solution, please post it there! – BackSlash Mar 24 '16 at 17:26

3 Answers3

5

See the library pdfbox-graphics2d, touted in this Jira.

You can draw the SVG, via Batik or Salamander or whatever, onto the class PdfBoxGraphics2D, which is parallel to iText's template.createGraphics(). See the GitHub page for samples.

PDDocument document = ...;
PDPage page = ...; // page whereon to draw

String svgXML = "<svg>...</svg>";
double leftX = ...;
double bottomY = ...; // PDFBox coordinates are oriented bottom-up!

// I set these to the SVG size, which I calculated via Salamander.
// Maybe it doesn't matter, as long as the SVG fits on the graphic.
float graphicsWidth = ...;
float graphicsHeight = ...;

// Draw the SVG onto temporary graphics.
var graphics = new PdfBoxGraphics2D(document, graphicsWidth, graphicsHeight);
try {
    int x = 0;
    int y = 0;
    drawSVG(svg, graphics, x, y); // with Batik, Salamander, or whatever you like
} finally {
    graphics.dispose();
}

// Graphics are not visible till a PDFormXObject is added.
var xform = graphics.getXFormObject();

try (var contentWriter = new PDPageContentStream(document, page, AppendMode.APPEND, false)) { // false = don't compress
    // XForm objects have to be placed via transform,
    // since they cannot be placed via coordinates like images.
    var transform = AffineTransform.getTranslateInstance(leftX, bottomY);
    xform.setMatrix(transform);

    // Now the graphics become visible.
    contentWriter.drawForm(xform);
}

And ... in case you want also to scale the SVG graphics to 25% size:

// Way 1: Scale the SVG beforehand
svgXML = String.format("<svg transform=\"scale(%f)\">%s</svg>", .25, svgXML);

// Way 2: Scale in the transform (before calling xform.setMatrix())
transform.concatenate(AffineTransform.getScaleInstance(.25, .25));
Coemgenus
  • 171
  • 1
  • 4
  • If you're adding to an existing PDF, set the fifth parameter to true in the PDPageContentStream constructor. (See in javadoc why) – Tilman Hausherr Oct 28 '19 at 12:14
  • 1
    To use Salamander with an SVG file, you can create an SVGUniverse, then an SVGDiagram (see this https://stackoverflow.com/questions/2397492/svg-salamander-example), and then do `diagram.render(graphics);` – Nirmal Dec 06 '20 at 07:54
  • Hello. `drawSVG(svg, graphics, x, y);` Could you be more specific: i..e. how to draw an SVG file with Batik? – coderodde Nov 24 '21 at 15:01
  • The comment before yours has a link to one common way to write it onto the graphics using the Salamander classes. – Coemgenus Oct 25 '22 at 02:15
0

I do this, but not directly. In first transform your SVG documents in PDF documents with FOP librairy and Batik. https://xmlgraphics.apache.org/fop/dev/design/svg.html.

In second times, you can use LayerUtility in pdfbox to transform your new pdf document in PDXObjectForm. After that, just needs to include PDXObjectForm in your final pdf documents.

Guyard
  • 53
  • 6
0

The final working solution for me that loads an SVG file and overlays it on a PDF file (this renders the SVG in a 500x500 box at (0,0) coordinate which is bottom left of the PDF document):

package com.example.svgadder;

import java.io.*;
import java.nio.*;

import org.apache.pdfbox.pdmodel.*;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;

import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D;

import java.awt.geom.AffineTransform;

import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGException;
import com.kitfox.svg.SVGUniverse;

public class App 
{
    public static void main( String[] args ) throws Exception {
        
        App app = new App();

    }

    public App() throws Exception {
        
        // loading PDF and SVG files

        File pdfFile = new File("input.pdf");
        File svgFile = new File("input.svg");

        PDDocument doc = PDDocument.load(pdfFile);
        PDPage page = doc.getPage(0);

        SVGUniverse svgUniverse = new SVGUniverse();
        SVGDiagram diagram = svgUniverse.getDiagram(svgUniverse.loadSVG(f.toURL()));
        
        PdfBoxGraphics2D graphics = new PdfBoxGraphics2D(doc, 500, 500);

        try {
            diagram.render(graphics);
        } finally {
            graphics.dispose();
        }

        PDFormXObject xform = graphics.getXFormObject();

        try (PDPageContentStream contentWriter = new PDPageContentStream(doc, page, AppendMode.APPEND, false)) {

            AffineTransform transform = AffineTransform.getTranslateInstance(0, 0);
            xform.setMatrix(transform);
            contentWriter.drawForm(xform);
        }

        doc.save("res.pdf");
        doc.close();
    }
}

Please use svgSalamander from here: https://github.com/mgarin/svgSalamander

Please use what Coemgenus suggested for scaling your final overlaid SVG. I tried the 2nd option and it works well.

Nirmal

Nirmal
  • 667
  • 5
  • 9