Converting the zoom level of all links in a PDF document - pdf

I have a document in PDF format. The document consists of several chapters, sections, etc.
Within the text, there are references to other chapters or sections; for instance:
We will see in chapter 15 that ...
The notion of ..., mentioned in section 7.1, ...
The references are "links"; that is, when you click on them, it jumps to the corresponding text.
However, the links change the zoom level of the PDF to "Fit Page", as shown in the following dialog box (the screenshot is taken in Adobe Acrobat):
I don't like this behavior, and prefer that the zoom level does not change. To this end, there's an option called "Inherit Zoom".
The problem is that there are too many links in the document to change them manually. So, I want to somehow programmatically change the zoom level of all links in the PDF document to "Inherit Zoom".
Is this possible using iText or similar libraries?

You may try Docotic.Pdf Library for this. To accomplish your task following should be done:
Enumerate actions somehow.
Reset action zoom level to 0 (it means that zoom remains unchanged)
Reset action zoom level function is common and may look like this:
private static void resetActionZoom(PdfAction action)
{
PdfGoToAction goToAction = action as PdfGoToAction;
if (goToAction == null)
return;
// process only actions with FitPage zoom level
if (goToAction.View.Zoom != PdfZoom.FitPage)
return;
goToAction.View.SetZoom(0); // now zoom will remain unchanged after click by link
}
Here is a sample that enumerates all actions in PDF document and reset zoom level for each:
PdfDocument pdf = new PdfDocument("path_to_your_file.pdf");
foreach (PdfAction action in pdf.Actions)
resetActionZoom(action);
pdf.Save("UpdateAllActions.pdf");
Another (and more accurate) way is to enumerate all links on each page and update associated actions the same way:
PdfDocument pdf = new PdfDocument("path_to_your_file.pdf");
foreach (PdfPage page in pdf.Pages)
{
foreach (PdfWidget widget in page.Widgets)
{
PdfActionArea actionArea = widget as PdfActionArea;
if (actionArea == null)
continue;
resetActionZoom(actionArea.Action);
}
}
pdf.Save("UpdatePageLinks.pdf");

Foxit reader
Preferences/Page Display
check "Forbid the change of the current Zoom factor during execution of "Go to Destination actions(these actions can be launched from bookmarks)"

Related

Adding an Annotation to a PdfFormXObject so the Annotation is reusable

