Add named destinations to an existing PDF document with iText - java

I have a PDF previously created with FOP, and I need to add some named destinations to it so later another program can open and navigate the document with the Adobe PDF open parameters, namely the #namedest=destination_name parameter.
I don't need to add bookmarks or other dynamic content but just some destinations with a name and thus injecting a /Dests collection with names defined in the resulting PDF.
I use iText 5.3.0 and I read the chapter 7 of iText in Action (2nd edition), but still I cannot figure it out how to add the destinations and so use them with #nameddest in a browser.
I'm reading and manipulating the document with PdfReader and PdfStamper. I already know in advance where to put every destination after having parsed the document with a customized Listener and a PdfContentStreamProcessor, searching for a specific text marker on each page.
This is a shortened version of my code:
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new BufferedOutputStream(dest));
// search text markers for destinations, page by page
for (int i=1; i<reader.getNumberOfPages(); i++) {
// get a list of markers for this page, as obtained with a custom Listener and a PdfContentStreamProcessor
List<MyDestMarker> markers = ((MyListener)listener).getMarkersForThisPage();
// add a destination for every text marker in the current page
Iterator<MyDestMarker> it = markers.iterator();
while(it.hasNext()) {
MyDestMarker marker = it.next();
String name = marker.getName();
String x = marker.getX();
String y = marker.getY();
// create a new destination
PdfDestination dest = new PdfDestination(PdfDestination.FITH, y); // or XYZ
// add as a named destination -> does not work, only for new documents?
stamper.getWriter().addNamedDestination(name, i /* current page */, dest);
// alternatives
PdfContentByte content = stamper.getOverContent(i);
content.localDestination(name, dest); // doesn't work either -> no named dest found
// add dest name to a list for later use with Pdf Open Parameters
destinations.add(name);
}
}
stamper.close();
reader.close();
I also tried creating a PdfAnnotation with PdfFormField.createLink() but still, I just manage to get the annotation but with no named destination defined it does not work.
Any solution for this? Do I need to add some "ghost" content over the existing one with Chunks or something else?
Thanks in advance.
edit 01-27-2016:
I recently found an answer to my question in the examples section of iText website, here.
Unfortunately the example provided does not work for me if I test it with a pdf without destinations previously defined in it, as it is the case with the source primes.pdf which already contains a /Dests array. This behaviour appears to be consistent with the iText code, since the writer loads the destinations in a map attribute of PdfDocument which is not "inherited" by the stamper on closing.
That said, I got it working using the method addNamedDestination() of PdfStamper added with version 5.5.7; this method loads a named destination in a local map attribute of the class which is later processed and consolidated in the document when closing the stamper.
This approach reaised a new issue though: the navigation with Pdf Open Parameters (#, #nameddest=) works fine with IE but not with Chrome v47 (and probably Firefox, too). I tracked the problem down to the order in which the dests names are defined and referenced inside the document; the stamper uses a HashMap as the container for the destinations, which of course does not guarantee the order of its objects and for whatever reason Chrome refuse to recognise destinations not listed in "natural" order. So, the only way I got it to work is replacing the namedDestinations HashMap with a natural-ordered TreeMap.
Hope this help others with the same issue.

I 've been in the same need for my project previously. Had to display and navigate pdf document with acrobat.jar viewer. To navigate i needed the named destinations in the pdf. I have looked around the web for a possible solution, but no fortunate for me. Then I this idea strikes my mind.
I tried to recreate the existing pdf with itext, navigating through each page and adding localdestinations to each page and i got what I wanted. below is the snip of my code
OutputStream outputStream = new FileOutputStream(new File(filename));
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
PdfContentByte cb = writer.getDirectContent();
PdfOutline pol = cb.getRootOutline();
PdfOutline oline1 = null;
InputStream in1 = new FileInputStream(new File(inf1));
PdfReader reader = new PdfReader(in1);
for (int i = 1; i <= reader.getNumberOfPages(); i++)
{
document.newPage();
document.setMargins(0.0F, 18.0F, 18.0F, 18.0F);
PdfImportedPage page = writer.getImportedPage(reader, i);
document.add(new Chunk(new Integer(i).toString()).setLocalDestination(new Integer(i).toString()));
System.out.println(i);
cb.addTemplate(page, 0.0F, 0.0F);
}
outputStream.flush();
document.close();
outputStream.close();
Thought it would help you.

Related

Remove or Update added Image icon from pdf page using OpenPdf based on iText Core

I have added an icon as Image object into PDF page with OpenPdf that is based on iText core. Here is my code
// inout stream from file
InputStream inputStream = new FileInputStream(file);
// we create a reader for a certain document
PdfReader reader = new PdfReader(inputStream);
// we create a stamper that will copy the document to a new file
PdfStamper stamp = new PdfStamper(reader, new FileOutputStream(file));
// adding content to each page
PdfContentByte over;
// get watermark icon
Image img = Image.getInstance(PublicFunction.getByteFromDrawable(context, R.drawable.ic_chat_lawone_new));
img.setAnnotation(new Annotation(0, 0, 0, 0, "https://github.com/LibrePDF/OpenPDF"));
img.setAbsolutePosition(pointF.x, pointF.y);
img.scaleAbsolute(50, 50);
// get page file number count
int pageNumbers = reader.getNumberOfPages();
if (pageNumbers < pageIndex) {
// closing PdfStamper will generate the new PDF file
stamp.close();
throw new PDFException("page index is out of pdf file page numbers", new Throwable());
}
// annotation added into target page
over = stamp.getOverContent(pageIndex);
if (over == null) {
stamp.close();
throw new PDFException("getUnderContent is null", new Throwable());
}
over.addImage(img);
// closing PdfStamper will generate the new PDF file
stamp.close();
// close reader
reader.close();
now I need to delete or update the color of added image object on user click, I have the click function that returns MotionEvent, now I need to delete or update or replace added image object.
Any Idea?!
In your parallel OpenPDF issue 464 you posted additionally:
Here my progress
Now I can achieve the XObjects added into pdf file, and I can remove them from pdf page this way:
// inout stream from file
InputStream inputStream = new FileInputStream(file);
// we create a reader for a certain document
PdfReader pdfReader = new PdfReader(inputStream);
// get page file number count
int pageNumbers = pdfReader.getNumberOfPages();
// we create a stamper that will copy the document to a new file
PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileOutputStream(file));
// get page
PdfDictionary page = pdfReader.getPageN(currPage);
PdfDictionary resources = page.getAsDict(PdfName.RESOURCES);
// get page resources
PdfArray annots = resources.getAsArray(PdfName.ANNOTS);
PdfDictionary xobjects = resources.getAsDict(PdfName.XOBJECT);
// remove Xobjects
for (PdfName key : xobjects.getKeys()) {
xobjects.remove(key);
}
// remove annots
for (PdfObject element : annots.getElements()) {
annots.remove(0);
}
// close pdf stamper
pdfStamper.close();
// close pdf reader
pdfReader.close();
So the XObjects will remove from the screen, but still there is a problem!!!
When I remove them and try to add a new one, last deleted object appears and add into the pdf page! REALLY!!! :))
I think there should be another place that these objects should be removed from.
What happens here is that you indeed do remove the bitmap image resources from the page:
for (PdfName key : xobjects.getKeys()) {
xobjects.remove(key);
}
but you don't remove the instructions for drawing these resources from the content stream. This has two consequences:
Your PDF strictly speaking becomes invalid as a resource is referenced from the content stream which is not defined in the page resources. Depending on the viewer in question this might result in warning messages.
If the PDF is further processed and some new XObject is added to the same page with the same resource name, the original image drawing instruction now again has a resource to draw and makes it appear at the original position once more.
This explains your observation:
When I remove them and try to add a new one, last deleted object appears and add into the pdf page! REALLY!!! :))
I assume you used the same source image in your test, so it looked like the original image appeared again at the original position when actually the new image appeared there.
Thus, instead of merely removing the image XObject, you have the choice of either
also removing the XObject drawing instruction from the content stream or
replacing the image XObject by an invisible XObject instead.
The former option in general is non-trivial, in particular if your PDF-tool-to-be also allows other changes of the page content. In case of iText 5 or iText 7 I'd propose using the PdfContentStreamEditor (see here) / PdfCanvasEditor (see here) class to find and remove Do operations from the page content streams but I have no OpenPDF version of that class yet.
What you can do quite easily, though, is replacing the image resources by form XObjects without any content:
PdfTemplate pdfTemplate = PdfTemplate.createTemplate(pdfStamper.getWriter(), 1, 1);
// replace Xobjects
for (PdfName key : xobjects.getKeys()) {
xobjects.put(key, pdfTemplate.getIndirectReference());
}
(RemoveImage test testRemoveImageAddedLikeHamidReza)
Beware, replacing all XObjects by empty XObjects has an obvious disadvantage, it replaces all XObjects, not merely the ones your tool created before! Thus, if the original PDFs processed by your tool also drew XObjects in their immediate content streams, those XObjects also are rendered invisible. If you don't want that, you need some specific criteria to recognize the image XObjects you added and only replace them.
Furthermore, there are other problems afoot: Each time you process the OverContent of a page in a PdfStamper, the pre-existing content of that page is wrapped into a q / Q (save-graphics-state / restore-graphics-state) envelope to prevent changes of the graphics state in that previous content bleed through and mix up your OverContent additions. Thus, if you manipulate a file many times in your tool, the original page content may be wrapped in a fairly deep nesting of such envelopes. Unfortunately PDF readers may support only a limited nesting depth, e.g. ISO 32000-1 mentions a maximum depth of 28 envelopes.
Thus, if you still have the chance to overhaul your design, you should consider putting the images into annotation appearances instead of into the page content. After all, you already do generate annotations, currently merely to transport a link, so you could also generate annotations with appearances.

