creating a pdf from a template in itextsharp and outputting as content disposition. - itext

I would like to open an existing pdf, add some text and then output as content disposition using itext sharp. I have the following code. Where it falls down it is that i want to output as memory stream but need to filestream to open the original file.
Here's what i have. Obviously defining PdfWriter twice won't work.
public static void Create(string path)
{
var Response = HttpContext.Current.Response;
Response.Clear();
Response.ContentType = "application/pdf";
System.IO.MemoryStream m = new System.IO.MemoryStream();
Document document = new Document();
PdfWriter wri = PdfWriter.GetInstance(document, new FileStream(path, FileMode.Create));
PdfWriter.GetInstance(document, m);
document.Open();
document.Add(new Paragraph(DateTime.Now.ToString()));
document.NewPage();
document.Add(new Paragraph("Hello World"));
document.Close();
Response.OutputStream.Write(m.GetBuffer(), 0, m.GetBuffer().Length);
Response.OutputStream.Flush();
Response.OutputStream.Close();
Response.End();
}

You've got a couple of problems that I'll try to walk you through.
First, the Document object is only for working with new PDFs, not modifying existing ones. Basically the Document object is a bunch of wrapper classes that abstract away the underlying parts of the PDF spec and allow you to work with higher level things like paragraphs and reflowable content. These abstractions turn what you think of "paragraphs" into raw commands that write the paragraph one line at a time with no relationship between lines. When working with an existing document there's no safe way to say how to reflow text so these abstractions aren't used.
Instead you want to use the PdfStamper object. When working with this object you have two choices for how to work with potentially overlapping content, either your new text gets written on top of existing content or your text gets written below it. The two methods GetOverContent() or GetUnderContent() of an instantiated PdfStamper object will return a PdfContentByte object that you can then write text with.
There's two main ways to write text, either manually or through a ColumnText object. If you've done HTML you can think of the ColumnText object as using a big fixed-position single row, single column <TABLE>. The advantage of the ColumnText is that you can use the higher level abstractions such as Paragraph.
Below is a full working C# 2010 WinForms app targeting iTextSharp 5.1.2.0 that show off the above. See the code comments for any questions. It should be pretty easy to convert this to ASP.Net.
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) {
string existingFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "file1.pdf");
string newFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "file2.pdf");
using (FileStream fs = new FileStream(existingFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
using (Document doc = new Document(PageSize.LETTER)) {
using (PdfWriter writer = PdfWriter.GetInstance(doc, fs)) {
doc.Open();
doc.Add(new Paragraph("This is a test"));
doc.Close();
}
}
}
//Bind a PdfReader to our first document
PdfReader reader = new PdfReader(existingFile);
//Create a new stream for our output file (this could be a MemoryStream, too)
using (FileStream fs = new FileStream(newFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
//Use a PdfStamper to bind our source file with our output file
using (PdfStamper stamper = new PdfStamper(reader, fs)) {
//In case of conflict we want our new text to be written "on top" of any existing content
//Get the "Over" state for page 1
PdfContentByte cb = stamper.GetOverContent(1);
//Begin text command
cb.BeginText();
//Set the font information
cb.SetFontAndSize(BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1250, false), 16f);
//Position the cursor for drawing
cb.MoveText(50, 50);
//Write some text
cb.ShowText("This was added manually");
//End text command
cb.EndText();
//Create a new ColumnText object to write to
ColumnText ct = new ColumnText(cb);
//Create a single column who's lower left corner is at 100x100 and upper right is at 500x200
ct.SetSimpleColumn(100,100,500,200);
//Add a higher level object
ct.AddElement(new Paragraph("This was added using ColumnText"));
//Flush the text buffer
ct.Go();
}
}
this.Close();
}
}
}
As to your second problem about the FileStream vs MemoryStream, if you look at the method signature for almost every (actually all as far as I know) method within iTextSharp you'll see that they all take a Stream object and not just a FileStream object. Any time you see this, even outside of iTextSharp, this means that you can pass in any subclass of Stream which includes the MemoryStream object, everything else stays the same.
The code below is a slightly modified version of the one above. I've removed most of the comments to make it shorter. The main change is that we're using a MemoryStream instead of a FileStream. Also, when we're done with the PDF when need to close the PdfStamper object before accessing the raw binary data. (The using statment will do this for us automatically later but it also closes the stream so we need to manually do it here.)
One other thing, never, ever use the GetBuffer() method of the MemoryStream. It sounds like what you want (and I have mistakenly used it, too) but instead you want to use ToArray(). GetBuffer() includes uninitialized bytes which usually produces corrupt PDFs. Also, instead of writing to the HTTP Response stream I'm saving the bytes to array first. From a debugging perspective this allows me to finish all of my iTextSharp and System.IO code and make sure that it is correct, then do whatever I want with the raw byte array. In my case I don't have a web server handy so I'm writing them to disk but you could just as easily call Response.BinaryWrite(bytes)
string existingFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "file1.pdf");
string newFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "file2.pdf");
PdfReader reader = new PdfReader(existingFile);
byte[] bytes;
using(MemoryStream ms = new MemoryStream()){
using (PdfStamper stamper = new PdfStamper(reader, ms)) {
PdfContentByte cb = stamper.GetOverContent(1);
ColumnText ct = new ColumnText(cb);
ct.SetSimpleColumn(100,100,500,200);
ct.AddElement(new Paragraph("This was added using ColumnText"));
ct.Go();
//Flush the PdfStamper's buffer
stamper.Close();
//Get the raw bytes of the PDF
bytes = ms.ToArray();
}
}
//Do whatever you want with the bytes
//Below I'm writing them to disk but you could also write them to the output buffer, too
using (FileStream fs = new FileStream(newFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
fs.Write(bytes, 0, bytes.Length);
}

The second part of your question title says:
"outputting as content disposition"
If that's what you really want you can do this:
Response.AddHeader("Content-Disposition", "attachment; filename=DESIRED-FILENAME.pdf");
Using a MemoryStream is unnecessary, since Response.OutputStream is available. Your example code is calling NewPage() and not trying to add the text to an existing page of your PDF, so here's one way to do what you asked:
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition", "attachment; filename=itextTest.pdf");
PdfReader reader = new PdfReader(readerPath);
// store the extra text on the last (new) page
ColumnText ct = new ColumnText(null);
ct.AddElement(new Paragraph("Text on a new page"));
int numberOfPages = reader.NumberOfPages;
int newPage = numberOfPages + 1;
// get all pages from PDF "template" so we can copy them below
reader.SelectPages(string.Format("1-{0}", numberOfPages));
float marginOffset = 36f;
/*
* we use the selected pages above with a PdfStamper to copy the original.
* and no we don't need a MemoryStream...
*/
using (PdfStamper stamper = new PdfStamper(reader, Response.OutputStream)) {
// use the same page size as the __last__ template page
Rectangle rectangle = reader.GetPageSize(numberOfPages);
// add a new __blank__ page
stamper.InsertPage(newPage, rectangle);
// allows us to write content to the (new/added) page
ct.Canvas = stamper.GetOverContent(newPage);
// add text at an __absolute__ position
ct.SetSimpleColumn(
marginOffset, marginOffset,
rectangle.Right - marginOffset, rectangle.Top - marginOffset
);
ct.Go();
}
I think you've already figured out that the Document / PdfWriter combination doesn't work in this situation :) That's the standard method for creating a new PDF document.

