I am trying to copy one page from an existing .pdf file and paste it to a new document like this:
using (var writer = new PdfWriter(OutputFile))
{
var reader = new PdfReader("Templates//PDF_Template_Empty.pdf");
PdfDocument template = new PdfDocument(reader);
var titlepage = template.GetPage(1);
using (var pdf = new PdfDocument(writer))
{
pdf.AddPage(titlepage); // exception
But on .AddPage() it throws this exception :
iText.Kernel.PdfException: 'Page iText.Kernel.Pdf.PdfPage cannot be
added to document iText.Kernel.Pdf.PdfDocument, because it belongs to
document iText.Kernel.Pdf.PdfDocument.'
How can I fix this ?
A PDF page object usually has a number of related objects. If you only add the page itself to a new document and not the related objects, the resulting page would be incomplete.
Thus, iText 7 checks in AddPage whether the page in question has been created inside the target document or not, and in the latter case throws an exception to prevent missing dependent objects.
To copy pages across documents there is the PdfDocument method CopyPagesTo with many overloads. For you e.g.
PdfDocument template = new PdfDocument(reader);
using (var pdf = new PdfDocument(writer))
{
// copy template pages 1..1 to pdf as target page 1 onwards
template.CopyPagesTo(1, 1, pdf, 1);
}
(Beware, if there are extras on the page, you might want to choose an overload of that method which accepts an additional IPdfPageExtraCopier instance, e.g. for AcroForm fields a PdfPageFormCopier.)
Related
I'm using IText7 version 7.0.2.2, I'm new with it, I'm trying to merge several pdfs at the same time into one that I'm uploading first, that is working fine, the problem is when I try dynamically to insert some text in one of the pdfs and then merge it, I'm using PdfWriter to write some content into the pdf and then try to merge it, but I'm getting this exception: 'Cannot copy indirect object from the document that is being written.
This is some of the code I'm using:
private byte[] MergePdfForms( HttpPostedFileBase firstPdf, List<SectionAndPdfs> sectionsAndPdf)
{
var dest = new MemoryStream();
PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
PdfMerger merger = new PdfMerger(pdf);
firstSourcePdf = new PdfDocument(new PdfReader(keyValuePair.Value), new PdfWriter(dest));
Document document = new Document(firstSourcePdf);
document.Add(new Paragraph(sectionsAndPdf[i].Key).SetBackgroundColor(iText.Kernel.Colors.Color.GRAY));
merger.Merge(firstSourcePdf, 1, subPages); //I'm getting the exception here..
firstSourcePdf.Close();
}
This is a known bug in the class PdfDestination. It was fixed, and will be present in our next release. At the moment you can of course use the snapshot release, which should solve the problem.
I was about to use iTextSharp to fill out PDF form, when I've found out, that there is new kid on the block: iText 7.0. Naturally I gave it a try and... it didn't go well, unfortunately. Here is an issue. I have editable PDF form, that's need to be filled out programmatically. I used following code to acomplished that:
string srcPdfFormPath = #"<Path to source pdf form>";
string destPdfFormPath = #"<Path to destination pdf form>";
using (PdfDocument pdfDocument = new PdfDocument(reader, new PdfWriter(destPdfFormPath)))
{
PdfAcroForm form = PdfAcroForm.GetAcroForm(pdfDocument, true);
var fieldName = "FullName";
var pdfField = form.GetField(fieldName);
if (pdfField != null)
pdfField.SetValue("John Doe", null);
else
Debug.WriteLine($"Cannot find following field {fieldName } on pdf form.");
pdfDocument.Close();
}
Though desired field has been populated, the form not only has been flattened(which, AFAIK is not expected behavior), but also when populated pdf would be opened in Acrobat reader it would produce following message:
"This document enabled extended features in Adobe Acrobat Reader DC.
The document has been changed since it was created and use of extended
features is no longer available. Please contact the author for the
original version of this document."
Those results are not what we have been shooting for. So in order to preserve the document editability and get rid of pesky message I've decided to open PDF form in "Append Mode". Here is a code I've used:
string srcPdfFormPath = #"<Path to source pdf form>";
string destPdfFormPath = #"<Path to destination pdf form>";
using (PdfDocument pdfDocument = new PdfDocument(reader, new PdfWriter(destPdfFormPath)
, new StampingProperties().UseAppendMode()))
{
PdfAcroForm form = PdfAcroForm.GetAcroForm(pdfDocument, true);
var fieldName = "FullName";
var pdfField = form.GetField(fieldName);
if (pdfField != null)
pdfField.SetValue("John Doe", null);
else
Debug.WriteLine($"Cannot find following field {fieldName } on pdf form.");
pdfDocument.Close();
}
And, alas, this attempt also failed on its mission. Though pesky message would no longer be seen, the form itself would not be populated, which is not acceptable. And for the life of me I could not figure out why those behaviors are persist and go together, like horse and carriage!
So question is: what am I missing here? Is there way to make it work using new version of iText?
I am converting html page to pdf using HtmlToPdf() of SelectPDF. Since html content is big, I am breaking it in half and creating 2 PDFs.
I am struggling to edit the total_pages in the footer to display actual total number of the pages, not only the current document; as well as page_number to display the actual page number in the context of both PDFs.
How can I assess {page_number} and {total_pages} to calculate proper values? All examples I found use PdfDocument(), not HtmlToPdf().
Dim converter As New HtmlToPdf()
Dim text As New PdfTextSection(0, 10, "Page: {page_number} of {total_pages} ")
text.HorizontalAlign = PdfTextHorizontalAlign.Center
converter.Footer.Add(text)
I am tagging both C# and VB since SelectPDF is for both languages, and relevant sample from either one will work for me. Thank you
Today I've stumbled upon the same issue and I have found a work-around for the problem. The converter was able to show page numbers for it's the generated document but can't be aware of multiple generated files (you can't access the page properties) so all my pages I concatenated were showing Page 1 of 1.
First I define one PdfDocument (see it as the main document) and I use HtmlToPdf to append html converted files to this main document.
// Create converter
converter = new HtmlToPdf();
PdfTextSection text = new PdfTextSection(0, 10, "Page: {page_number} of {total_pages} ", new Font("Arial", 8));
text.HorizontalAlign = PdfTextHorizontalAlign.Right;
converter.Footer.Add(text);
// Create main document
pdfDocument = new PdfDocument();
Then I add pages (from html) using this method
public void AddPage(string htmlPage)
{
PdfDocument doc = converter.ConvertHtmlString(htmlPage);
pdfDocument.Append(doc);
converter.Footer.TotalPagesOffset += doc.Pages.Count;
converter.Footer.FirstPageNumber += doc.Pages.Count;
}
This results in correct page numbers for the main document. The same trick could be used for splitting files and page numbers over multiple documents like you described.
EDIT: In case you don't see any page numbering using the HtmlToPdf converter, don't forget to set following property:
converter.Options.DisplayFooter = true;
There is an open source library called itextsharp that will help get total page count.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using iTextSharp.text.pdf;
using iTextSharp.text.xml;
namespace GetPages_PDF
{
class Program
{
static void Main(string[] args)
{
// Right side of equation is location of YOUR pdf file
string ppath = "C:\\aworking\\Hawkins.pdf";
PdfReader pdfReader = new PdfReader(ppath);
int numberOfPages = pdfReader.NumberOfPages;
Console.WriteLine(numberOfPages);
Console.ReadLine();
}
}
}
Then you can stamp text also on the page but you will need to add the location to where it needs to go.
link: http://crmhunt.com/how-to-modify-pdf-file-using-itextsharp/
hope this helps in some way.
You should use the following properties:
FirstPageNumber - Controls the page number for the first page being
rendered.
TotalPagesOffset - Controls the total number of pages
offset in the generated pdf document.
More details here:
http://selectpdf.com/html-to-pdf/docs/html/HtmlToPdfHeadersAndFooters.htm
The answers above did not work for me as I was trying to merge multiple PDFs with different orientations. bonnoj's answer did add page numbers but they were incorrect and I couldn't find a way to correct them. So I took a different approach - I created a PDF, then for each HTML page I added a pdfPage and then added a PdfHtmlElement to that page. Finally I loop over the pages and add a custom footer to each page. This may not be the most efficient way to do this but it's the only way that I could find that added the footer in the correct place when mixing portrait and landscape pages. Hopefully it will save somebody else spending hours playing with different properties.
var pdfDocument = new PdfDocument(PdfStandard.Full);
foreach (var (html, pdfPageOrientation) in pages)
{
var page = pdfDocument.AddPage(PdfCustomPageSize.A4, new PdfMargins(marginLeft, marginRight, marginTop, marginBottom));
page.Orientation = pdfPageOrientation;
var pdfHtmlElement = new PdfHtmlElement(html, "");
page.Add(pdfHtmlElement);
}
var pdfFont = pdfDocument.AddFont(PdfStandardFont.Helvetica);
pdfFont.Size = 12;
foreach (PdfPage page in pdfDocument.Pages)
{
var customFooter = pdfDocument.AddTemplate(page.PageSize.Width, 30);
var pdfFooterTextElement = new PdfTextElement(0, 15,
pageFooterText,
pdfFont)
{
HorizontalAlign = PdfTextHorizontalAlign.Right,
VerticalAlign = PdfTextVerticalAlign.Bottom,
};
customFooter.Add(pdfFooterTextElement);
page.CustomFooter = customFooter;
}
pdfDocument.Save(stream);
How can I write a multi-page ToC to the end of a PDF consisting of merged documents, using iTextSharp?
The answer to Create Index File(TOC) for merged pdf using itext library in java explains how to create a ToC page when merging PDFs (catalogued in the iTextSharp book http://developers.itextpdf.com/examples/merging-pdf-documents/merging-documents-and-create-table-contents#795-mergewithtoc.java). Code in this answer is based on those examples.
However it only works if the ToC is 1 page long. If the content becomes longer, then it repeats itself on the same page rather than spanning into the next page.
Trying to add the link directly to the text via:
ct.Add(new Chunk("link").SetLocalGoto("p1"))
causes an exception ("Cannot add Annotations, not enough pages in document").
Can anyone explain a method that will allow me to append multiple pages of content to a PDF when merging them (the more general the approach, the better). Is there a way to write into the document using Document.Add() instead of having to copy in template pages and write on the top of them?
(Note, code is in c#)
This answer is based on the example from the iTextSharp documentation, but converted to C#.
To make the added text span multiple pages, I found I could use ColumnText.HasMoreText(ct.Go()) to tell me if there was more text than could fit on the current page. You can then save the current page, re-create a new page template, and move the columntext to the new page. Below this is in a function called CheckForNewPage:
private bool CheckForNewPage(PdfCopy copy, ref PdfImportedPage page, ref PdfCopy.PageStamp stamp, ref PdfReader templateReader, ColumnText ct)
{
if (ColumnText.HasMoreText(ct.Go()))
{
//Write current page
stamp.AlterContents();
copy.AddPage(page);
//Start a new page
ct.SetSimpleColumn(36, 36, 559, 778);
templateReader = new PdfReader("template.pdf");
page = copy.GetImportedPage(templateReader, 1);
stamp = copy.CreatePageStamp(page);
ct.Canvas = stamp.GetOverContent();
ct.Go();
return true;
}
return false;
}
This should be called each time text is added to the ct variable.
If CheckForNewPage returns true you can then increment the page count, and reset the y variable to the top of the new page so that link annotation is in the correct place on the new page.
e.g.
var tocPageCount = 0;
var para = new iTextSharp.text.Paragraph(documentName);
ct.AddElement(para);
ct.Go();
if (CheckForNewPage(context, copy, ref page, ref stamp, ref tocReader, ct))
{
tocPageCount++;
y = 778;
}
//Add link annotation
action = PdfAction.GotoLocalPage(d.DocumentID.ToString(), false);
link = new PdfAnnotation(copy, TOC_Page.Left, ct.YLine, TOC_Page.Right, y, action);
stamp.AddAnnotation(link);
y = ct.YLine;
This creates the pages correctly. The below code adapts the end of ToC2 example for re-ordering the pages, in order to handle more than 1 page.
var rdr = new PdfReader(baos.toByteArray());
var totalPageCount = rdr.NumberOfPages;
rdr.SelectPages(String.Format("{0}-{1}, 1-{2}", totalPageCount - tocPageCount +1, totalPageCount, totalPageCount - tocPageCount));
PdfStamper stamper = new PdfStamper(rdr, new FileStream(outputFilePath, FileMode.Create));
stamper.Close();
By re-using the CheckForNewPage function, you should be able to add any content to new pages you create, and have it span multiple pages. If you don't need the annnotations you call CheckForNewPage in a loop at the end of adding all your content (just don't call ct.Go() beforehand).
I have an existing PDF document named as aa.pdf. This PDF document has 3 pages. I'd like to add a PDF form field (or a text) at the page bottom of the first page in aa.pdf using iTextSharp.
Meanwhile, I also hope that the PDF form field added (or the text added) can link into another page of aa.pdf. For example, after I click the PDF form field (or the text) located in the first page of aa.pdf,this PDF document skips into the second page.
How can I realize the aboved functionalities using iTextSharp?
Thanks.
To create links within a PDF you use a PdfAction which can be set on a Chunk which can optionally be added to a Paragraph. There are several different types of actions that you can choose from, the two that you are probably interested in are the NEXTPAGE action and/or the GotoLocalPage action. The first item does what it says and goes to the next page. This one is nice because you don't have to worry about figuring out what page number you are on. The second item allows you to specify the specific page number to go to. In its simplest form you can do:
Chunk ch = new Chunk("Go to next page").SetAction(new PdfAction(PdfAction.NEXTPAGE));
This creates a Chunk that you can add in whatever way you want. When working with an existing PDF there's several different ways to add text to a page. One way it to use a ColumnText object which has a method called SetSimpleColumn that allows you to define a simple rectangle that you can add elements to.
Lastly, PDF readers don't automatically treat links differently within a PDF except to give a different cursor when hovering. More specifically, unlike a webpage where hyperlinks are turned a different color, PDFs don't change the color of links unless you tell them to, so this should be kept in mind when creating them. Also, when modifying a PDF you generally never want to overwrite the existing PDF during the process because that would be writing to something that your reading from. Sometimes it works, more often then not it breaks, sometimes subtly. Instead, write to a second file and when you are completely done, erase the first file and rename the second file.
The code below is a full working C# 2010 WinForms app targeting iTextSharp 5.1.2.0. The first part of the code creates a sample PDF called "aa.pdf" on the desktop. If you already have that file you can comment this section out but its in here so others can reproduce this example. The second part creates a new file called "bb.pdf" based on "aa.pdf". It adds two text links to the bottom of the first page. The first link advances the PDF to just the next page while the second link advances the PDF to a specific page number. See the comments in the code for specific implementation details.
using System;
using System.IO;
using System.Windows.Forms;
using iTextSharp.text;
using iTextSharp.text.pdf;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
//Files that we'll be working with
string inputFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "aa.pdf");
string outputFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "bb.pdf");
//Create a standard PDF to test with, nothing special here
using (FileStream fs = new FileStream(inputFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
using (Document doc = new Document(PageSize.LETTER)) {
using (PdfWriter writer = PdfWriter.GetInstance(doc, fs)) {
doc.Open();
//Create 10 pages with labels on each page
for (int i = 1; i <= 10; i++) {
doc.NewPage();
doc.Add(new Paragraph(String.Format("This is page {0}", i)));
}
doc.Close();
}
}
}
//For the OP, this is where you would start
//Declare some variables to be used later
ColumnText ct;
Chunk c;
//Bind a reader to the input file
PdfReader reader = new PdfReader(inputFile);
//PDFs don't automatically make hyperlinks a special color so we're specifically creating a blue font to use here
iTextSharp.text.Font BlueFont = FontFactory.GetFont("Arial", 12, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLUE);
//Create our new file
using (FileStream fs = new FileStream(outputFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
//Bind a stamper to our reader and output file
using (PdfStamper stamper = new PdfStamper(reader, fs)) {
Chunk ch = new Chunk("Go to next page").SetAction(new PdfAction(PdfAction.NEXTPAGE));
//Get the "over" content for page 1
PdfContentByte cb = stamper.GetOverContent(1);
//This example adds a link that goes to the next page
//Create a ColumnText object
ct = new ColumnText(cb);
//Set the rectangle to write to
ct.SetSimpleColumn(0, 0, 200, 20);
//Add some text and make it blue so that it looks like a hyperlink
c = new Chunk("Go to next page", BlueFont);
//Set the action to go to the next page
c.SetAction(new PdfAction(PdfAction.NEXTPAGE));
//Add the chunk to the ColumnText
ct.AddElement(c);
//Tell the system to process the above commands
ct.Go();
//This example add a link that goes to a specific page number
//Create a ColumnText object
ct = new ColumnText(cb);
//Set the rectangle to write to
ct.SetSimpleColumn(200, 0, 400, 20);
//Add some text and make it blue so that it looks like a hyperlink
c = new Chunk("Go to page 3", BlueFont);
//Set the action to go to a specific page number. This option is a little more complex, you also have to specify how you want to "fit" the document
c.SetAction(PdfAction.GotoLocalPage(3, new PdfDestination(PdfDestination.FIT), stamper.Writer));
//Add the chunk to the ColumnText
ct.AddElement(c);
//Tell the system to process the above commands
ct.Go();
}
}
this.Close();
}
}
}