How to Navigate from one page to another page in same PDF file using links in IText?

I have 2 PDF Files, One PDF File is created using Apache FOP and another PDF File is created by converting a word document. I need to merge both these PDF's into single PDF File using IText, which is already been done. In addition to that i need to create a link in my first PDF which has to take me to the first page of the second PDF which has been merged.
The Problem here is i need to create the link from the first PDF by looking for a string/text like "Go To Page", if i find that string in my first PDF , i need to replace that string as a link , and on clicking on that link needs to take me to the desired page.
The below code actually does the page navigating job for me, but i'm not able to do that by creating a link as per my requirement.
Is it possible to find the rectangle coordinates of the string/text, so that i can pass those coordinates to create the link? Or Is there any way to replace my string as a link using itext?
I'm using IText 5.x
public void pdfNavigation(String src, String dest) throws IOException, DocumentException
{
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
PdfDestination d1 = new PdfDestination(PdfDestination.FIT);
Rectangle rect = new Rectangle(0, 806, 595, 842);
PdfAnnotation annotation1= PdfAnnotation.createLink(stamper.getWriter(),
rect,
PdfAnnotation.HIGHLIGHT_INVERT, 10, d1);
stamper.addAnnotation(annotation1, 1);
PdfDestination d2 = new PdfDestination(PdfDestination.FIT);
PdfAnnotation annotation2= PdfAnnotation.createLink(stamper.getWriter(),
rect,
PdfAnnotation.HIGHLIGHT_PUSH, 1, d2);
stamper.addAnnotation(annotation2, 4);
stamper.close();
}
Thanks for Help in advance !
You can use Chunk class for this. I think you will get your answer from this link.
You can also use named actions for this like the below code which you can find in iTEXT documentation (Link is given below).
Paragraph p = new Paragraph()
.add("Go to last page")
.setAction(PdfAction.createNamed(PdfName.LastPage));
document.add(p);
p = new Paragraph()
.add("Go to first page")
.setAction(PdfAction.createNamed(PdfName.FirstPage));
document.add(p);
https://itextpdf.com/en/resources/books/itext-7-building-blocks/chapter-6-creating-actions-destinations-and-bookmarks
https://itextpdf.com/en/resources/examples/itext-7/chapter-6-actions-destinations-bookmarks#2568-c06e02_namedaction.java

