How to edit PdfTemplate width (in Java) - itext

Is it possible to edit width of PdfTemplate object before close of document ?
In my pdf process I create a document, and set a template for write total page number. I set width but it doesnot works :
// onOpenDocument
PdfTemplate pageNumTemplate = writer.getDirectContent().createTemplate(10, 20);
globalDataMap.put("myTemplate", pageNumTemplate)
// onCloseDocument
Font font = (Font) globalDataMap.get("myFont");
String pagenum = String.valueOf(writer.getPageNumber());
float widthPoint = font.getBaseFont().getWidthPoint(pagenum, font.getSize());
PdfTemplate pdfTemplate = (PdfTemplate) globalDataMap.get("myTemplate");
pdfTemplate.setWidth(widthPoint);
ColumnText.showTextAligned(pdfTemplate, Element.ALIGN_LEFT, new Phrase(pagenum), 0, 1, 0);

An error in the OP's initial code
You call ColumnText.showTextAligned with a String as third parameter:
String pagenum = String.valueOf(writer.getPageNumber());
[...]
ColumnText.showTextAligned(pdfTemplate, Element.ALIGN_LEFT, pagenum, 0, 1, 0);
But all ColumnText.showTextAligned overloads expect a Phrase there:
public static void showTextAligned(final PdfContentByte canvas, final int alignment, final Phrase phrase, final float x, final float y, final float rotation)
public static void showTextAligned(final PdfContentByte canvas, int alignment, final Phrase phrase, final float x, final float y, final float rotation, final int runDirection, final int arabicOptions)
(at least up to the current 5.5.9-SNAPSHOT development version).
Thus, you might want to use
ColumnText.showTextAligned(pdfTemplate, Element.ALIGN_LEFT, new Phrase(pagenum), 0, 1, 0);
instead.
But now it works
Based on the OP's code I built this proof-of-concept:
try ( FileOutputStream stream = new FileOutputStream(new File(RESULT_FOLDER, "dynamicTemplateWidths.pdf")) )
{
Document document = new Document(PageSize.A6);
PdfWriter writer = PdfWriter.getInstance(document, stream);
writer.setPageEvent(new PdfPageEventHelper()
{
PdfTemplate dynamicTemplate = null;
Font font = new Font(BaseFont.createFont(), 12);
String postfix = "0123456789";
#Override
public void onOpenDocument(PdfWriter writer, Document document)
{
super.onOpenDocument(writer, document);
dynamicTemplate = writer.getDirectContent().createTemplate(10, 20);
}
#Override
public void onEndPage(PdfWriter writer, Document document)
{
writer.getDirectContent().addTemplate(dynamicTemplate, 100, 300);
}
#Override
public void onCloseDocument(PdfWriter writer, Document document)
{
float widthPoint = font.getBaseFont().getWidthPoint(postfix, font.getSize());
dynamicTemplate.setWidth(widthPoint / 2.0f);
ColumnText.showTextAligned(dynamicTemplate, Element.ALIGN_LEFT, new Phrase(String.valueOf(writer.getPageNumber()) + "-" + postfix), 0, 1, 0);
}
});
document.open();
document.add(new Paragraph("PAGE 1"));
document.newPage();
document.add(new Paragraph("PAGE 2"));
document.close();
}
The output:
Considering I set the template width to half the width of the postfix 0123456789 in onCloseDocument, this is exactly as expected.

Related

multiple signing pdf iText

