Drawing vector images on PDF with PDFBox - java

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.

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));

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.

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

Related

Is there a way to set bounding box of figure in PDFBox

I'm trying to create a PDF that's PAC3 compliant and I need to add images to the PDF. I was able add an image to the PDF but when I run the PDF in PAC 3. I get an error because my image doesn't have a bounding box.
PAC3 output:
image of PAC3
Here's my code for adding an image to the pdf document.
PDStructureElement currentElem;
public void drawImage(PDStructureElement parent, float width, float height, float x,float y) throws IOException {
currentElem = addContentToParent(null, StandardStructureTypes.Figure, parent);
currentElem.setAlternateDescription("logo");
PDImageXObject logoImg = PDImageXObject.createFromFile("logo.jpg", this.pdf);
PDPageContentStream contentStream = new PDPageContentStream(this.pdf, this.pdf.getPage(0), PDPageContentStream.AppendMode.APPEND, false);
setNextMarkedContentDictionary();
contentStream.beginMarkedContent(COSName.IMAGE, PDPropertyList.create(currentMarkedContentDictionary));
contentStream.drawImage(logoImg, x, y,45,42);
contentStream.endMarkedContent();
contentStream.close();
addContentToParent(COSName.IMAGE, null, currentElem);
}
My Question:
How do I add a bounding box to a image? Is it even possible with PDFBox?
Originally I found a workaround for passing the PAC 3 tests, I would draw the images after everything was written and for whatever reason that worked on my 13 page document but when I tried to convert a 2 page document the error started to appear again.
Since then I found the proper way of giving an image a bounding box you will need to add this code to the image creation and it will satisfy PAC 3 tests (the proper way)
PDRectangle boundingBox = new PDRectangle(x, y, width, height);
COSDictionary attr = new COSDictionary();
attr.setString(COSName.BBOX, boundingBox.toString());
currentElem.getCOSObject().setItem(COSName.A, attr);

PDFBox 2.0.9 - pdf to image with alpha

I have problem with transferring PDF page to image (png) using PDFBox. I'm trying to transfer to image because then I want to use in PowerPoint presentation. Everything works fine except one case. Graph colors are not passed to image when fill of graph has transparency setup and colors are not from main palette.
Here is the link to pdf
Attaching sample image with pdf on left and output on the right
Here is sample responsible for pdf to img:
public static BufferedImage convertPDF2Image(PDDocument pdf, int page) throws IOException {
BufferedImage image = null;
ImageType imageType = ImageType.ARGB;
// int dpi = 96;
int dpi = 100;
PDFRenderer renderer = new PDFRenderer(pdf);
image = renderer.renderImageWithDPI(page, dpi, imageType);
return image;
}

How to insert a image created by decode of a string to a pdf in pdfbox

I'm trying to insert an image (which needs to be converted from a string by java.util.Base64.getDecoder().decode(imageInputString)) to a certain position of a pdf file.
The main logic of the code will be:
//create a PDImageXObject myImage first (or something that could be used in addImage method.
//And this is what I could not figure out how to accomplish.
//open the pdf file and use addImage to insert the image to the specific page at specific position.
PDDocument document = PDDocument.load(pdfFile);
PDPageContentStream contentStream = new PDPageContentStream(document, pageNumber);
contentStream.addImage(myImage,x,y);
document.save();
Most of the tutorial I found created the myImage from reading an image file. Could someone help me to see if I could do the same thing but with a byte [], which is the output of java.util.Base64.getDecoder().decode(imageInputString)?
Thanks!
You can use the static method PDImageXObject.createFromByteArray(), which detects the file type based on contents and will decide which PDF image type / image compression is best. (javadoc)
Thanks to Tilman Hausherr.
Here is the final code (just the core part):
int pageNumber = j;
PDPage page = document.getPage(pageNumber);
PDResources resources = page.getResources();
byte[] ba = java.util.Base64.getDecoder().decode(base64str);
PDImageXObject sigimg = PDImageXObject.createFromByteArray(document,ba,"signature");
float imgW = sigimg.getWidth();
float imgH = sigimg.getHeight();
PDPageContentStream contentStream = new PDPageContentStream(document, page,PDPageContentStream.AppendMode.APPEND, true,true);
PDRectangle sigRect = field.getWidgets().get(0).getRectangle();
float fieldW = sigRect.getWidth();
float fieldH = sigRect.getHeight();
if (imgW > fieldW || imgH > fieldH){
if(imgW/fieldW > imgH/fieldH){
sigimg.setWidth(Math.round(fieldW));
sigimg.setHeight(Math.round(imgH/imgW*fieldW));
}
else{
sigimg.setWidth(Math.round(imgW/imgH*fieldH));
sigimg.setHeight(Math.round(fieldH));
}
}
contentStream.drawImage(sigimg,sigRect.getLowerLeftX(),sigRect.getLowerLeftY());
contentStream.close();

PDFBox renderImage produces incorrect image dimensions at specified scale

