I am using the below code to add watermark to my pdf.
private void Merge(List<string> src, string dest)
{
iTextKernel.PdfWriter writer = new iTextKernel.PdfWriter(dest);
iTextKernel.PdfDocument pdfDocument1 = new iTextKernel.PdfDocument(new iTextKernel.PdfReader(src[0]), writer);
pdfDocument1.AddEventHandler(PdfDocumentEvent.END_PAGE, new WatermarkingEventHandler());
for (int i = 1, max = src.Count; i < max; i++)
{
iTextKernel.PdfDocument pdfDocument2 = new iTextKernel.PdfDocument(new iTextKernel.PdfReader(src[i]));
var pagesCount = pdfDocument2.GetNumberOfPages();
pdfDocument2.CopyPagesTo(1, pagesCount, pdfDocument1);
pdfDocument2.Close();
}
pdfDocument1.Close();
protected class WatermarkingEventHandler : IEventHandler {
public void HandleEvent(Event e) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) e;
iTextKernel.PdfDocument pdfDoc = docEvent.GetDocument();
iTextKernel.PdfPage page = docEvent.GetPage();
iText.Kernel.Font.PdfFont font = null;
try {
font = PdfFontFactory.CreateFont(FontConstants.HELVETICA_BOLD);
} catch (IOException ex) {
//_log.Error(ex);
}
PdfCanvas canvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdfDoc);
new Canvas(canvas, pdfDoc, page.GetPageSize())
.SetFontColor(iText.Kernel.Colors.DeviceGray.LIGHT_GRAY)
.SetFontSize(60)
.SetFont(font)
.ShowTextAligned(new Paragraph("FOR YOUR RECORDS ONLY: DO NOT SUBMIT"), 298, 421, pdfDoc.GetPageNumber(page),
TextAlignment.CENTER, VerticalAlignment.MIDDLE, 45);
}
But am getting the watermark only in the last page that too hidden under the contents. Could you please modify this code so that i could get the watermark on all the pages and shown over the contents.
Please take a look at the iText 7 for C# jump-start tutorial, more specifically Chapter 5: Manipulating an existing PDF document. Scroll to the part where it says: "Adding a header, footer, and watermark" and look at the example:
PdfDocument pdfDoc = new PdfDocument(new PdfReader(src), new PdfWriter(dest));
Document document = new Document(pdfDoc);
Rectangle pageSize;
PdfCanvas canvas;
int n = pdfDoc.GetNumberOfPages();
for (int i = 1; i <= n; i++) {
PdfPage page = pdfDoc.GetPage(i);
pageSize = page.GetPageSize();
canvas = new PdfCanvas(page);
//Draw header text
}
pdfDoc.close();
As you can see, we only need one PdfDocument instance, but instead of passing only a PdfWriter, we also pass a PdfReader instance. We will read the file with path src and we will write to a file with path dest.
You want to add content to each page. This means that you have to loop over each page (from 1 to n). Get the PdfPage object for each page i and replace the line //Draw header text with whatever it is you want to do.
In your case, you add an image underneath the existing content. That is the normal thing to do, but you say that the watermark is covered by the existing content. That happens for instance when the actual content consists of images (e.g. scanned pages). If you add a watermark under the pages of a PDF that consists of scanned pages, you will never see the watermark.
In that case, you have to add the content on top of the existing content, but it is best to make the watermark transparent:
Paragraph p = new Paragraph("FOR YOUR RECORDS ONLY: DO NOT SUBMIT").SetFontSize(60);
canvas.SaveState();
PdfExtGState gs1 = new PdfExtGState().SetFillOpacity(0.2f);
canvas.SetExtGState(gs1);
document.ShowTextAligned(p, pageSize.GetWidth() / 2, pageSize.GetHeight() / 2, pdfDoc.GetPageNumber(page), TextAlignment.CENTER, VerticalAlignment.MIDDLE, 45);
canvas.RestoreState();
Note that in the tutorial, we are using pageSize.GetWidth() / 2 and pageSize.GetHeight() / 2 as coordinates, which means that we assume that the lower-left corner of the page has the coordinate (0, 0). That may not be the case. You may have to add the x-offset and the y-offset to that value.
Related
We are creating a PDF, it contains a large table, with headers (bold), sub-headers (non bold), rows and cells.
One of the requirements is to add an already existing PDF (can be multiple pages) inside the cell (under the sub-header). And this is where we are struggling. What we have tried so far:
Converting the PDF to a FormXObject, and adding that into the cell.
FontProvider fontProvider = new FontProvider();
ConverterProperties props = new ConverterProperties();
props.SetFontProvider(fontProvider);
var historyId = text.Replace("=Spectec_template", "");
var pdfTemplate = templateToPdfClient.GetPdf(historyId, "HI").Result;
PdfDocument srcDoc = new PdfDocument(new PdfReader(pdfTemplate.FileStream));
for (int i = 1; i < srcDoc.GetNumberOfPages() + 1; i++)
{
Cell newCell = new Cell(1, columnInfo.ColumnSpan).SetFont(PdfFontFactory.CreateFont(_fontName));
PdfPage origPage = srcDoc.GetPage(i);
Rectangle rect = origPage.GetPageSize();
PdfFormXObject pageCopy = origPage.CopyAsFormXObject(pdf);
Image image = new Image(pageCopy);
image.SetBorder(Border.NO_BORDER);
image.SetMaxWidth(UnitValue.CreatePercentValue(100));
image.SetMaxHeight(UnitValue.CreatePercentValue(50));
Div div = new Div();
div.Add(image.SetMaxWidth(UnitValue.CreatePercentValue(100)));
newCell.Add(div);
newCell = ApplyLayoutsToCell(newCell, cellStyles);
table.AddCell(newCell);
}
But the end result is that we only see 1 page and it's completely overlapping at the end of the page.
When we set image.SetAutoScale(true); the PDF is visible but it's extremely small.
When adding the Image to a Paragraph instead of a DIV the PDF pages display next to each other.
Any ideas, suggestions?
Thank you
Thanks to #mkl and #KJ I figured this out.
I ended up adding each PDF page in a new Cell, and setting the scaling of image to 0.6f.
for (int i = 1; i < srcDoc.GetNumberOfPages() + 1; i++)
{
newCell = new Cell(1, columnInfo.ColumnSpan).SetFont(PdfFontFactory.CreateFont(_fontName));
PdfPage origPage = srcDoc.GetPage(i);
PdfFormXObject pageCopy = origPage.CopyAsFormXObject(pdf);
Image image = new Image(pageCopy);
image.SetBorder(Border.NO_BORDER);
image.Scale(0.6f, 0.6f);
newCell.Add(image);
newCell = ApplyLayoutsToCell(newCell, cellStyles);
table.AddCell(newCell);
}
I am building a PDF at runtime in itext7.pdfHTML, using a template file. I want to add a footer to every page of the resulting PDF, which has two pages, but for some reason the footer only appears on the second page.
I'm trying to build on this example from the iText website. The example deals with page numbers, but since I'm just adding static text to my document, the principle is the same. Here's my code:
string footer = "This is the footer".
string body = "<span>This is raw HTML</span>";
//create a temporary PDF with the raw HTML, made from my template and given data
private void createPDF()
{
destination = System.IO.Path.Combine(HttpContext.Current.Server.MapPath("~/pdf_repo"), "tempFile.pdf");
ConverterProperties properties = new ConverterProperties();
properties.SetBaseUri(HttpContext.Current.Server.MapPath("~/templates/"));
HtmlConverter.ConvertToPdf(body, new FileStream(destination, FileMode.Create), properties);
addFooter(id);
}
//modify the PDF file created above by adding the footer
private void addFooter(string id)
{
string newFile = System.IO.Path.Combine(HttpContext.Current.Server.MapPath("pdf_repo", "finalFile.pdf");
PdfDocument pdfDoc = new PdfDocument(new PdfReader(destination), new PdfWriter(newFile));
Document doc = new Document(pdfDoc);
Paragraph foot = new Paragraph(footer);
foot.SetFontSize(8);
float x = 300; //559
float y = 0; //806
int numberOfPages = pdfDoc.GetNumberOfPages();
for (int i = 1; i <= numberOfPages; i++)
{
doc.ShowTextAligned(foot, x, y, TextAlignment.CENTER, VerticalAlignment.BOTTOM);
}
doc.Close();
//delete temporary PDF
File.Delete(destination);
}
I've tried setting i to 0 in the addFooter() "for" loop, but that doesn't solve the issue. How can I get the footer to appear on every page?
Yeah, you weren't specifying which page to add the footer to, so it only added it to the bottom of the entire document. Try this:
Note, the only change was: doc.ShowTextAligned(foot, x, y, i, TextAlignment.CENTER, VerticalAlignment.BOTTOM);
string footer = "This is the footer".
string body = "<span>This is raw HTML</span>";
//create a temporary PDF with the raw HTML, made from my template and given data
private void createPDF()
{
destination = System.IO.Path.Combine(HttpContext.Current.Server.MapPath("~/pdf_repo"), "tempFile.pdf");
ConverterProperties properties = new ConverterProperties();
properties.SetBaseUri(HttpContext.Current.Server.MapPath("~/templates/"));
HtmlConverter.ConvertToPdf(body, new FileStream(destination, FileMode.Create), properties);
addFooter(id);
}
//modify the PDF file created above by adding the footer
private void addFooter(string id)
{
string newFile = System.IO.Path.Combine(HttpContext.Current.Server.MapPath("pdf_repo", "finalFile.pdf");
PdfDocument pdfDoc = new PdfDocument(new PdfReader(destination), new PdfWriter(newFile));
Document doc = new Document(pdfDoc);
Paragraph foot = new Paragraph(footer);
foot.SetFontSize(8);
float x = 300; //559
float y = 0; //806
int numberOfPages = pdfDoc.GetNumberOfPages();
for (int i = 1; i <= numberOfPages; i++)
{
doc.ShowTextAligned(foot, x, y, i, TextAlignment.CENTER, VerticalAlignment.BOTTOM);
}
doc.Close();
//delete temporary PDF
File.Delete(destination);
}
I am currently trying to iterate over an existing PDF and stamp each page with some footer text using the OnPageEnd event as detailed in the iText documentation, Chapter 5: Table, cell, and page events.
When I assign my new custom event class to the PdfCopy instance, I receive this exception:
"Operation is not valid due to the current state of the object" at
iTextSharp.text.pdf.PdfCopy.set_PageEvent(IPdfPageEvent value)
Below is the code I have written to preform the operation:
PdfReader pdf = new PdfReader(file.Value);
int pages = pdf.NumberOfPages;
pdf.SelectPages(string.Format("0 - {0}", pages));
using (MemoryStream stream = new MemoryStream())
{
Document doc = new Document();
PdfCopy copy = new PdfCopy(doc, stream) { PageEvent = new PdfFooterStamp() };
doc.Open();
for (int x = 0, y = pages; x < y; x++)
{
copy.AddPage(copy.GetImportedPage(pdf, x + 1));
}
doc.Close();
copy.Flush();
copy.Close();
collection[file.Key] = stream.ToArray();
}
And this is my custom event class definition:
public class PdfFooterStamp : PdfPageEventHelper
{
public override void OnEndPage(PdfWriter writer, Document document)
{
Rectangle rect = writer.PageSize;
ColumnText.ShowTextAligned(writer.DirectContent,
Element.ALIGN_CENTER, new Phrase("PERSONALISED DOCUMENT"),
(rect.Left + rect.Right) / 2, rect.Bottom - 18, 0);
base.OnEndPage(writer, document);
}
}
Is there anyone that might have an idea as to what might be going wrong?
Following #BrunoLowagie's advice, I went with the former option to create a PageStamp from an imported page and alter it's content as I iterated over the collection of imported PDF.
You can keep on using PdfCopy and use PageStamp to add the text to
each page that is added. Or you can create the PDF in two passes:
first create the concatenated PDF in memory with PdfCopy; then add the
footer with PdfStamper in a second pass.
The reason my previous attmepts had not worked was due to the fact that,
PdfPageEventHelper and PdfCopy are mutually exclusive. You can't
define a page event when using PdfCopy - #BrunoLowagie
The following code is an example of the preferred solution and tests have proven it to work as intended.
Document doc = new Document();
PdfCopy copy = new PdfCopy(doc, stream);
doc.Open();
for (int x = 0, y = pages; x < y; x++)
{
PdfImportedPage import = copy.GetImportedPage(pdf, x + 1);
PageStamp stamp = copy.CreatePageStamp(import);
Rectangle rect = stamp.GetUnderContent().PdfWriter.PageSize;
ColumnText.ShowTextAligned(stamp.GetUnderContent(),
Element.ALIGN_CENTER, new Phrase(User.Identity.Name, font),
(rect.Bottom + rect.Top) / 2, rect.Bottom + 8, 0);
stamp.AlterContents();
copy.AddPage(import);
}
I'm trying the create a multiple-page pdf using iTextSharp, but I'm having some issue creating a loop to have more than a single page.
My code below is working well for one page; however, my content will not fit into a single page. Thanks.
How can I write a loop to write contents on the first pdf page and the remaining to the second, third, etc...So far, I'm only seeing one page. Thank you.
int height = 600;
int totalPage = 1;
int oldPage = 1;
bool cons = false;
bool st = false;
foreach (string al in combo)
foreach (string al in combo) //my loop to write on the pdf but "combo" has 100 lines, which would fit into a single page.
{
string[] word = al.Split(',');
int strIndex = combo.IndexOf(al);
if (al.Length != 0)
{
if (word[0].ToString().ToUpper().Contains("(REV")==true && cons == false)
{
cb.SetFontAndSize(BaseFont.CreateFont(BaseFont.HELVETICA_BOLD, BaseFont.CP1252, BaseFont.NOT_EMBEDDED), 11);
cb.BeginText();
// put the alignment and coordinates here
cb.ShowTextAligned(PdfContentByte.ALIGN_LEFT, "CONSTRUCTION PRINTS", 80, height, 0);
cb.EndText();
height = height - 20;
cons = true;
}
if (word[0].ToString().ToUpper().Contains("(REV")==false && st == false)
{
cb.SetFontAndSize(BaseFont.CreateFont(BaseFont.HELVETICA_BOLD, BaseFont.CP1252, BaseFont.NOT_EMBEDDED), 11);
cb.BeginText();
// put the alignment and coordinates here
cb.ShowTextAligned(PdfContentByte.ALIGN_LEFT, "SAG & TENSION CHARTS", 80, height, 0);
cb.EndText();
height = height - 20;
st = true;
}
cb.SetFontAndSize(BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED), 11);
totalPage = totalPage + oldPage;
// write the text in the pdf content
cb.BeginText();
// put the alignment and coordinates here
cb.ShowTextAligned(PdfContentByte.ALIGN_LEFT, word[0].ToString().ToUpper(), 80, height, 0);
cb.EndText();
// write the text in the pdf content
cb.BeginText();
// put the alignment and coordinates here
cb.ShowTextAligned(PdfContentByte.ALIGN_CENTER, "......................................................................................", 335, height, 0);
cb.EndText();
// write the text in the pdf content
cb.BeginText();
// put the alignment and coordinates here
cb.ShowTextAligned(PdfContentByte.ALIGN_RIGHT, totalPage.ToString(), 500, height, 0);
cb.EndText();
oldPage = Convert.ToInt32(word[1]);
}
height = height - 20;
}
//// create the new page and add it to the pdf
// reader = new PdfReader(oldFile);//old file
PdfImportedPage page = writer.GetImportedPage(reader, 1);
cb.AddTemplate(page, 0, 0);
//// close the streams and voilá the file should be changed :)
document.Close();
reader.Close();
fs.Close();
writer.Close();
Please go to the official documentation and click Q&A to go to the most frequently asked questions. Pick the Getting started category. The first thing you'll see, is the most popular iText example (which I am porting to C# for your convenience):
// step 1
Document document = new Document();
// step 2
FileStream fs = new FileStream("hello.pdf", FileMode.Create);
PdfWriter.GetInstance(document, fs);
// step 3
document.Open();
// step 4
document.Add(new Paragraph("Hello World!"));
// step 5
document.Close();
In your code, you are adding text at absolute positions by introducing PDF syntax, such as BeginText() (BT) / EndText() (ET) / SetFontAndSize() (Tf). This is creating PDF the hard way. If is very easy to make mistakes, as shown in this question: What is causing syntax errors in a page created with iText?
If you don't know the PDF reference (ISO 32000-1) by heart, you should avoid code like this. Instead you can use objects such as Paragraph, List, PdfPTable... The beauty of adding these objects to a Document using the Add() method, is that a new page gets triggered automagically as soon as a page is full. You can also trigger a new page yourself using:
document.NewPage();
If you want to add text at absolute positions, iText offers convenience methods and objects such as ColumnText. See my answer to Adding footer to existing PDF
You can define a Rectangle and add objects such as Paragraph, List, PdfPTable... Take a look at the answer to the question How to continue an ordered list on a second page? for inspiration:
ColumnText ct = new ColumnText(cb);
ct.AddElement(list);
Rectangle rect = new Rectangle(36, 36, 559, 806);
ct.SetSimpleColumn(rect);
int status = ct.Go();
while (ColumnText.HasMoreText(status)) {
document.NewPage();
ct.SetSimpleColumn(rect);
ct.Go();
}
The while loop is the loop you are looking for.
I'm working on program that insert some kind of watermark on existing pdf document. The watermark isn't image but text generated during program's work. I need to rotate the text, send it to center of page, and those things are done, but the text has many lines. So I put new line character "\n" in Phrase but PDF prints only characters before first use of "\n". How could I overlay some text on pdf document (mainly scanned docs) and rotate it?
My source code (simplified):
PdfContentByte canvas = stamper.GetOverContent(1);
String message = "some\n multiline \n expression";
Phrase text = new Phrase(text);
ColumnText.ShowTextAligned(canvas, Element.ALIGN_MIDDLE, text, size.Width / 2, size.Height / 2, 30);
That is documented: the ShowTextAligned() method is for single lines only. If you want multiple lines, you need to use a ColumnText object, define a Rectangle using the SetSimpleColumn() method and draw the content using the Go() method.
If you want to rotate such a Rectangle, you need to create a PdfTemplate first. Render the ColumnText to this PdfTemplate and add the template to the document.
Adding the template to the document can be done with the AddTemplate() method. In this case, you need some algebra to rotate the template. This is the algebra you'll need for the parameters:
a = cosine(angle);
b = sine(angle);
c = -sine(angle);
d = cosine(angle);
e = x;
f = y;
If you can't get the algebra working, you can wrap the template inside an Image object:
Image img = Image.getInstance(template);
Now you have different methods to introduce a rotation, including a simple one that merely requires an angle in degrees.
P.S.: wrapping a template inside an image doesn't "rasterize" the text. It creates a Form XObject (as opposed to an Image XObject).
I solved the problem in another way:
using (PdfStamper stamper = new PdfStamper(reader, ms))
{
PdfContentByte canvas = stamper.GetOverContent(1);
String message = String.Format("mutli\nline\text");
}
PdfGState gState = new PdfGState();
gState.FillOpacity = 0.3f;
canvas.SetGState(gState);
using (StringReader stringReader = new StringReader(message))
{
string line;
float y = size.Height / 2 + 200;
while ((line = stringReader.ReadLine()) != null)
{
Phrase p = new Phrase(line,FontFactory.GetFont(FontFactory.HELVETICA, 35));
ColumnText.ShowTextAligned(canvas, Element.ALIGN_CENTER, p, 300, y, 30);
y = y - 70;
}
}
}