How to replace text in pdf using java

What I am trying to achieve is to replace a text in pdf file. I have the following code:
PdfReader reader = new PdfReader("test.pdf");
PdfDictionary dict = reader.getPageN(1);
PdfObject object = dict.getDirectObject(PdfName.CONTENTS);
if (object instanceof PRStream)
{
PRStream stream = (PRStream) object;
byte[] data = PdfReader.getStreamBytes(stream);
System.out.println(new String(data));
stream.setData(new String(data).replace("application", "HELLO WORLD").getBytes());
}
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("test-output.pdf"));
stamper.close();
reader.close();
When I trying to print out to see the data (System.out.println(new String(data))), "application" is showing as "ap)-4(plica)-3(tion", that's the reason why I failed to replace the text, any idea or other method that can achieve what I trying to achieve?
You will not be able to do this with iText.
Believe me, this is one of the most frustrating discoveries about PDFs: you can build them with iText, but you cannot go back later and replace text with something else, as you have in your example.
There really is not much you can do about it. Once text is there, you can't modify it.
All that notwithstanding, you can usually ADD new content (text, images, etc.) to an existing PDF. So... if you can alter the universe slightly and create a PDF with empty space in the correct size, you can go back later and use the PdfStamper class to "stamp" on another layer of graphical content.
More on this can be found in the iText documentation, and in this fine question:
How to add Content to a PDF using iText PdfStamper