I'm using iText 7 to construct reusable PDF components that I reuse across multiple pages within a document. I'm using iText-dotnet for this task (v7), using F# as the language. (This shouldn't be hard to follow for non-F# people as it's just iText calls :D)
I know how to add annotations to a Page, that isn't the issue. Adding the annotation to the page is as simple as page.AddAnnotation(newAnnotation).
Where I'm having difficulty, is that there is no "Page" associated with a Canvas when you are using a PdfFormXObject() to render a Pdf fragment.
let template = new PdfFormXObject(rect)
let templateCanvas = PdfCanvas(template, pageContext.Canvas.GetPdfDocument())
let newCanvas = new Canvas(templateCanvas, rect)
Once I have the new Canvas, I try to write to the Canvas and add the Annotation via Page.AddAnnotation(). The problem is that there is no Page attached to the PdfFormXObject!
// Create the destination and annotation (destPage is the pageNumber)
let dest = PdfExplicitDestination.CreateFitB(destPage)
let action = PdfAction.CreateGoTo(dest)
let annotation = PdfLinkAnnotation(rect)
let border = iText.Kernel.Pdf.PdfAnnotationBorder(0f, 0f, 0f)
// set up the Annotation with action and display information
annotation
.SetHighlightMode(PdfAnnotation.HIGHLIGHT_PUSH)
.SetAction(action)
.SetBorder(border)
|> ignore
// Try adding the annotation to the page BOOM! (There is *NO* page (null) associated with newCanvas)
newCanvas.GetPage().AddAnnotation(annotation) |> ignore // HELP HERE: Is there another way to do this?
The issue is that I do not know of a different way to set the Annotation on the canvas. Is there a way to render the annotation and just add the annotation directly to the canvas as raw PDF instructions?
Alternatively, is there a way create a different reusable PDF fragment in iText so I can also reuse the GoTo annotation.
N.B. I could split off the annotations and then apply them every time I use the PdfFormXObject() on a new page, but that sort of defeats the purpose of reusing Pdf fragments (template) in my final PDF to reduce it's size.
If you can point me in the right direction, that would be great.
Again, this is not how to add an annotation to a Page(), that's easy. It's how to add an annotation to a PdfFormXObject (or similar mechanism that I'm unaware of for constructing rusable Pdf fragments).
-- As per John's comments below:
I cannot seem to find any reference to single use annotations.
I'm aware of the following example link, so I modified it to look like this:
private static void Main(string[] args)
{
try
{
PdfDocument pdfDocument = new PdfDocument(new PdfWriter("TestMultiLink.pdf"));
Document document = new Document(pdfDocument);
string destinationName = "MyForwardDestination";
// Create a PdfStringDestination to use more than once.
var stringDestination = new PdfStringDestination(destinationName);
for (int page = 1; page <= 50; page++)
{
document.Add(new Paragraph().SetFontSize(100).Add($"{page}"));
switch (page)
{
case 1: // First use of PdfStringDestination
document.Add(new Paragraph(new Link("Click here for a forward jump", stringDestination).SetFontSize(20)));
break;
case 3: // Re-use the stringDestination
document.Add(new Paragraph(new Link("Click here for a forward jump", stringDestination).SetFontSize(10)));
break;
case 42:
pdfDocument.AddNamedDestination(destinationName, PdfExplicitDestination.CreateFit(pdfDocument.GetLastPage()).GetPdfObject());
break;
}
if (page < 50)
document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
document.Close();
}
catch (Exception e)
{
Console.WriteLine($"Ouch: {e.Message}");
}
}
If you dig into the iText source for iText.Layout.Link, you'll see that the String Destination is added as an Annotation. Therefore, I'm not sure if John's answer is true anymore.
Does anyone know how I can convert the Annotation to a Dictionary and how I would go about adding the PdfDictionary (raw) info into the PftFormXObject?
Thanks
#johnwhitington is correct.
Per PDF specification, annotations can only be added to a page, they cannot be added to a form XObject. It is not a limitation of iText or any other PDF library.
Annotations cannot be reused, each annotation is a distinct object.

getImportedPage without annotation

I want to use PdfStamper to enlarge every page, and copy some pages.
Enlarge every page:
for (int i = 1; i <= n; i++) {
page = reader.getPageN(i);
media = page.getAsArray(PdfName.CROPBOX);
if (media == null) {
media = page.getAsArray(PdfName.MEDIABOX);
}
crop = new PdfArray();
crop.add(new PdfNumber(0));
crop.add(new PdfNumber(0));
float w = media.getAsNumber(2).floatValue();
crop.add(new PdfNumber(media.getAsNumber(2).floatValue() * 2));
crop.add(new PdfNumber(media.getAsNumber(3).floatValue() * 1));
page.put(PdfName.MEDIABOX, crop);
page.put(PdfName.CROPBOX, crop);
stamper.getUnderContent(i).setLiteral(String.format("\nq 1 0 0 1 %.1f 0 cm\nq\n", w / 2));
stamper.getOverContent(i).setLiteral("\nQ\nQ\n");
}
Insert an empty page, and copy content from page 1.
stamper.insertPage(2, stamper.getReader().getPageSize(1))
PdfContentByte canvas = stamper.getOverContent(2);
PdfImportedPage ip = stamper.getImportedPage(reader, 1);
Image ipage = Image.getInstance(ip);
canvas.addImage(ipage);
But I found PdfImportedPage deletes annotations, and after I enlarge a page the annotations are in the wrong position.
How can I handle these annotations, both when copying a page and when enlarging.
Displaced Annotations
after I enlarge a page the annotations are in the wrong position.
The Cause Of Their Displacement
The cause for this is simple: You (attempt to) enlarge the page to the right and then you prepend a shift to the right for the static page content. But you don't move the annotations. Thus, the annotations remain in their original position while the static page content is moved away.
What Needs To Be Done
If you actually want to move the coordinates of the static content, you also have to move all the other coordinate arguments in the PDF which relate to that content. This means in particular annotations (multiple entries), destinations, and structure tree information.
A complete implementation thereof is beyond the scope of a stack overflow answer.
A Simpler Option
You (attempt to) enlarge the page to the right and then you prepend a shift to the right for the static page content. Why don't you simply enlarge the page half as far both to the left and the right and leave the content at its coordinates? Then you do not need to change any coordinates at all...
Vanished Annotations
PdfImportedPage deletes annotations
The Cause Of Their Vanishment
The getImportedPage implementations of both the basic PdfWriter and the PdfStamper merely copy the static page content, not the annotations or other extra s. The reason is that in these cases the page is imported as a form Xobject which can be drawn in the content of any content stream (of a page, of an annotation, of another Xobject, ...) but which simply cannot have annotations of its own.
What Can Be Done
You can either flatten all annotations in a preparation step (using a PdfStamper and calling setAnnotationFlattening and setFormFlattening) or duplicate the page in a preparation step (using PdfCopy and its getImportedPage).
The former option is keeping document level information but makes the dynamic annotation contents static. The latter option looses document level information but keeps annotations dynamic.

Generating and Downloading a PDF

I am developing an Android app for flight reservation and i am using firebase Database for storing and retriving data. I need to generate a PDF of Ticket and contents should be changed as per passenger details and stored in local directory. I have a template of Ticket. What should i do?
Thank you in advance.
Since Android 5 you can use PdfDocument and its friend classes to generate a PDF document on Android device. The official documentation is here. There is no library to use a template with PdfDocument. You have to use some drawing primitive to accomplish your task.
Here is a sample to generate a PDF with a single page A4:
// create a new document
PdfDocument document = new PdfDocument();
// crate a page description
PageInfo pageInfo = new PageInfo.Builder(595, 842, 1).create();
// start a page
Page page = document.startPage(pageInfo);
Canvas canvas=page.getCanvas());
// draw something on the page
Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawCircle(50, 50, 30, paint);
// finish the page
document.finishPage(page);
// write the document content
document.writeTo(getOutputStream());
// close the document
document.close();
The page content is defined with Canvas object. Just another example. I hope this helps you.

Need alternative to local or remote goto/destinations with merged documents

BACKGROUND
I have a java program that analyzes data and creates a pdf report using itext 5.
I recently had to add a summary of major problems at the start of the document so a user would not have to read over a hundred pages to find problems. Problems are only discovered when serially looking through the data.
I solved the problem by creating 3 pdf documents and then merging them, a start/title pdf, the summary of problems pdf, and the body or analysis pdf. (Basically splitting the original document at the point I wanted to insert the summary)
I use PdfReader and PdfCopy to combine the documents. I am able to keep the chapter bookmarks OK.
THE PROBLEM
As I encounter a significant problem I add it to the 'summary' document. I want to add a link in the summary to point to the problem in the body.
I tried to use Chunk.setLocalDestination and setLocalGoto but realized why that did not work, so I tried using setLocalDestination and setRemoteGoto (with and without 'file://'), but that did not work either. (Also, I used the final pdf document name in the RemoteGoto, not the temporary pdf document name.)
I do not want to use bookmarks because that seems wrong and would not look right.
I am hoping someone could suggest an alternate method or make a suggestion.
To recap, in my current code a create a Chunk with setLocalDestination and that chunk goes into the 'body' document. At the same time I create a setRemoteGoto which is put into the summary document. I was hoping when they were combined the link would work, but when the link is clicked, you go to the first page of the combined document.
Thanks.....
PS I have both iText in action books
CLARIFICATION 3/5/2014
What I was calling 'bookmarks' are really Chapter class entities that are inserted into sections of the 3 documents as they are being created.
After saving the 3 documents, PdfReader is used to open each and PdfCopy is used to put them into a new, final document.
I get the data from the Chapters, which creates the 'bookmarks' on the left side of the Pdf reader used by the user, e.g. Acrobat Reader.
int thisPdfPages = reader.getNumberOfPages();
reader.consolidateNamedDestinations();
java.util.List<HashMap<String, Object>> bookmarks = SimpleBookmark.getBookmark(reader);
if (bookmarks != null) {
if (pageOffset != 0) {
if (debug3) auditLogger.log("Shifting pages by " + pageOffset );
SimpleBookmark.shiftPageNumbers(bookmarks, pageOffset, null);
}
masterBookmarks.addAll(bookmarks);
}
for (int i = 0; i < thisPdfPages;) {
page = copy.getImportedPage(reader, ++i);
stamp = copy.createPageStamp(page);
// add page numbers
ColumnText.showTextAligned(stamp.getUnderContent(), Element.ALIGN_CENTER, new Phrase(String.format("page %d of %d", start + i, totalPages)), 297.5f, 28, 0);
stamp.alterContents();
copy.addPage(page);
}
PRAcroForm form = reader.getAcroForm();
if (form != null) {
copy.copyAcroForm(reader);
}
When analyzing the data I have 2 documents open, a base document which contains all the details and a summary document which contains notable events over some thresholds.
//NOTE section is part of the 'body' document
//NOTE summaryPhrase is a part of the 'summary' document
String linkName = "summaryPf_" + networkid ;
//create Link target
section.add(new Chunk("CHANGE TO EMPTY STRING WHEN WORKING").setLocalDestination( linkName ));
//create Link
Chunk linkChunk = new Chunk( "[Link] " );
Font linkFont = new Font( regularFont );
linkFont.setColor(BaseColor.BLUE);
linkFont.setStyle( Font.UNDERLINE );
linkChunk.setFont( linkFont );
boolean useLocal = true;
// both local and remote goto's fail
if (useLocal) {
linkChunk.setLocalGoto( linkName);
} else {
// all permutations of setting filename fail,
// but it does bring up a permissions dialog when the link is clicked.
//String remotePdfName = "file://./" + pdfReportName ;
//String remotePdfName = "file://" + pdfReportName ;
//String remotePdfName = "file:" + pdfReportName ;
String remotePdfName = pdfReportName ;
linkChunk.setRemoteGoto( remotePdfName, linkName);
}
// add link to summary document
summaryPhrase.add( linkChunk );
summaryPhrase.add( String.format("There were %d devices with ping failures", summaryCount));
summaryPhrase.add( Chunk.NEWLINE );
}
If I use setLocalGoto, when you click the link in the final document you goto the first page.
If I use setRemoteGoto, a dialog ask permission to go to a document, but the document fails to open, tried several permutations on filename.