im trying to sign a document several times simulating a signature by different users using itext 5.5.13.1, PdfStamper is on AppendMode. If document has not signatures, the certification level is CERTIFIED_NO_CHANGES_ALLOWED or CERTIFIED_FORM_FILLING_AND_ANNOTATIONS, else i dont set this param for PdfSignatureAppearence. After the second signing the first signature is invalid, because the document was changed. Any ideas how to fix this?
Here's my code:
public void Sign(string Thumbprint, string document, string logoPath) {
X509Certificate2 certificate = FindCertificate(Thumbprint);
PdfReader reader = new PdfReader(document);
//Append mode
PdfStamper st = PdfStamper.CreateSignature(reader, new FileStream(SignedDocumentPath(document), FileMode.Create, FileAccess.Write), '\0', null, true);
int signatureWidth = 250;
int signatureHeight = 100;
int NewXPos = 0;
int NewYPos = 0;
SetStampCoordinates(reader, st, ref NewXPos, ref NewYPos, signatureWidth, signatureHeight);
PdfSignatureAppearance sap = st.SignatureAppearance;
if (reader.AcroFields.GetSignatureNames().Count == 0)
{
SetSignatureFieldOptions(certificate, sap, reader, "1", 1, NewXPos, NewYPos, signatureWidth, signatureHeight);
}
else {
SetSignatureFieldOptions(certificate, sap, reader, "2", NewXPos, NewYPos, signatureWidth, signatureHeight);
}
Image image = Image.GetInstance(logoPath);
image.ScaleAbsolute(50, 50);
Font font1 = SetFont("TIMES.TTF", BaseColor.BLUE, 10, 0);
Font font2 = SetFont("TIMES.TTF", BaseColor.BLUE, 8, 0);
PdfTemplate layer = sap.GetLayer(2);
Chunk chunk1 = new Chunk($"\r\nДОКУМЕНТ ПОДПИСАН\r\nЭЛЕКТРОННОЙ ПОДПИСЬЮ\r\n", font1);
Chunk chunk2 = new Chunk($"Сертификат {certificate.Thumbprint}\r\n" +
$"Владелец {certificate.GetNameInfo(X509NameType.SimpleName, false)}\r\n" +
$"Действителен с {Convert.ToDateTime(certificate.GetEffectiveDateString()).Date.ToShortDateString()} " +
$"по {Convert.ToDateTime(certificate.GetExpirationDateString()).Date.ToShortDateString()}\r\n", font2);
PdfTemplate layer0 = sap.GetLayer(0);
image.SetAbsolutePosition(5, 50);
layer0.AddImage(image);
Paragraph para1 = SetParagraphOptions(chunk1, 1, 50, 0, 2, 1.1f);
Paragraph para2 = SetParagraphOptions(chunk2, 0, 5, 15, 0.5f, 1.1f);
ColumnText ct = new ColumnText(layer);
ct.SetSimpleColumn(3f, 3f, layer.BoundingBox.Width - 3f, layer.BoundingBox.Height);
ct.AddElement(para1);
ct.AddElement(para2);
ct.Go();
layer.SetLineWidth(3);
layer.SetRGBColorStroke(0, 0, 255);
layer.Rectangle(0, 0, layer.BoundingBox.Right, layer.BoundingBox.Top);
layer.Stroke();
EncryptDocument(certificate, sap);
}
public X509Certificate2 FindCertificate(string Thumbprint) {
X509Store store = new X509Store("My", StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
X509Certificate2Collection found = store.Certificates.Find(
X509FindType.FindByThumbprint, Thumbprint, true);
X509Certificate2 certificate = found[0];
if (certificate.PrivateKey is Gost3410_2012_256CryptoServiceProvider cert_key)
{
var cspParameters = new CspParameters
{
KeyContainerName = cert_key.CspKeyContainerInfo.KeyContainerName,
ProviderType = cert_key.CspKeyContainerInfo.ProviderType,
ProviderName = cert_key.CspKeyContainerInfo.ProviderName,
Flags = cert_key.CspKeyContainerInfo.MachineKeyStore
? (CspProviderFlags.UseExistingKey | CspProviderFlags.UseMachineKeyStore)
: (CspProviderFlags.UseExistingKey),
KeyPassword = new SecureString()
};
certificate = new X509Certificate2(certificate.RawData)
{
PrivateKey = new Gost3410_2012_256CryptoServiceProvider(cspParameters)
};
}
return certificate;
}
public Font SetFont(string TTFFontName, BaseColor color, float size, int style) {
string ttf = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), TTFFontName);
BaseFont baseFont = BaseFont.CreateFont(ttf, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
Font font = new Font(baseFont, size, style);
font.Color = color;
return font;
}
public Paragraph SetParagraphOptions(Chunk chunk, int ParagraphAligment, float marginLeft, float marginTop, float fixedLeading, float multipledLeading) {
Paragraph paragraph = new Paragraph();
paragraph.Alignment = ParagraphAligment;
paragraph.IndentationLeft = marginLeft;
paragraph.SpacingBefore = marginTop;
paragraph.SetLeading(fixedLeading, multipledLeading);
paragraph.Add(chunk);
return paragraph;
}
public string SignedDocumentPath(string document) {
string filename = String.Concat(Path.GetFileNameWithoutExtension(document), "_signed.pdf");
string path = Path.Combine(Path.GetDirectoryName(document), filename);
return path;
}
public void SetSignatureFieldOptions(X509Certificate2 certificate, PdfSignatureAppearance sap, PdfReader reader, string field, int level, int XPos, int YPos, int width, int height)
{
X509CertificateParser parser = new X509CertificateParser();
try
{
Rectangle rectangle = new Rectangle(XPos, YPos, XPos + width, YPos + height);
sap.SetVisibleSignature(rectangle, reader.NumberOfPages, field);
sap.Certificate = parser.ReadCertificate(certificate.RawData);
sap.Reason = "I agree";
sap.Location = "Location";
sap.Acro6Layers = true;
sap.SignDate = DateTime.Now;
sap.CertificationLevel = level;
}
catch (Exception ex)
{
if (ex.Message == $"The field {certificate.Thumbprint} already exists.")
throw new Exception("Вы уже подписали данный документ");
}
}
public void SetSignatureFieldOptions(X509Certificate2 certificate, PdfSignatureAppearance sap, PdfReader reader,string field, int XPos, int YPos, int width, int height) {
X509CertificateParser parser = new X509CertificateParser();
try
{
Rectangle rectangle = new Rectangle(XPos, YPos, XPos + width, YPos + height);
sap.SetVisibleSignature(rectangle, reader.NumberOfPages, field);
sap.Certificate = parser.ReadCertificate(certificate.RawData);
sap.Reason = "I agree";
sap.Location = "Location";
sap.Acro6Layers = true;
sap.SignDate = DateTime.Now;
}
catch (Exception ex) {
if (ex.Message == $"The field {certificate.Thumbprint} already exists.")
throw new Exception("Вы уже подписали данный документ");
}
}
public void EncryptDocument(X509Certificate2 certificate, PdfSignatureAppearance sap) {
PdfName filterName;
if (certificate.PrivateKey is Gost3410CryptoServiceProvider)
filterName = new PdfName("CryptoPro#20PDF");
else
filterName = PdfName.ADOBE_PPKLITE;
PdfSignature dic = new PdfSignature(filterName, PdfName.ADBE_PKCS7_DETACHED);
dic.Date = new PdfDate(sap.SignDate);
dic.Name = certificate.GetNameInfo(X509NameType.SimpleName, false);
if (sap.Reason != null)
dic.Reason = sap.Reason;
if (sap.Location != null)
dic.Location = sap.Location;
sap.CryptoDictionary = dic;
int intCSize = 4000;
Dictionary<PdfName, int> hashtable = new Dictionary<PdfName, int>();
hashtable[PdfName.CONTENTS] = intCSize * 2 + 2;
sap.PreClose(hashtable);
Stream s = sap.GetRangeStream();
MemoryStream ss = new MemoryStream();
int read = 0;
byte[] buff = new byte[8192];
while ((read = s.Read(buff, 0, 8192)) > 0)
{
ss.Write(buff, 0, read);
}
ContentInfo contentInfo = new ContentInfo(ss.ToArray());
SignedCms signedCms = new SignedCms(contentInfo, true);
CmsSigner cmsSigner = new CmsSigner(certificate);
signedCms.ComputeSignature(cmsSigner, false);
byte[] pk = signedCms.Encode();
byte[] outc = new byte[intCSize];
PdfDictionary dic2 = new PdfDictionary();
Array.Copy(pk, 0, outc, 0, pk.Length);
dic2.Put(PdfName.CONTENTS, new PdfString(outc).SetHexWriting(true));
sap.Close(dic2);
}
The Change
The most important part of your screenshot
is the text "1 Page(s) Modified" between the signatures on the signature panel. This tells us that you do other changes than merely adding and filling signature fields. Inspecting the file itself one quickly recognizes the change:
In the original sample.pdf
there is just a single content stream.
In sample_signed.pdf with one signature
there are three content streams, the original one enveloped by the new ones.
In sample_signed_signed.pdf with two signatures
there are five content streams, the former three enveloped by two new ones.
So in each signing pass you change the page content. As you can read in this answer, changes to the page content of signed documents are always disallowed. It doesn't even help that the contents of the added streams are trivial, each stream added in front contains:
q
and each stream added at the end contains
Q
q
Q
i.e. only some saving and restoring the graphics state happens.
The Cause
The changes described above are typical preparation steps done by the PdfStamper method GetOverContent, wrapping the original content in a q ... Q (save & restore graphics state) envelope to prevent changes there to influence additions in the OverContent and starting a new block also enveloped in such an envelope. That the latter block remained empty, indicates that the OverContent has not been edited.
I don't find such a call in the code you posted, but in your code the method SetStampCoordinates is missing. Do you probably call GetOverContent for the PdfStamper argument in that method?

