rectangle size on template in itext confusing - java

I got a problem I do not really know how to proceed with... When I draw a rectangle on a page (the blue one in the picture) and then draw the same rectangle on a template on the same page (the green one), the rectangle on the template is larger. Has anybody a clue WHY?
Run the following class:
public class RectangleTemplate {
public static void main(String[] args){
try {
File file = new File("rectagnleTemplate_" + System.currentTimeMillis() + ".pdf");
FileOutputStream fileout = new FileOutputStream(file);
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, fileout);
document.open();
Rectangle rectangleOnPage = new Rectangle(20, 20, 100, 100);
rectangleOnPage.setBorderColor(BaseColor.BLUE);
rectangleOnPage.setBorder(Rectangle.BOX);
rectangleOnPage.setBorderWidth(2);
PdfContentByte canvas = writer.getDirectContent();
canvas.rectangle(rectangleOnPage);
canvas.stroke();
PdfTemplate template = canvas.createTemplate(document.getPageSize().getWidth(), document.getPageSize()
.getHeight());
template.rectangle(rectangleOnPage.getLeft(), rectangleOnPage.getBottom(), rectangleOnPage.getRight(),
rectangleOnPage.getTop());
template.setColorFill(BaseColor.GREEN
);
template.fill();
template.stroke();
canvas.addTemplate(template, -10,-10);
canvas.sanityCheck();
canvas.stroke();
document.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}
}
the green rectangle should be as large as the blue one:

This is indeed confusing: iText 1, 2 and 5 grew organically: different people contributed code and as a result, there's a difference between the way a Rectangle object is created and the way you define a rectangle using the rectangle() method.
Take a look at the API docs:
The Rectangle constructor looks like this:
public Rectangle(float llx, float lly, float urx, float ury)
You need the coordinates of the lower-left and the upper-right corner.
The rectangle method looks like this:
public void rectangle(float x, float y, float w, float h)
In this case, you only pass the coordinate of the lower-left corner. The other two parameters are the width and the height.
This line in your code is wrong:
template.rectangle(
rectangleOnPage.getLeft(), rectangleOnPage.getBottom(),
rectangleOnPage.getRight(), rectangleOnPage.getTop());
It should be:
template.rectangle(
rectangleOnPage.getLeft(), rectangleOnPage.getBottom(),
rectangleOnPage.getWidth(), rectangleOnPage.getHeight());
This problem is fixed in iText 7. iText 7 is a complete rewrite of iText by a coordinated team.

Related

Cannot capture annotations in a PDImageXObject using PDFBox

I am highlighting (in green) certain words on each page of my input pdf document using PDAnnotationTextMarkup. A sample screenshot is seen below;
However, when I crop this part of the page and try to save it as a PDImageXObject, I get the following result;
The code I am using to crop the image is as follows;
public PDImageXObject cropAndSaveImage(int pageNumber, Rectangle dimensions)
{
PDImageXObject pdImage = null;
// Extract arguments from the rectangle object
float x = dimensions.getXValue();
float y = dimensions.getYValue();
float w = dimensions.getWidth();
float h = dimensions.getHeight();
PDRectangle pdRectangle;
int pageResolution = 140;
try{
// Fetch the source page
PDPage sourcePage = document.getPage(pageNumber-1);
// Calculate height of the source page
PDRectangle mediaBox = sourcePage.getMediaBox();
float sourcePageHeight = mediaBox.getHeight();
// Fetch the original crop box of the page
PDRectangle originalCrop = sourcePage.getCropBox();
/*
* PDF Crop Box - Here we initialize the rectangle area
* that is needed to crop a region from the PDF document
*/
if(pageOriginShifted)
{
pdRectangle = new PDRectangle(x, sourcePageHeight - y, w, h);
}
else
{
pdRectangle = new PDRectangle(x, y, w, h);
}
// Crop the required rectangle from the source page
sourcePage.setCropBox(pdRectangle);
// PDF renderer
PDFRenderer renderer = new PDFRenderer(document);
// Convert to an image
BufferedImage bufferedImage = renderer.renderImageWithDPI(pageNumber-1, pageResolution, ImageType.RGB);
pdImage = LosslessFactory.createFromImage(document, bufferedImage);
// Restore the original page back to the document
sourcePage.setCropBox(originalCrop);
return pdImage;
}catch(Exception e)
{
Logger.logWithoutTimeStamp(LOG_TAG + "cropAndSaveImage()", Logger.ERROR, e);
}
return null;
}
I am quite perplexed on why the highlighted text in green won't show up. Any help in this regard is highly appreciated (I cannot attach the input PDF document owing to privacy issues).
Thanks in advance,
Bharat.