Related

Itextsharp remove page numberer

I've spent much time to no avail on this issue. My PDF pages are automatically numbered 'Page 1 of 0' when creating a PDF as follows:
using (MemoryStream ms = new MemoryStream())
using (Document document = new Document(PageSize.A4, 10, 10, 25, 25))
using (PdfWriter writer = PdfWriter.GetInstance(document, ms))
{
writer.PageEvent = new TextEvents();
document.Open();
document.NewPage();
document.Add(new Phrase("Hello World!"));
document.Close();
writer.Close();
var docout = ms.ToArray();
ms.Close();
return docout;
}
How do I stop this behaviour? I do not want a page numberer.
In this line
writer.PageEvent = new TextEvents();
you tell itext to send page events to an instance of your own TextEvents class. As no other part of the code you show adds page numbers, it must be this class of yours that does.
You can test this by removing the code line quoted above.
Beware: probably that TextEvents class does something else, too, probably something you want. Instead of completely removing that line above, therefore, you might eventually have to analyse your TextEvents class and only remove the unwanted behaviour.
To add to the above answer, TextEvents() should be extending PdfPageEventHelper which has an onEndPage() method where you will find the code that adds page x of n.

adding page numbers and creating landscape A4 in streams with itext

I have the following code that generates a pdf into the stream. This works well but i now have the following requirements.
1) make page landscape: Looking at other examples they add the property to the document object. But i'm doing this instream. So how would i add this property?
2) Add page numbers. I need to put items into a grid so that there are x number of rows per page. With a page number at the footer of the page. How can this kind of feature be acheived with Itext sharp.
public static void Create(ICollection<Part> parts, string path)
{
PdfReader reader = new PdfReader(path);
var pageWidth = 500;
byte[] bytes;
using (MemoryStream ms = new MemoryStream())
{
using (PdfStamper stamper = new PdfStamper(reader, ms))
{
PdfContentByte cb = stamper.GetOverContent(1);
//Flush the PdfStamper's buffer
stamper.Close();
//Get the raw bytes of the PDF
bytes = ms.ToArray();
var now = String.Format("{0:d-M-yyyy}", DateTime.Now);
var pdfName = string.Format("{0}_factory_worksheet", now).Replace("%", "").Replace(" ", "_");
var context = HttpContext.Current;
context.Response.ContentType = "application/pdf";
context.Response.AddHeader("content-disposition", "attachment;filename=" + pdfName);
context.Response.Buffer = true;
context.Response.Clear();
context.Response.OutputStream.Write(ms.GetBuffer(), 0, ms.GetBuffer().Length);
context.Response.OutputStream.Flush();
context.Response.End();
}
}
}
I don't really know how you handling it C# but logical flow will be like this:
Use PdfDictionary to rotate content in reader to 90 degree.Assuming your pdf contain multiple pages,
PdfReader reader = new PdfReader(path);
for (int pgCnt=1; pgCnt <= reader.getNumberOfPages(); pgCnt++) {
//Logic to implement rotation & Add Page number
}
To get current rotation(assuming you are using Portrait mode & try to convert it in landscape mode) use int rttnPg = reader.getPageRotation(pgCnt); also get the PdfDictionary of that page pgDctnry=reader.getPageN(i);(I named that variable as pgDctnry)
Now to rotate it in 90 degree use
pgDctnry.put(PdfName.ROTATE, new PdfNumber(rttnPg+90));
Now bind it using PdfStamper as you are currently doing it.Now to add page number get over content(here i named it pgCntntBt) of the current page
pgCntntBt = stamper .getOverContent(pgCnt);
rctPgSz = rdrPgr.getPageSizeWithRotation(pgCnt);
pgCntntBt.beginText();
bfUsed=//Base Font used for text to be displayed.Also set font size pgCntntBt.setFontAndSize(bfUsed,8.2f);
txtPg=String.format(pgTxt+" %d/%d",pgCnt,totPgCnt);
pgCntntBt.showTextAligned(2,txtPg,//Put Width,//Put Height,//Rotation);
pgCntntBt.endText();
Actually i don't understand what you mean by this:"I need to put items into a grid so that there are x number of rows per page. With a page number at the footer of the page".Now close the stamper to flush it in outputstream.

Using PDF itextSharp it is possible to put an image on top of text while creating the pdf document

I attempted several ways to do this, but still cannot get it.
It appears iTextSharp requires a 2 pass situation so that an image appears on top of the text.
So I am attempting to do this using memory streams, but I keep getting errors.
Public Function createDoc(ByRef reqResponse As HttpResponse) As Boolean
Dim m As System.IO.MemoryStream = New System.IO.MemoryStream()
Dim document As Document = New Document()
Dim writer As PdfWriter = iTextSharp.text.pdf.PdfWriter.GetInstance(document, m)
document.Open()
document.Add(New Paragraph(DateTime.Now.ToString()))
document.Add(New Paragraph(DateTime.Now.ToString()))
document.Add(New Paragraph(DateTime.Now.ToString()))
document.Add(New Paragraph(DateTime.Now.ToString()))
document.Add(New Paragraph(DateTime.Now.ToString()))
document.Add(New Paragraph(DateTime.Now.ToString()))
document.Add(New Paragraph(DateTime.Now.ToString()))
document.Close()
writer.Flush()
writer.Flush()
'yes; I get the pdf if this is the last statement
'reqResponse.OutputStream.Write(m.GetBuffer(), 0, m.GetBuffer().Length)
'this statment does not work it says the stream is closed
'm.Position = 0
Dim Reader As PdfReader = New PdfReader(m)
'Dim rm As MemoryStream = New MemoryStream(m.GetBuffer(), 0, m.GetBuffer().Length)
Dim PdfStamper As PdfStamper = New PdfStamper(Reader, reqResponse.OutputStream)
Dim cb As iTextSharp.text.pdf.PdfContentByte = Nothing
cb = PdfStamper.GetOverContent(1)
Dim locMyImage As System.Drawing.Image = System.Drawing.Image.FromStream(zproProduceWhiteImageToCovertBarCodeNumbers())
Dim BImage As iTextSharp.text.Image = iTextSharp.text.Image.GetInstance(locMyImage, iTextSharp.text.BaseColor.CYAN)
Dim overContent As PdfContentByte = PdfStamper.GetOverContent(1)
BImage.SetAbsolutePosition(5, 5)
overContent.AddImage(BImage)
PdfStamper.FormFlattening = True
PdfStamper.Close()
'rm.Flush()
'rm.Close()
'Dim data As Byte() = rm.ToArray()
'reqResponse.Clear()
'Dim finalMs As MemoryStream = New MemoryStream(data)
'reqResponse.ContentType = "application/pdf"
'reqResponse.AddHeader("content-disposition", "attachment;filename=labtest.pdf")
'reqResponse.Buffer = True
'finalMs.WriteTo(reqResponse.OutputStream)
'reqResponse.End()
'Dim data As Byte() = rm.ToArray()
'reqResponse.OutputStream.Write(data, 0, data.Length)
''Response.OutputStream.Write(m.GetBuffer(), 0, m.GetBuffer().Length);
''Response.OutputStream.Flush();
''Response.OutputStream.Close();
''Response.End();
HttpContext.Current.ApplicationInstance.CompleteRequest()
Return True
End Function
reference:
Put text on top of an image?
seach engine reference:
whiteout text on a pdf document by using a image which is the same color as the background pdf
image overlap with itextpdf
itextsharp image on top of the text whiteout
itextsharp place picture on top of text
itextpdf image on top
thanks,
Doug Lubey of Louisiana
You can do this pretty easily. The Document object is a helper object that abstracts away many of the internals of the PDF model and for the most part assumes that you want to flow content and that text would go above images. If you want to get around this you can talk directly the PdfWriter object instead. It has two properties, DirectContent and DirectContentUnder that both have methods named AddImage() that you can use to set an absolute position on an image. DirectContent is above existing content and DirectContentUnder is below it. See the code for an example:
You appear to be doing this on the web so you'll need to adapt this to whatever stream you are using but that should be pretty easy.
One note, NEVER call GetBuffer() on a MemoryStream, ALWAYS use ToArray(). The former method includes uninitialized bytes that will give you potentially corrupt PDFs.
''//File that we are creating
Dim OutputFile As String = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Test.pdf")
''//Image to place
Dim SampleImage As String = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "SampleImage.jpg")
''//Standard PDF creation setup
Using FS As New FileStream(OutputFile, FileMode.Create, FileAccess.Write, FileShare.None)
Using Doc As New Document(PageSize.LETTER)
Using writer = PdfWriter.GetInstance(Doc, FS)
''//Open the document for writing
Doc.Open()
''//Add a simple paragraph
Doc.Add(New Paragraph("Hello world"))
''//Create an image object
Dim Img = iTextSharp.text.Image.GetInstance(SampleImage)
''//Give it an absolute position in the top left corner of the document (remembering that 0,0 is bottom left, not top left)
Img.SetAbsolutePosition(0, Doc.PageSize.Height - Img.Height)
''//Add it directly to the raw pdfwriter instead of the document helper. DirectContent is above and DirectContentUnder is below
writer.DirectContent.AddImage(Img)
''//Close the document
Doc.Close()
End Using
End Using
End Using