Itext error IllegalArgumentException: Tagging must be set before opening the document

I am using itext to fill a template pdf, but I want to add tag to the template pdf and to the elements that I am trying to fill into it.
The first step that I made is trying to insert tag for the element that I am trying to insert into, here my code:
PdfReader reader = new PdfReader("myTemplatepath");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfStamper stamper = new PdfStamper(reader, baos);
PdfWriter writer = stamper.getWriter();
writer.setTagged(); //Make document tagged
But when I am using writer.setTagged() I have the following error:
java.lang.IllegalArgumentException: Tagging must be set before opening
the document
I saw, in this Topic that the problem is that PdfStamper doesn't support tagging,and the best solution is to create a new PDF and tagged it so my question is :
Since the topic is from 2007, there is any new implementation about this?
If not, what is the best way to doing so? The template that I have is not so simple and It has editable elements ( that i fill automaticatily).

How can I insert a word in a sentence of a PDF content in Java?

I want to add a word in a sentence of a PDF content.
For example:
This is a sample content.
I want to insert a word in that content like this output.
This is a nice sample content.
This is a sample code for itextPdf that I found in the internet. Assumed that the content already exists and we want to modify it by adding a text in a sentence.
try {
//Create PdfReader instance.
PdfReader pdfReader =
new PdfReader(SRC);
//Create PdfStamper instance.
PdfStamper pdfStamper = new PdfStamper(pdfReader,
new FileOutputStream(DEST));
//Create BaseFont instance.
BaseFont baseFont = BaseFont.createFont(
BaseFont.TIMES_ROMAN,
BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
//Get the number of pages in pdf.
int pages = pdfReader.getNumberOfPages();
System.out.println(pdfStamper.getOverContent(1));
//Iterate the pdf through pages.
for(int i=1; i<=pages; i++) {
//Contain the pdf data.
PdfContentByte pageContentByte =
pdfStamper.getOverContent(i);
pageContentByte.setFlatness(89);
pageContentByte.beginText();
//Set text font and size.
pageContentByte.setFontAndSize(baseFont, 14);
pageContentByte.setTextMatrix(50, 720);
//Write text
pageContentByte.setWordSpacing(12);
pageContentByte.showText("hello world");
pageContentByte.endText();
}
//Close the pdfStamper.
pdfStamper.close();
System.out.println("PDF modified successfully.");
} catch (Exception e) {
e.printStackTrace();
}
I tried itextPdf and PdfBox but neither of them would work.
I can get the objects in the pdf document using PDFStreamParser of pdfbox.
PDFOperator{Td}, COSArray{[COSString{Name }, COSFloat{163.994}, COSString{____________________________________________________}, COSFloat{-8.03223}, COSString{________________________________________________________}]}, PDFOperator{TJ}, COSInt{19}, PDFOperator{TL}, PDFOperator{T*}, COSArray{[COSString{T}, COSInt{36}, COSString{itle}, COSFloat{0.997925}, COSString{ }, COSFloat{-94.9982}, COSString{_____________________________________________________________________________________________________________}]}, PDFOperator{TJ}, PDFOperator{T*}, COSArray{[
How can I implement a code that inserts a text?
Not.
Pdf is not a wysiwyg format. Internally, it's more like a file containing code. It has instructions for moving around a cursor, and drawing text and graphics at the tip of the cursor.
Then there's the fact that most instructions get packaged into "objects". All objects get placed in a dictionary that uses byte-offsets to reference them.
So inserting anything in a pdf-document will cause problems on 2 levels.
You would mess up the byte-offset of everything in the document
You would need to unscramble all the existing rendering operations to make sense of the document (to derive structure like lines of text, paragraph, etc) so that you can properly re-flow the content after you've inserted something.
Hence my short answer. You can't. And that immediately explains why none of the pdf toolkits you've tried can do it. It's simply an insanely hard task.

Categories

Resources