Efficient SVG rendering for PDFs (Java, Batik, Flying Saucer)

I'm rendering PDFs with XHTML and flying saucer. I've added SVG images (icons etc) as well. However, when I try to draw a lot of images (like 5000+) the rendering takes really long (obviously). There are only 10 or so different images to draw, but just repeating them a lot of times (same size).
Is there a way/library to do this efficiently?
Currently using batik, flying saucer combo to draw images. The following code is used to parse the xhtml and find the img tags to place the SVG images:
#Override
public ReplacedElement createReplacedElement(LayoutContext layoutContext, BlockBox blockBox, UserAgentCallback userAgentCallback, int cssWidth, int cssHeight) {
Element element = blockBox.getElement();
if (element == null) {
return null;
}
String nodeName = element.getNodeName();
if ("img".equals(nodeName)) {
SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName());
SVGDocument svgImage = null;
try {
svgImage = factory.createSVGDocument(new File(element.getAttribute("src")).toURL().toString());
} catch (IOException e) {
e.printStackTrace();
}
Element svgElement = svgImage.getDocumentElement();
element.appendChild(element.getOwnerDocument().importNode(svgElement, true));
return new SVGReplacedElement(svgImage, cssWidth, cssHeight);
}
return this.superFactory.createReplacedElement(layoutContext, blockBox, userAgentCallback, cssWidth, cssHeight);
}
And to draw the images i use:
#Override
public void paint(RenderingContext renderingContext, ITextOutputDevice outputDevice,
BlockBox blockBox) {
PdfContentByte cb = outputDevice.getWriter().getDirectContent();
float width = cssWidth / outputDevice.getDotsPerPoint();
float height = cssHeight / outputDevice.getDotsPerPoint();
PdfTemplate template = cb.createTemplate(width, height);
Graphics2D g2d = template.createGraphics(width, height);
PrintTranscoder prm = new PrintTranscoder();
TranscoderInput ti = new TranscoderInput(svg);
prm.transcode(ti, null);
PageFormat pg = new PageFormat();
Paper pp = new Paper();
pp.setSize(width, height);
pp.setImageableArea(0, 0, width, height);
pg.setPaper(pp);
prm.print(g2d, pg, 0);
g2d.dispose();
PageBox page = renderingContext.getPage();
float x = blockBox.getAbsX() + page.getMarginBorderPadding(renderingContext, CalculatedStyle.LEFT);
float y = (page.getBottom() - (blockBox.getAbsY() + cssHeight)) + page.getMarginBorderPadding(
renderingContext, CalculatedStyle.BOTTOM);
x /= outputDevice.getDotsPerPoint();
y /= outputDevice.getDotsPerPoint();
cb.addTemplate(template, x, y);
}
An idea of the scaling. 100 images take 2 seconds, 5000 images take about 42 seconds on an i5 8gb RAM.
So is there a way to store a drawn SVG in memory and paste it more quickly or something? Because right now it seems to take all images as separate images and eat all my memory and take forever.
Managed to optimize the memory and speed by doing two things.
I pre-generated the SVGDocuments in the createReplacedElement method which sped it up a bit.
The main improvement was pre-generating all pdfTemplates for all the images. This greatly increased speed as the templates already contained the rendered images.
The rendering of all regular text is still slow, so I might turn down the DPI.
EDIT: further optimization see Is there any way improve the performance of FlyingSaucer?

How to watermark PDFs using text or images?