Itexpdf PdfContentByte special character

I'm using ItextPdf 5.
How I can add special characters to my document via PdfContentByte ?
there is my code :
public void addFlag(PdfWriter writer, BaseFont baseFont, Font font) {
String mytext = "My flag : \u2691";
PdfContentByte cb = writer.getDirectContent();
cb.setFontAndSize(baseFont, font.getSize());
cb.beginText();
cb.showTextAligned(Element.ALIGN_LEFT, mytext, 10, 50, 0f);
cb.endText();
cb.stroke();
}

How do i use iText to have a landscaped PDF on half of a A4 back to portrait and full size on A4

I have a landscaped form lay on a top half of A4 page, I want it to be rotated and enlarge to a portrait layout size fill up the A4 then saved before it is faxed out. Otherwise, the fax service program will fax it out with only partial info. Here is my attempt, result is the same as the input pdf. This is my first day on programming using iText, all the google not getting me what I want. Please let me know if you can help. Thanks,
public class CopeALandscapePdfFiletoPortraitPdfFile {
//public static final String SRC = "resources/pdfs/landscapeForm.pdf";
public static final String SRC = "resources/pdfs/potraitForm.pdf";
public static final String DEST = "results/stamper/portraitFormAfterCopy.pdf";
public static void main(String[] args) throws IOException, DocumentException
{
copyPdf();
}
private static void copyPdf() throws IOException, DocumentException
{
Document document = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(DEST));
document.open();
PdfContentByte cb = writer.getDirectContent();
PdfReader reader = new PdfReader(SRC);
document.newPage();
int n = reader.getNumberOfPages();
PdfDictionary page;
PdfNumber rotate;
for (int p = 1; p <= n; p++) {
page = reader.getPageN(p);
rotate = page.getAsNumber(PdfName.ROTATE);
if (rotate == null) {
page.put(PdfName.ROTATE, new PdfNumber(90));
} else {
page.put(PdfName.ROTATE, new PdfNumber((rotate.intValue() + 90) % 360));
}
}
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(DEST));
stamper.close();
PdfImportedPage ipage = writer.getImportedPage(stamper.getReader(), 1);
cb.addTemplate(ipage, 0, 0);
document.close();
}
}
As you want to enlarge the PDF anyways, I would put enlarging and rotating into one afine transformation. Thus:
PdfReader reader = new PdfReader(SOURCE);
Document document = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.getInstance(document, RESULT);
document.open();
double sqrt2 = Math.sqrt(2);
Rectangle pageSize = reader.getPageSize(1);
PdfImportedPage importedPage = writer.getImportedPage(reader, 1);
writer.getDirectContent().addTemplate(importedPage, 0, sqrt2, -sqrt2, 0, pageSize.getTop() * sqrt2, -pageSize.getLeft() * sqrt2);
document.close();
(EnlargePagePart.java)
E.g. for this page
it generates