I am using the very useful PDFBox to build a simple pdf stamping GUI.
I noticed a serious issue with a particular document however.
When I specify a particular scale factor for the rendering, the expected output image size is different.
What is worse? the scaling factor used for the resultant image along the horizontal axis is different from that along the vertical axis.
Here is the code I used:
/**
* #param pdfPath The path to the pdf document
* #param page The pdf page number(is zero based)
*/
public BufferedImage loadPdfImage(String pdfPath, int page) {
File file = new File(pdfPath);
try (PDDocument doc = PDDocument.load(file)) {
pageCount = doc.getNumberOfPages();
PDPage pDPage = doc.getPage(page);
float w = pDPage.getCropBox().getWidth();
float h = pDPage.getCropBox().getHeight();
System.out.println("Pdf opening: width: "+w+", height: "+h);
PDFRenderer renderer = new PDFRenderer(doc);
float dpiRatio = 1.5f;
BufferedImage img = renderer.renderImage(page, dpiRatio);
float dpiXRatio = img.getWidth() / w;
float dpiYRatio = img.getHeight()/ h;
System.out.println("dpiXRatio: "+dpiXRatio+", dpiYRatio: "+dpiYRatio);
return img;
} catch (IOException ex) {
System.out.println( "invalid pdf found. Please check");
}
return null;
}
The code above loads most pdf documents that I have tried it on and converts given pages within them to BufferedImage objects.
For the said document however, it seems to be unable to render the converted image at the supplied scale-factor.
Is there anything wrong with my code? or is it a known bug?
Thanks.
EDIT
I am using PDFBOX v2.0.15
And the page has no rotation.
The error was mine; for the most part.
I had used the MediaBox to compute the scale factors and unfortunately the MediaBox and CropBox of the pdf file in question were not the same.
For example:
cropbox-rect: [8.50394,34.0157,586.496,807.984]
mediabox-rect: [0.0,0.0,595.0,842.0]
After making corrections for these, the scale-factors matched better along both axes, save for the errors due to the fact that the image sizes are integer numbers.
This is negligible enough for me to neglect, though.
When stamping, all I had to do was to make the necessary corrections for the cropbox. For example to draw the image(stamp) at P(x,y), I would do:
x += cropBox.getLowerLeftX();
y += cropBox.getLowerLeftY();
before calling the draw image functionality.
It all came out fine!

create a one page PDF from two PDFs using PDFBOX

I have a small (quarter inch) one page PDF I created with PDFBOX with text (A). I want to put that small one page PDF (A) on the top of an existing PDF page (B), preserving the existing content of the PDF page (B). In the end, I will have a one page PDF, representing the small PDF on top(A), and the existing PDF intact making up the rest (B). How can I accomplish this with PDFBOX?
To join two pages one atop the other onto one target page, you can make use of the PDFBox LayerUtility for importing pages as form XObjects in a fashion similar to PDFBox SuperimposePage example, e.g. with this helper method:
void join(PDDocument target, PDDocument topSource, PDDocument bottomSource) throws IOException {
LayerUtility layerUtility = new LayerUtility(target);
PDFormXObject topForm = layerUtility.importPageAsForm(topSource, 0);
PDFormXObject bottomForm = layerUtility.importPageAsForm(bottomSource, 0);
float height = topForm.getBBox().getHeight() + bottomForm.getBBox().getHeight();
float width, topMargin, bottomMargin;
if (topForm.getBBox().getWidth() > bottomForm.getBBox().getWidth()) {
width = topForm.getBBox().getWidth();
topMargin = 0;
bottomMargin = (topForm.getBBox().getWidth() - bottomForm.getBBox().getWidth()) / 2;
} else {
width = bottomForm.getBBox().getWidth();
topMargin = (bottomForm.getBBox().getWidth() - topForm.getBBox().getWidth()) / 2;
bottomMargin = 0;
}
PDPage targetPage = new PDPage(new PDRectangle(width, height));
target.addPage(targetPage);
PDPageContentStream contentStream = new PDPageContentStream(target, targetPage);
if (bottomMargin != 0)
contentStream.transform(Matrix.getTranslateInstance(bottomMargin, 0));
contentStream.drawForm(bottomForm);
contentStream.transform(Matrix.getTranslateInstance(topMargin - bottomMargin, bottomForm.getBBox().getHeight()));
contentStream.drawForm(topForm);
contentStream.close();
}
(JoinPages method join)
You use it like this:
try ( PDDocument document = new PDDocument();
PDDocument top = ...;
PDDocument bottom = ...) {
join(document, top, bottom);
document.save("joinedPage.pdf");
}
(JoinPages test testJoinSmallAndBig)
The result looks like this:
Just as an additional point to #mkl's answer.
If anybody is looking to scale the PDFs before placing them on the page use,
contentStream.transform(Matrix.getScaleInstance(<scaling factor in x axis>, <scaling factor in y axis>)); //where 1 is the scaling factor if you want the page as the original size
This way you can rescale your PDFs.

Categories

Resources