PdfWriter and Events

I want to create a PdfWriter Object and set Events for Header and Footer.
The problem is it works if I create a new PDF. But my problem is I already have a PDF in Output Stream. Please find my sample code below.
Document document = new Document();
try {
// step 2:
FileInputStream is = new FileInputStream("D://2.pdf");
int nRead;
byte[] data = new byte[16384];
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
PdfWriter writer = PdfWriter.getInstance(document,buffer);
writer.setViewerPreferences(PdfWriter.PageLayoutTwoColumnLeft);
writer.setPageEvent(new DossierPortalUtil());
document.setMargins(36, 36, 54, 72);
// step 3:
document.open();
document.add( new Chunk("testing"));
} catch (Exception de) {
de.printStackTrace();
}
finally{
document.close();
}
If I comment the line
document.add( new Chunk("testing"));
I get an exception
Exception in thread "main" ExceptionConverter: java.io.IOException: The document has no pages.
Without commenting there are no exceptions but it doesnt add the Header and Footer. Any clues are highly appreciated.
Regards,
Tina
enter code here
Yep.
You're trying to modify an existing PDF with PdfWriter, when you should be using PdfStamper.
Adding text with a stamper is Far Less Trivial than doing so with PdfWriter and a Document.
You need to create a ColumnText object, and get a PdfContentByte by calling myStamper.getOverContent(pageNum).
You add the paragraphs/chunks/etc to the ColumnText, and pass it the PdfContentByte (and some positional parameters) to draw the text.
Alternatively, you can create a separate PDF with your text (and anything else), then use PdfStamper & PdfImportedPage to import those pages and write them over top of the existing ones. PDF page backgrounds are transparent until you draw something over them, so the text (and stuff) will appear over top of the existing page. This is noticeably less efficient, as the second document has to be converted to a byte array in PDF syntax (if you're using a ByteArrayOutputStream instead of writing to a file, which would be even slower), parsed again, and then added to the original doc and written out a second time.
It's worth a little extra effort to use ColumnText.
You'll also need to write your header and footer directly with PdfContentByte calls, but you have to do that already within your PdfPageEvent, so those changes should be quite simple.

How to add a form field to an existing pdf with itextsharp?

How to add a form field to an existing pdf with itextsharp?
I have an existing pdf document, I'd like to add form fields to it without creating a copy and writing out a new document.
After further review, the ruling on the field is overturned. Turns out if you form flatten the stamper the fields do not show on the resulting document (because they lack 'appearance' settings). BTW, form flattening prevents further edits of a form field. Now we can add appearance to the form, however, an easier way is to use the TextField class and not worry about explicitly setting up 'appearance' objects.
public void ABetterWayToAddFormFieldToExistingPDF( )
{
PdfReader reader = new PdfReader(#"c:\existing.pdf");
FileStream out = new FileStream(#"C:\existingPlusFields.pdf", FileMode.Create, FileAccess.Write);
PdfStamper stamp = new PdfStamper(reader, out);
TextField field = new TextField(stamp.Writer, new iTextSharp.text.Rectangle(40, 500, 360, 530), "some_text");
// add the field here, the second param is the page you want it on
stamp.AddAnnotation(field.GetTextField(), 1);
stamp.FormFlattening = true; // lock fields and prevent further edits.
stamp.Close();
}
I struggled with this for awhile so figured I'd post the Question & Answer
Using the PdfStamper itext class is the key. (I guess this does make a copy but it's much cleaner than using the itext PdfCopy classes).
public void AddFormFieldToExistingPDF( )
{
PdfReader reader = new PdfReader(#"c:\existing.pdf");
FileStream out = new FileStream(#"C:\existingPlusFields.pdf", FileMode.Create, FileAccess.Write);
PdfStamper stamp = new PdfStamper(reader, out);
PdfFormField field = PdfFormField.CreateTextField(stamp.Writer, false, false, 50);
// set a field w/some position and size
field.SetWidget(new iTextSharp.text.Rectangle(40, 500, 360, 530),
PdfAnnotation.HIGHLIGHT_INVERT);
field.SetFieldFlags(PdfAnnotation.FLAGS_PRINT);
field.FieldName = "some_field";
// add the field here, the second param is the page you want it on
stamp.AddAnnotation(field, 1);
stamp.Close();
}
Using pdfStamper you can complete it.
PdfStamper Stamper= new PdfStamper(new PdfReader(sourcefile), File.Create(NewOutputFile));
TextField moreText = new TextField(Stamper.Writer,
new iTextSharp.text.Rectangle(20, 20, 590, 780), "moreText");
moreText.Visibility = TextField.VISIBLE_BUT_DOES_NOT_PRINT;
moreText.Text = "Use this space for any additional information";
moreText.Options = (TextField.MULTILINE);
PdfFormField Fieldtxt = moreText.GetTextField();
Stamper.AddAnnotation(Fieldtxt, n);