How can you eliminate white-space in multiple columns using iTextSharp?

I'd like to add a Paragraph of text to pages in 2 columns. I understand that MultiColumnText has been eliminated. I know I can create column 1, write to it, and if there is more text create column 2 and write to it. If there is still more text, go to the next page and repeat.
However I always end up with either:
a long chunk of text orphaned in the left column.
a full left column and partially used right column.
How can I format my content in 2 columns while reducing white space, such as compressing the columns so I end with 2 full columns of equal length?
Thanks!
You should add your column twice, once in simulation mode and once for real.
I have adapted the ColumnTextParagraphs example to show what is meant by simulation mode. Take a look at the ColumnTextParagraphs2 example:
We add the column in simulation mode to obtain the total height needed for the column. This is done in the following method:
public float getNecessaryHeight(ColumnText ct) throws DocumentException {
ct.setSimpleColumn(new Rectangle(0, 0, COLUMN_WIDTH, -500000));
ct.go(true);
return -ct.getYLine();
}
We use this height when we add the left and right column:
Rectangle left;
float top = COLUMNS[0].getTop();
float middle = (COLUMNS[0].getLeft() + COLUMNS[1].getRight()) / 2;
float columnheight;
int status = ColumnText.START_COLUMN;
while (ColumnText.hasMoreText(status)) {
if (checkHeight(height)) {
columnheight = COLUMNS[0].getHeight();
left = COLUMNS[0];
}
else {
columnheight = (height / 2) + ERROR_MARGIN;
left = new Rectangle(
COLUMNS[0].getLeft(),
COLUMNS[0].getTop() - columnheight,
COLUMNS[0].getRight(),
COLUMNS[0].getTop()
);
}
// left half
ct.setSimpleColumn(left);
ct.go();
height -= COLUMNS[0].getTop() - ct.getYLine();
// separator
canvas.moveTo(middle, top - columnheight);
canvas.lineTo(middle, top);
canvas.stroke();
// right half
ct.setSimpleColumn(COLUMNS[1]);
status = ct.go();
height -= COLUMNS[1].getTop() - ct.getYLine();
// new page
document.newPage();
}
This is how we check the height:
public boolean checkHeight(float height) {
height -= COLUMNS[0].getHeight() + COLUMNS[1].getHeight() + ERROR_MARGIN;
return height > 0;
}
As you can see, we add the full columns as long as the height of both columns is smaller than the remaining height. When columns are added, we adjust the remaining height. As soon as the height is lower than the height of to columns, we adapt the height of the first column.
Note that we work with an ERROR_MARGIN because dividing by two often leads to a situation where the second column has one line more than the first column. It is better when it's the other way around.
This is the full example at your request:
/**
* Example written by Bruno Lowagie in answer to:
* http://stackoverflow.com/questions/29378407/how-can-you-eliminate-white-space-in-multiple-columns-using-itextsharp
*/
package sandbox.objects;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfWriter;
public class ColumnTextParagraphs2 {
public static final String DEST = "results/objects/column_paragraphs2.pdf";
public static final String TEXT = "This is some long paragraph that will be added over and over again to prove a point.";
public static final float COLUMN_WIDTH = 254;
public static final float ERROR_MARGIN = 16;
public static final Rectangle[] COLUMNS = {
new Rectangle(36, 36, 36 + COLUMN_WIDTH, 806),
new Rectangle(305, 36, 305 + COLUMN_WIDTH, 806)
};
public static void main(String[] args) throws IOException, DocumentException {
File file = new File(DEST);
file.getParentFile().mkdirs();
new ColumnTextParagraphs2().createPdf(DEST);
}
public void createPdf(String dest) throws IOException, DocumentException {
// step 1
Document document = new Document();
// step 2
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(dest));
// step 3
document.open();
// step 4
PdfContentByte canvas = writer.getDirectContent();
ColumnText ct = new ColumnText(canvas);
addContent(ct);
float height = getNecessaryHeight(ct);
addContent(ct);
Rectangle left;
float top = COLUMNS[0].getTop();
float middle = (COLUMNS[0].getLeft() + COLUMNS[1].getRight()) / 2;
float columnheight;
int status = ColumnText.START_COLUMN;
while (ColumnText.hasMoreText(status)) {
if (checkHeight(height)) {
columnheight = COLUMNS[0].getHeight();
left = COLUMNS[0];
}
else {
columnheight = (height / 2) + ERROR_MARGIN;
left = new Rectangle(
COLUMNS[0].getLeft(),
COLUMNS[0].getTop() - columnheight,
COLUMNS[0].getRight(),
COLUMNS[0].getTop()
);
}
// left half
ct.setSimpleColumn(left);
ct.go();
height -= COLUMNS[0].getTop() - ct.getYLine();
// separator
canvas.moveTo(middle, top - columnheight);
canvas.lineTo(middle, top);
canvas.stroke();
// right half
ct.setSimpleColumn(COLUMNS[1]);
status = ct.go();
height -= COLUMNS[1].getTop() - ct.getYLine();
// new page
document.newPage();
}
// step 5
document.close();
}
public void addContent(ColumnText ct) {
for (int i = 0; i < 35; i++) {
ct.addElement(new Paragraph(String.format("Paragraph %s: %s", i, TEXT)));
}
}
public float getNecessaryHeight(ColumnText ct) throws DocumentException {
ct.setSimpleColumn(new Rectangle(0, 0, COLUMN_WIDTH, -500000));
ct.go(true);
return -ct.getYLine();
}
public boolean checkHeight(float height) {
height -= COLUMNS[0].getHeight() + COLUMNS[1].getHeight() + ERROR_MARGIN;
return height > 0;
}
}

