Embedding font using itext 5 for PDF/UA compliance - itext

We are currently building a proof of concept to generate PDF/UA compliant PDF from from a CSS and html (xhtml) file using xslt. We are able tag the PDF and add the appropriate metadata information.
The last major issue we are unable to solve is embedding a standard PDF font zapfdinbats, which our accessibility assessment tool complains about - using PAC 2.0 along with adobe DC built in checker.
As you can see from the image below the other fonts we are using seems automatically get embedded using the xmlworker from our CSS.
I have also tried finding the font as indicated and found one, however, it doesn't seem to be the correct one.
Here is a sample of our code
private static ReturnValue CreateFromHtml(string html)
{
ReturnValue Result = new ReturnValue();
var stream = new MemoryStream();
using (var doc = new Document(PageSize.LETTER))
{
using (var ms = new MemoryStream())
{
using (var writer = PdfWriter.GetInstance(doc, ms))
{
writer.CloseStream = false;
writer.SetPdfVersion(PdfWriter.PDF_VERSION_1_7);
//TAGGED PDFVERSION_1_7
//Make document tagged
writer.SetTagged();
//===============
//PDF/UA
//Set document metadata
writer.ViewerPreferences = PdfWriter.DisplayDocTitle;
doc.AddLanguage("en-US");
doc.AddTitle("document title");
writer.CreateXmpMetadata();
doc.Open();
var embedfont = HttpContext.Current.Server.MapPath("~/scripts/ZapfDingbats.ttf");
var fontProv = new XMLWorkerFontProvider();
fontProv.DefaultEncoding = "UTF-8";
fontProv.Register(embedfont);
//Testing zapfDingbats font
Font font = FontFactory.GetFont(embedfont, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
Paragraph p1 = new Paragraph("Testing of Fonts", font);
doc.Add(p1);
//end font processing
var tagProcessors = (DefaultTagProcessorFactory)Tags.GetHtmlTagProcessorFactory();
tagProcessors.RemoveProcessor(HTML.Tag.IMG);
tagProcessors.AddProcessor(HTML.Tag.IMG, new CustomImageTagProcessor());
var cssFiles = new CssFilesImpl();
cssFiles.Add(XMLWorkerHelper.GetInstance().GetDefaultCSS());
var cssResolver = new StyleAttrCSSResolver(cssFiles);
var charset = Encoding.UTF8;
var context = new HtmlPipelineContext(new CssAppliersImpl(new XMLWorkerFontProvider()));
context.SetAcceptUnknown(true).AutoBookmark(true).SetTagFactory(tagProcessors);
var htmlPipeline = new HtmlPipeline(context, new PdfWriterPipeline(doc, writer));
var cssPipeline = new CssResolverPipeline(cssResolver, htmlPipeline);
var worker = new XMLWorker(cssPipeline, true);
var xmlParser = new XMLParser(true, worker, charset);
using (var sr = new StringReader(html))
{
xmlParser.Parse(sr);
doc.Close();
ms.Position = 0;
ms.CopyTo(stream);
stream.Position = 0;
}
}
}
}
// get bytes from stream
Result.Data = stream.ToArray();
// success
Result.Success = true;
return Result;
}
Maybe there is something in the CSS we need to do (our CSS is quite large f

iText only ships with the Adobe Font Metrics (AFM) file of Zapfdingbats. This means that you can't embed that font unless you provide the corresponding PostScript Font Binary (PFB) file. This PFB file can't be shipped with iText because iText doesn't have a license to do so.
The first step to solve this, is to:
purchase a Zapfdingbats license so that you get the PFB (If I recall correctly, it's a font owned by Adobe), or
use an alternative font when you want to insert special characters (check boxes, phone symbols,...) into your text (e.g. purchase a license for the AdobePiStd font that was used as a substitution font and use that font instead of Zapfdingbats).
In your case, you provided a font ZapfDingbats.ttf which you register with the XMLWorkerFontProvider. When you register this font, it can be recognized through an alias. If ZapfDingbats.ttf isn't picked up by XML Worker, there is probably a mismatch between the name of the font used in the PDF and the alias that was used when ZapfDingbats.ttf was registered.
What is the font name used for ZapfDingbats in the CSS? You should register ZapfDingbats using that name as alias.

Related

Image scrambled when embedded into pdf using iTextSharp

I'm having an issue when attempting to create a PDF with an image using iTextSharp. I'm adding the image to the top of the PDF like a masthead and then the rest of the PDF is HTML. The PDF generates fine and displays correctly when viewed in Edge, Chrome and Firefox. But, if you open it in IE or in Adobe Reader, the masthead is completely scrambled. The rest of the PDF (the HTML content) generates as expected in all browsers and programs. I've googled for days now trying to discover if anyone else has had this issue, but all I can find is text being garbled, not images. Below is the code I'm using to generate the image. Any ideas?
using (var document = new Document(PageSize.A4, 10f, 10f, 10f, 10f))
{
iTextSharp.text.Image image = iTextSharp.text.Image.GetInstance(Server.MapPath("~/Content/Images/kfa_masthead.jpg"));
using (var ms = new MemoryStream())
{
image.Left = 10f;
image.Top = image.Height;
image.ScaleAbsolute(560f, 58f);
using (var writer = PdfWriter.GetInstance(document, ms))
{
document.Open();
writer.CloseStream = false;
//Add the masthead
document.Add(image);
//Add the report information
var html = CreateTable(profile);
using (var xHtml = new StringReader(html))
{
XMLWorkerHelper.GetInstance().ParseXHtml(writer, document, xHtml);
}
document.CloseDocument();
document.Close();
}
return ms.ToArray();
}
}

How to generate PDF in Hebrew? currently the PDF is generated empty

I'm using iTextSharp 5.5.13 and when i try to generate the PDF with Hebrew it comes out empty.
this is my code: I'm i doing something wrong?
public byte[] GenerateIvhunPdf(FinalIvhunSolution ivhun)
{
byte[] pdfBytes;
using (var mem = new MemoryStream())
{
Document document = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.GetInstance(document, mem);
writer.PageEvent = new MyHeaderNFooter();
document.Open();
var font = new
Font(BaseFont.CreateFont("C:\\Downloads\\fonts\\Rubik-Light.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED), 14);
Paragraph p = new Paragraph("פסקת פתיחה")
{
Alignment = Element.ALIGN_RIGHT
};
PdfPTable table = new PdfPTable(2)
{
RunDirection = PdfWriter.RUN_DIRECTION_RTL
};
PdfPCell cell = new PdfPCell(new Phrase("מזהה", font));
cell.BackgroundColor = BaseColor.BLACK;
table.AddCell(cell);
document.Add(p);
document.Add(table);
document.Close();
pdfBytes = mem.ToArray();
}
return pdfBytes;
}
The PDF comes out blank
I changed a few details of your code, and now I get this:
My changes:
Embedding the font
As I don't have Rubik installed on my system, I have to embed the font into the PDF to have a chance to see anything. Thus, I replaced BaseFont.NOT_EMBEDDED by BaseFont.EMBEDDED when creating the var font:
var font = new Font(BaseFont.CreateFont("Rubik-Light.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED), 14);
Making the Paragraph kind of work
You create the Paragraph p without specifying a font. Thus, a default font with default encoding is used. The default encoding is WinAnsiEncoding which is Latin1-like, so no Hebrew characters can be represented. I added your Rubik font instance to the Paragraph p creation:
Paragraph p = new Paragraph("פסקת פתיחה", font)
{
Alignment = Element.ALIGN_RIGHT
};
Et voilà, the writing appears.
iText developers often have communicated that in iText 5.x and earlier right-to-left scripts are only supported properly in certain contexts, e.g. in tables, but not in others like paragraphs immediately added to the document. As your Paragraph p is added immediately to the Document document, its letters appear in the wrong order in the output.
Making the PdfPTable work
You defined the PdfPTable table to have two columns (new PdfPTable(2)) but then you added only one cell. Thus, table contains not even a single complete row. iText, therefore, draws nothing when it is added to the document.
I changed the definition of table to have a single column only:
PdfPTable table = new PdfPTable(1)
{
RunDirection = PdfWriter.RUN_DIRECTION_RTL
};
Furthermore, I commented out the line setting the cell background to black because usually it is difficult to read black on black:
PdfPCell cell = new PdfPCell(new Phrase("מזהה", font));
//cell.BackgroundColor = BaseColor.BLACK;
table.AddCell(cell);
And again the writing appears.
Properly downloading the font
Another possible obstacle is that when downloading the font from the URL you gave — https://fonts.google.com/selection?selection.family=Rubik — one can see in the customization tab of the selection drawer that by default only Latin characters are included in the download, in particular not Hebrew ones:
I tested with a font file I downloaded with all language options enabled:

How to use existing font in itext [duplicate]

There are tips in "iText In Action" that cover setting fonts, as well as the "FontFactory.RegisterDirectories" method (which is, as the book says...an expensive call). However, in my case, the font that I want to use for new fields is already embedded in the document (in an existing Acrofield). With no guarantee that the same font will exist on the user's machine (or on a web server)....is there a way that I can register that already-embedded font, so that I can re-use it for other objects? In the code below, Acrofield "TheFieldIWantTheFontFrom" has the font that I want to re-use for a field named "my_new_field". Any help would be greatly appreciated!
using (MemoryStream output = new MemoryStream())
{
// Use iTextSharp PDF Reader, to get the fields and send to the
//Stamper to set the fields in the document
PdfReader pdfReader = new PdfReader(#"C:\MadScience\MSE_030414.pdf");
// Initialize Stamper (ms is a MemoryStream object)
PdfStamper pdfStamper = new PdfStamper(pdfReader, output);
// Get Reference to PDF Document Fields
AcroFields pdfFormFields = pdfStamper.AcroFields;
//*** CODE THAT HAVE NOT YET BEEN ABLE TO MAKE USE OF TO ASSIST WITH MY FONT ISSUE
//*** MIGHT BE HELP?
//List<object[]> fonts = BaseFont.GetDocumentFonts(pdfReader);
//BaseFont[] baseFonts = new BaseFont[fonts.Count];
//string[] fn = new string[fonts.Count];
//for (int i = 0; i < fonts.Count; i++)
//{
// Object[] obj = (Object[])fonts[i];
// baseFonts[i] = BaseFont.CreateFont((PRIndirectReference)(obj[1]));
// fn[i] = baseFonts[i].PostscriptFontName.ToString();
// //Console.WriteLine(baseFonts[i].FamilyFontName[0][1].ToString());
// //FontFactory.RegisteredFonts.Add(fn[i]);
// //FontFactory.Register(
// Console.WriteLine(fn[i]);
//}
//ICollection<string> registeredFonts = iTextSharp.text.FontFactory.RegisteredFonts;
//foreach (string s in registeredFonts)
//{
// Console.WriteLine("pre-registered: " + s);
//}
if (!FontFactory.Contains("georgia-bold"))
{
FontFactory.RegisterDirectories();
Console.WriteLine("had to register everything"); }
//registeredFonts = iTextSharp.text.FontFactory.RegisteredFonts;
//foreach (string s in registeredFonts)
//{
// Console.WriteLine("post-registered: " + s);
//}
Font myfont = FontFactory.GetFont("georgia-bold");
string nameOfField = "my_field";
AcroFields.Item fld = pdfFormFields.GetFieldItem(nameOfField);
//set the text of the form field
pdfFormFields.SetField(nameOfField, "test stuff");
pdfFormFields.SetField("TheFieldIWantTheFontFrom", "test more stuff");
bool madeit = pdfFormFields.SetFieldProperty(nameOfField, "textfont", myfont.BaseFont, null);
bool madeit2 = pdfFormFields.SetFieldProperty(nameOfField, "textsize", 8f, null);
pdfFormFields.RegenerateField(nameOfField);
// Set the flattening flag to false, so the document can continue to be edited
pdfStamper.FormFlattening = true;
// close the pdf stamper
pdfStamper.Close();
//get the bytes from the MemoryStream
byte[] content = output.ToArray();
using (FileStream fs = File.Create(#"C:\MadScience\MSE_Results.pdf"))
{
//byte[] b = outList[i];
fs.Write(content, 0, (int)content.Length);
fs.Flush();
}
}
Yes you can re-use fonts and the PDF specification actually encourages it. You should, however, keep in mind that some fonts may be embedded as subsets only.
The below code is adapted from this post (be careful, that site has nasty popups sometimes). See the comments in the code for more information. This code was tested against iTextSharp 5.4.4.
/// <summary>
/// Look for the given font name (not file name) in the supplied PdfReader's AcroForm dictionary.
/// </summary>
/// <param name="reader">An open PdfReader to search for fonts in.</param>
/// <param name="fontName">The font's name as listed in the PDF.</param>
/// <returns>A BaseFont object if the font is found or null.</returns>
static BaseFont findFontInForm(PdfReader reader, String fontName) {
//Get the document's acroform dictionary
PdfDictionary acroForm = (PdfDictionary)PdfReader.GetPdfObject(reader.Catalog.Get(PdfName.ACROFORM));
//Bail if there isn't one
if (acroForm == null) {
return null;
}
//Get the resource dictionary
var DR = acroForm.GetAsDict(PdfName.DR);
//Get the font dictionary (required per spec)
var FONT = DR.GetAsDict(PdfName.FONT);
//Look for the actual font and return it
return findFontInFontDict(FONT, fontName);
}
/// <summary>
/// Helper method to look at a specific font dictionary for a given font string.
/// </summary>
/// <remarks>
/// This method is a helper method and should not be called directly without knowledge of
/// the internals of the PDF spec.
/// </remarks>
/// <param name="fontDict">A /FONT dictionary.</param>
/// <param name="fontName">Optional. The font's name as listed in the PDF. If not supplied then the first font found is returned.</param>
/// <returns>A BaseFont object if the font is found or null.</returns>
static BaseFont findFontInFontDict(PdfDictionary fontDict, string fontName) {
//This code is adapted from http://osdir.com/ml/java.lib.itext.general/2004-09/msg00018.html
foreach (var internalFontName in fontDict.Keys) {
var internalFontDict = (PdfDictionary)PdfReader.GetPdfObject(fontDict.Get(internalFontName));
var baseFontName = (PdfName)PdfReader.GetPdfObject(internalFontDict.Get(PdfName.BASEFONT));
//// compare names, ignoring the initial '/' in the baseFontName
if (fontName == null || baseFontName.ToString().IndexOf(fontName) == 1) {
var iRef = (PRIndirectReference)fontDict.GetAsIndirectObject(internalFontName);
if (iRef != null) {
return BaseFont.CreateFont(iRef);
}
}
}
return null;
}
And here's the test code that runs this. It first creates a sample document with an embedded font and then it creates a second document based upon that and re-uses that font. In your code you'll need to actually know beforehand what the font name is that you're searching for. If you don't have ROCK.TTF (Rockwell) installed you'll need to pick a different font file to run this.
//Test file that we'll create with an embedded font
var file1 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.pdf");
//Secondary file that we'll try to re-use the font above from
var file2 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test2.pdf");
//Path to font file that we'd like to use
var fontFilePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "ROCK.TTF");
//Create a basefont object
var font = BaseFont.CreateFont(fontFilePath, BaseFont.WINANSI, true);
//Get the name that we're going to be searching for later on.
var searchForFontName = font.PostscriptFontName;
//Step #1 - Create sample document
//The below block creates a sample PDF file with an embedded font in an AcroForm, nothing too special
using (var fs = new FileStream(file1, FileMode.Create, FileAccess.Write, FileShare.None)) {
using (var doc = new Document()) {
using (var writer = PdfWriter.GetInstance(doc, fs)) {
doc.Open();
//Create our field, set the font and add it to the document
var tf = new TextField(writer, new iTextSharp.text.Rectangle(50, 50, 400, 150), "first-name");
tf.Font = font;
writer.AddAnnotation(tf.GetTextField());
doc.Close();
}
}
}
//Step #2 - Look for font
//This uses a stamper to draw on top of the existing PDF using a font already embedded
using (var fs = new FileStream(file2, FileMode.Create, FileAccess.Write, FileShare.None)) {
using (var reader = new PdfReader(file1)) {
using (var stamper = new PdfStamper(reader, fs)) {
//Try to get the font file
var f = findFontInForm(reader, searchForFontName);
//Make sure we found something
if (f != null) {
//Draw some text
var cb = stamper.GetOverContent(1);
cb.BeginText();
cb.MoveText(200, 400);
cb.SetFontAndSize(f, 72);
cb.ShowText("Hello!");
cb.EndText();
}
}
}
}
EDIT
I made a small modification to the findFontInFontDict method above. The second parameter is now optional. If null it returns the first font object that it finds in the supplied dictionary. This change allows me to introduce the below method which looks for a specific field by name and gets the font.
static BaseFont findFontByFieldName(PdfReader reader, String fieldName) {
//Get the document's acroform dictionary
PdfDictionary acroForm = (PdfDictionary)PdfReader.GetPdfObject(reader.Catalog.Get(PdfName.ACROFORM));
//Bail if there isn't one
if (acroForm == null) {
return null;
}
//Get the fields array
var FIELDS = acroForm.GetAsArray(PdfName.FIELDS);
if (FIELDS == null || FIELDS.Length == 0) {
return null;
}
//Loop through each field reference
foreach (var fieldIR in FIELDS) {
var field = (PdfDictionary)PdfReader.GetPdfObject(fieldIR);
//Check the field name against the supplied field name
if (field.GetAsString(PdfName.T).ToString() == fieldName) {
//Get the resource dictionary
var DR = acroForm.GetAsDict(PdfName.DR);
//Get the font dictionary (required per spec)
var FONT = DR.GetAsDict(PdfName.FONT);
return findFontInFontDict(FONT);
}
}
return null;
}

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.

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

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.