I have a bunch of PDF documents in a folder and I want to augment them with a watermark. What are my options from a Java serverside context?
Preferably the watermark will support transparency. Both vector and raster is desirable.
Please take a look at the TransparentWatermark2 example. It adds transparent text on each odd page and a transparent image on each even page of an existing PDF document.
This is how it's done:
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
int n = reader.getNumberOfPages();
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
// text watermark
Font f = new Font(FontFamily.HELVETICA, 30);
Phrase p = new Phrase("My watermark (text)", f);
// image watermark
Image img = Image.getInstance(IMG);
float w = img.getScaledWidth();
float h = img.getScaledHeight();
// transparency
PdfGState gs1 = new PdfGState();
gs1.setFillOpacity(0.5f);
// properties
PdfContentByte over;
Rectangle pagesize;
float x, y;
// loop over every page
for (int i = 1; i <= n; i++) {
pagesize = reader.getPageSizeWithRotation(i);
x = (pagesize.getLeft() + pagesize.getRight()) / 2;
y = (pagesize.getTop() + pagesize.getBottom()) / 2;
over = stamper.getOverContent(i);
over.saveState();
over.setGState(gs1);
if (i % 2 == 1)
ColumnText.showTextAligned(over, Element.ALIGN_CENTER, p, x, y, 0);
else
over.addImage(img, w, 0, 0, h, x - (w / 2), y - (h / 2));
over.restoreState();
}
stamper.close();
reader.close();
}
As you can see, we create a Phrase object for the text and an Image object for the image. We also create a PdfGState object for the transparency. In our case, we go for 50% opacity (change the 0.5f into something else to experiment).
Once we have these objects, we loop over every page. We use the PdfReader object to get information about the existing document, for instance the dimensions of every page. We use the PdfStamper object when we want to stamp extra content on the existing document, for instance adding a watermark on top of each single page.
When changing the graphics state, it is always safe to perform a saveState() before you start and to restoreState() once you're finished. You code will probably also work if you don't do this, but believe me: it can save you plenty of debugging time if you adopt the discipline to do this as you can get really strange effects if the graphics state is out of balance.
We apply the transparency using the setGState() method and depending on whether the page is an odd page or an even page, we add the text (using ColumnText and an (x, y) coordinate calculated so that the text is added in the middle of each page) or the image (using the addImage() method and the appropriate parameters for the transformation matrix).
Once you've done this for every page in the document, you have to close() the stamper and the reader.
Caveat:
You'll notice that pages 3 and 4 are in landscape, yet there is a difference between those two pages that isn't visible to the naked eye. Page 3 is actually a page of which the size is defined as if it were a page in portrait, but it is rotated by 90 degrees. Page 4 is a page of which the size is defined in such a way that the width > the height.
This can have an impact on the way you add a watermark, but if you use getPageSizeWithRotation(), iText will adapt. This may not be what you want: maybe you want the watermark to be added differently.
Take a look at TransparentWatermark3:
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
int n = reader.getNumberOfPages();
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
stamper.setRotateContents(false);
// text watermark
Font f = new Font(FontFamily.HELVETICA, 30);
Phrase p = new Phrase("My watermark (text)", f);
// image watermark
Image img = Image.getInstance(IMG);
float w = img.getScaledWidth();
float h = img.getScaledHeight();
// transparency
PdfGState gs1 = new PdfGState();
gs1.setFillOpacity(0.5f);
// properties
PdfContentByte over;
Rectangle pagesize;
float x, y;
// loop over every page
for (int i = 1; i <= n; i++) {
pagesize = reader.getPageSize(i);
x = (pagesize.getLeft() + pagesize.getRight()) / 2;
y = (pagesize.getTop() + pagesize.getBottom()) / 2;
over = stamper.getOverContent(i);
over.saveState();
over.setGState(gs1);
if (i % 2 == 1)
ColumnText.showTextAligned(over, Element.ALIGN_CENTER, p, x, y, 0);
else
over.addImage(img, w, 0, 0, h, x - (w / 2), y - (h / 2));
over.restoreState();
}
stamper.close();
reader.close();
}
In this case, we don't use getPageSizeWithRotation() but simply getPageSize(). We also tell the stamper not to compensate for the existing page rotation: stamper.setRotateContents(false);
Take a look at the difference in the resulting PDFs:
In the first screen shot (showing page 3 and 4 of the resulting PDF of TransparentWatermark2), the page to the left is actually a page in portrait rotated by 90 degrees. iText however, treats it as if it were a page in landscape just like the page to the right.
In the second screen shot (showing page 3 and 4 of the resulting PDF of TransparentWatermark3), the page to the left is a page in portrait rotated by 90 degrees and we add the watermark as if the page is in portrait. As a result, the watermark is also rotated by 90 degrees. This doesn't happen with the page to the right, because that page has a rotation of 0 degrees.
This is a subtle difference, but I thought you'd want to know.
If you want to read this answer in French, please read Comment créer un filigrane transparent en PDF?
Best option is iText. Check a watermark demo here
Important part of the code (where the watermar is inserted) is this:
public class Watermark extends PdfPageEventHelper {
#Override
public void onEndPage(PdfWriter writer, Document document) {
// insert here your watermark
}
Read carefully the example.
onEndPage() method will be something like (in my logo-watermarks I use com.itextpdf.text.Image;):
Image image = Image.getInstance(this.getClass().getResource("/path/to/image.png"));
// set transparency
image.setTransparency(transparency);
// set position
image.setAbsolutePosition(absoluteX, absoluteY);
// put into document
document.add(image);

iTextPDF: changing the Table Alignment dynamically

I want to dynamically align the iText PdfTable.
How to set the x and y position based alignment in iTextPDF.
PdfPCell cell;
cell = new PdfPCell(testTable);
cell.setFixedHeight(44f);
cell.setColspan(3);
cell.setBorder(0);
table.addCell(cell);
table1.addCell(table);
please look in to this example...
public static void Main() {
// step 1: creation of a document-object
Document document = new Document();
try {
// step 2:
// we create a writer that listens to the document
// and directs a PDF-stream to a file
PdfWriter writer = PdfWriter.getInstance(document, new FileStream("Chap1002.pdf", FileMode.Create));
// step 3: we open the document
document.Open();
// step 4: we grab the ContentByte and do some stuff with it
PdfContentByte cb = writer.DirectContent;
// we tell the ContentByte we're ready to draw text
cb.beginText();
// we draw some text on a certain position
cb.setTextMatrix(100, 400);
cb.showText("Text at position 100,400.");
// we tell the contentByte, we've finished drawing text
cb.endText();
}
catch(DocumentException de) {
Console.Error.WriteLine(de.Message);
}
catch(IOException ioe) {
Console.Error.WriteLine(ioe.Message);
}
// step 5: we close the document
document.Close();
}
}
Please take a look at the C# port of the examples of chapter 4 of my book: http://tinyurl.com/itextsharpIIA2C04
You can add the table to a ColumnText object and add the column at an absolute position:
ColumnText column = new ColumnText(writer.DirectContent);
column.AddElement(table);
column.SetSimpleColumn(llx, lly, urx, ury);
column.Go();
In this snippet llx, lly and urx, ury are the coordinates of the lower-left corner and the upper-right corner of the column on the page (see the ColumnTable example).
In the PdfCalendar example, another method is used:
table.WriteSelectedRows(0, -1, x, y, writer.DirectContent);
The first parameters define which rows need to be drawn (0 to -1 means all rows), x and y define the absolute position.