attach image to fdf using itextsharp

I'm working to refactor a PDF form web application that is using the Active PDF Toolkit and the FDFToolkit from Adobe. My goal is to use iTextSharp to:
Pre-populate the form fields with data from the database
Allow the user to attach a signature and/or barcode image via FDF
Item #1 is not the problem. Item #2 is the biggest challenge. Let me provide some background:
This is a web application which renders the PDF form once. After the initial load, there are 2 key buttons on the form which submit the PDF form to a URL with an action parameter in the query string. These buttons are called "Save" and "Sign". The Save button takes the FDF field dictionary and saves it to the database. The Sign button looks up the signature for the logged-in user and attaches the signature image to the FDF and writes the FDF to the HTTP Response.
The FDFToolkit supports attaching an image to a field using this method:
FDFSetAP(string bstrFieldName, short whichFace, string bstrFileName, short pageNum)
iTextSharp does not offer a comparable method in the FdfWriter class. I've considered subclassing the FdfWriter class and adding my own method to attach an image, but wanted to reach out here to see if anyone has had the same problem.
I have been able to overlay an image on top of a field using this method, but this is in the underlying PDF and not the FDF.
AcroFields.FieldPosition pos = _Stamper.AcroFields.GetFieldPositions("SIGNATUREFIELD").First();
Image signature = Image.GetInstance("Signature.gif");
image.SetAbsolutePosition(pos.position.Left, pos.position.Bottom);
image.ScaleToFit(pos.position.Width, pos.position.Height);
PdfContentByte pcb = _Stamper.GetOverContent(pos.page);
pcb.AddImage(image);
Thanks!
I've put images on forms by using the PdfStamper and making Pushbutton fields. You can replace your existing field with a Pushbutton field and set the Pushbutton to READ_ONLY so that it can't be pressed and it will look like a static image. This will keep the image you're trying to add as a field annotation instead of adding it to the page content.
using (PdfStamper stamper = new PdfStamper(new PdfReader(inputFile), File.Create(outputFile)))
{
AcroFields.FieldPosition fieldPosition = stamper.AcroFields.GetFieldPositions(fieldName)[0];
PushbuttonField imageField = new PushbuttonField(stamper.Writer, fieldPosition.position, fieldName);
imageField.Layout = PushbuttonField.LAYOUT_ICON_ONLY;
imageField.Image = iTextSharp.text.Image.GetInstance(imageFile);
imageField.ScaleIcon = PushbuttonField.SCALE_ICON_ALWAYS;
imageField.ProportionalIcon = false;
imageField.Options = BaseField.READ_ONLY;
stamper.AcroFields.RemoveField(fieldName);
stamper.AddAnnotation(imageField.Field, fieldPosition.page);
}