How can I combine multiple PDF files excluding page breaks using iTextSharp?

I wonder if anyone has done this with iTextSharp, but I would like to combine multiple PDF files into one but leave the page breaks out. For example, I would like to create 4 PDF files containing 3 lines of text each, so I want the resulting file to have all 12 lines in 1 page. Is this possible?
As the OP also tagged this question with [iText] and I am more at home with Java than .Net, here an answer for iText/Java. It should be easy to translate to iTextSharp/C#.
The original question
I would like to combine multiple PDF files into one but leave the page breaks out. For example, I would like to create 4 PDF files containing 3 lines of text each, so I want the resulting file to have all 12 lines in 1 page.
For PDF files as indicated in that example you can use this simple utility class:
public class PdfDenseMergeTool
{
public PdfDenseMergeTool(Rectangle size, float top, float bottom, float gap)
{
this.pageSize = size;
this.topMargin = top;
this.bottomMargin = bottom;
this.gap = gap;
}
public void merge(OutputStream outputStream, Iterable<PdfReader> inputs) throws DocumentException, IOException
{
try
{
openDocument(outputStream);
for (PdfReader reader: inputs)
{
merge(reader);
}
}
finally
{
closeDocument();
}
}
void openDocument(OutputStream outputStream) throws DocumentException
{
final Document document = new Document(pageSize, 36, 36, topMargin, bottomMargin);
final PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
this.document = document;
this.writer = writer;
newPage();
}
void closeDocument()
{
try
{
document.close();
}
finally
{
this.document = null;
this.writer = null;
this.yPosition = 0;
}
}
void newPage()
{
document.newPage();
yPosition = pageSize.getTop(topMargin);
}
void merge(PdfReader reader) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
for (int page = 1; page <= reader.getNumberOfPages(); page++)
{
merge(reader, parser, page);
}
}
void merge(PdfReader reader, PdfReaderContentParser parser, int page) throws IOException
{
TextMarginFinder finder = parser.processContent(page, new TextMarginFinder());
Rectangle pageSizeToImport = reader.getPageSize(page);
float heightToImport = finder.getHeight();
float maxHeight = pageSize.getHeight() - topMargin - bottomMargin;
if (heightToImport > maxHeight)
{
throw new IllegalArgumentException(String.format("Page %s content too large; height: %s, limit: %s.", page, heightToImport, maxHeight));
}
if (heightToImport > yPosition - pageSize.getBottom(bottomMargin))
{
newPage();
}
else if (!writer.isPageEmpty())
{
heightToImport += gap;
}
yPosition -= heightToImport;
PdfImportedPage importedPage = writer.getImportedPage(reader, page);
writer.getDirectContent().addTemplate(importedPage, 0, yPosition - (finder.getLly() - pageSizeToImport.getBottom()));
}
Document document = null;
PdfWriter writer = null;
float yPosition = 0;
final Rectangle pageSize;
final float topMargin;
final float bottomMargin;
final float gap;
}
If you have a list of PdfReader instances inputs, you can merge them like this into an OutputStream output:
PdfDenseMergeTool tool = new PdfDenseMergeTool(PageSize.A4, 18, 18, 5);
tool.merge(output, inputs);
This creates a merged document using an A4 page size, a top and bottom margin of 18/72" each and a gap between contents of different PDF pages of 5/72".
The comments
The iText TextMarginFinder (used in the PdfDenseMergeTool above) only considers text. If other content types also are to be considered, this class has to be extended somewhat.
Each PDF has just a few lines, perhaps a table or an image, but I want the end result in one page.
If the tables contain decorations reaching above or below the text content (e.g. lines or colored backgrounds), you should use a larger gap value. Unfortunately the parsing framework used by the TextMarginFinder does not forward vector graphics commands to the finder.
If the images are bitmap images, the TextMarginFinder should be extended by implementing its renderImage method to take the image area into account, too.
Also, some of the PDFs may contain fields, so I'd like to keep those fields in the resulting combined PDF as well.
If AcroForm fields are also to be considered, you have to
extend the rectangle represented by the TextMarginFinder to also include the visualization rectangles of the widget annotations, and
extend the PdfDenseMergeTool.merge(PdfReader, PdfReaderContentParser, int) method to also copy those widget annotations.
Update
I wrote above
Unfortunately the parsing framework used by the TextMarginFinder does not forward vector graphics commands to the finder.
Meanwhile (in version 5.5.6) that parsing framework has been extended to also forward vector graphics commands.
If you replace the line
TextMarginFinder finder = parser.processContent(page, new TextMarginFinder());
by
MarginFinder finder = parser.processContent(page, new MarginFinder());
using the MarginFinder class presented at the bottom of this answer, all content is considered, not merely text.
For those of you who want the above code in C#, here you go.
using System;
using System.Collections.Generic;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;
namespace Test.WebService.Support {
public class PDFMerge {
private Rectangle PageSize;
private float TopMargin;
private float BottomMargin;
private float Gap;
private Document Document = null;
private PdfWriter Writer = null;
private float YPosition = 0;
public PDFMerge(Rectangle size, float top, float bottom, float gap) {
this.PageSize = size;
this.TopMargin = top;
this.BottomMargin = bottom;
this.Gap = gap;
} // PDFMerge
public void Merge(MemoryStream outputStream, List<PdfReader> inputs) {
try {
this.OpenDocument(outputStream);
foreach (PdfReader reader in inputs) {
this.Merge(reader);
}
} finally {
this.CloseDocument();
}
} // Merge
private void Merge(PdfReader reader) {
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
for (int p = 1; p <= reader.NumberOfPages; p++) {
this.Merge(reader, parser, p);
}
} // Merge
private void Merge(PdfReader reader, PdfReaderContentParser parser, int pageIndex) {
TextMarginFinder Finder = parser.ProcessContent(pageIndex, new TextMarginFinder());
Rectangle PageSizeToImport = reader.GetPageSize(pageIndex);
float HeightToImport = Finder.GetHeight();
float MaxHeight = PageSize.Height - TopMargin - BottomMargin;
if (HeightToImport > MaxHeight) {
throw new ArgumentException(string.Format("Page {0} content too large; height: {1}, limit: {2}.", pageIndex, HeightToImport, MaxHeight));
}
if (HeightToImport > YPosition - PageSize.GetBottom(BottomMargin)) {
this.NewPage();
} else if (!Writer.PageEmpty) {
HeightToImport += Gap;
}
YPosition -= HeightToImport;
PdfImportedPage ImportedPage = Writer.GetImportedPage(reader, pageIndex);
Writer.DirectContent.AddTemplate(ImportedPage, 0, YPosition - (Finder.GetLly() - PageSizeToImport.Bottom));
} // Merge
private void OpenDocument(MemoryStream outputStream) {
Document Document = new Document(PageSize, 36, 36, this.TopMargin, BottomMargin);
PdfWriter Writer = PdfWriter.GetInstance(Document, outputStream);
Document.Open();
this.Document = Document;
this.Writer = Writer;
this.NewPage();
} // OpenDocument
private void CloseDocument() {
try {
Document.Close();
} finally {
this.Document = null;
this.Writer = null;
this.YPosition = 0;
}
} // CloseDocument
private void NewPage() {
Document.NewPage();
YPosition = PageSize.GetTop(TopMargin);
} // NewPage
}
}