How do I draw graphics to PDF using iText?

I am trying to complete an example that draws graphics and writes them to PDF, but I keep getting errors that the PDF has no pages. if I add something simple with document.add() after opening it works fine, I just never see the graphics. Here is my code:
Document document = new Document();
PdfWriter writer = new PdfWriter();
response.setContentType("application/pdf");
response.setHeader("Content-Disposition",
" attachment; filename=\"Design.pdf\"");
writer = PdfWriter.getInstance(document, response.getOutputStream());
document.open();
PdfContentByte cb = writer.getDirectContent();
Graphics2D graphics2D = cb.createGraphics(36, 54);
graphics2D.drawString("Hello World", 36, 54);
graphics2D.dispose();
document.close();
Do I have to do something else to add the graphic to the document or is my syntax incorrect?
I am not an expert in IText, but last week I tryed to draw some circles with it. So this is what I have noticed during my tests:
If you draw graphics, you must (or lets say I must when I tryed it) "wrap" the graphics commands in a section starting with saveState() and ending with restoreState(), es well as I needed to invoke fillStroke() -- if I do not invoke fillStroke() then nothing was drawn.
Example
private void circle(float x, float y, PdfWriter writer) {
PdfContentByte canvas = writer.getDirectContent();
canvas.saveState();
canvas.setColorStroke(GrayColor.BLACK);
canvas.setColorFill(GrayColor.BLACK);
canvas.circle(x, y, 2);
canvas.fillStroke();
canvas.restoreState();
}
#Test
public void testPossition() throws DocumentException, IOException {
OutputStream outputStream = FileUtil.openOutputStream("testPosition.pdf");
//this is my personal file util, but it does not anything more
//then creating a file and opening the file stream.
Document document = new Document(PageSize.A4, 50, 50, 50, 50);
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
markPosition(100, 100, writer);
document.add(new Paragraph("Total: 595 x 842 -- 72pt (1 inch)"));
document.close();
outputStream.flush();
outputStream.close();
}
private void markPosition(float x, float y, PdfWriter writer)
throws DocumentException, IOException {
placeChunck("x: " + x + " y: " + y, x, y, writer);
circle(x, y, writer);
}
private void placeChunck(String text, float x, float y, PdfWriter writer)
throws DocumentException, IOException {
PdfContentByte canvas = writer.getDirectContent();
BaseFont font = BaseFont.createFont(BaseFont.HELVETICA,
BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
canvas.saveState();
canvas.beginText();
canvas.moveText(x, y);
canvas.setFontAndSize(font, 9);
canvas.showText(text);
canvas.endText();
canvas.restoreState();
}
But PdfContentByte (canvas) has much more functions, for example rectangle.
Does Document doc = new Document(PageSize.A4);
make any difference?
I don't know if you need to add a Paragraph like this:
doc.add(new Paragraph(...));
Also we use doc.add(ImgRaw); to add images.
Without going too far into it, I think your general approach is fine. I think what might be happening here is that the Graphics2D origin is different from the PDF origin, so maybe you need to change the call to drawString() so it uses 0,0 as the location??
I think the problem is that directcontent writes directly to the page object. This way you can add backgrounds or backdrop images. Try adding a new page (doc.newPage()) before writing to the directcontent.
Have you tried drawing operations on the g2d object that just use shapes instead of text? That would eliminate the possibility of something odd going on with font selection or something like that.
iText In Action Chapter 12 has exactly what you are looking for - it really is worth picking up. Preview of Chapter 12
I just put together the following unit test against the latest HEAD of iText:
Document document = new Document();
PdfWriter writer = new PdfWriter();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writer = PdfWriter.getInstance(document, baos);
document.open();
PdfContentByte cb = writer.getDirectContent();
Graphics2D graphics2D = cb.createGraphics(36, 54);
graphics2D.setColor(Color.black);
graphics2D.drawRect(0, 0, 18, 27);
Font font = new Font("Serif", Font.PLAIN, 10);
graphics2D.setFont(font);
graphics2D.drawString("Yo Adrienne", 0, 54);
graphics2D.dispose();
document.close();
TestResourceUtils.openBytesAsPdf(baos.toByteArray());
And it works fine - I get a small black rectangle in the lower left hand corner of the page, plus text. Note that I am specifying X=0 for my drawString method (you were specifying 36 which causes the text to render outside of the image bounds). Note also that I explicitly specified a font - if I leave that out, it still renders, but it's usually a great idea to not trust the defaults for that sort of thing. Finally, I explicitly set the foreground color - again, not truly necessary, but trusting defaults can be scary.
So I'd have to say that the core issue here was the placement of the text at x=36.
In none of my tests was I able to create an error saying that the PDF has no pages - can you post the stack trace of the exception you are getting?
I can't imagine that adding a paragraph to the document makes any difference to this (that's the sort of bug that would have gotten taken care of long, long ago)

Categories

Resources