Insert Element between two elements with iTextSharp - c#

I want to add an extra Table between two tables in a pdf file.
Things to consider:
The Blue and the Red one always have a fixed height.
The Green table height between different file may change and after adding the Red element in the middle, we may need two pages.
What I know? I know how to make a table and add it to a pdf file.
My Question: Is it possible to move the Green Element and add the Red one between? How?

As you say that you know how to make a table and add it to a pdf file and that the Blue and the Red one always have a fixed height, let's assume
you have already created the table for the red part in a separate PDF,
you have determined the y coordinate at which to split the input document to insert the red part, and
you also have determined between which y coordinates in the separate PDF the table to insert is located.
Thus, the problem is reduced to
"cutting" the input document page into three stripes,
the stripe above the splitting y coordinate containing the blue box,
the stripe below the splitting y coordinate containing as much of the green box as will still fit on that page after insertion of the red box, and
the stripe at the bottom of the page containing the remainder of the green box that will overflow to a new second page,
"cutting" a stripe from the separate PDF containing the red box to insert, and
"pasting" these stripes together in the desired new order.
For this you can use a tool like this:
public class CutAndPasteTool
{
readonly Document document;
readonly PdfWriter pdfWriter;
readonly Dictionary<string, PdfTemplate> templates = new Dictionary<string, PdfTemplate>();
public CutAndPasteTool(Rectangle pageSize, Stream os)
{
document = new Document(pageSize);
pdfWriter = PdfWriter.GetInstance(document, os);
document.Open();
}
public ICutter CreateCutter(PdfReader pdfReader, int pageNumber)
{
return new SimpleCutter(pdfReader, pageNumber, pdfWriter, templates);
}
public void Paste(string name, float x, float y)
{
pdfWriter.DirectContent.AddTemplate(templates[name], x, y);
}
public void NewPage()
{
document.NewPage();
}
public void Close()
{
document.Close();
}
class SimpleCutter : ICutter
{
readonly PdfImportedPage importedPage;
readonly Dictionary<string, PdfTemplate> templates;
internal SimpleCutter(PdfReader pdfReader, int pageNumber, PdfWriter pdfWriter, Dictionary<string, PdfTemplate> templates)
{
this.importedPage = pdfWriter.GetImportedPage(pdfReader, pageNumber);
this.templates = templates;
}
public void Cut(string name, Rectangle rectangle)
{
PdfTemplate template = importedPage.CreateTemplate(rectangle.Width, rectangle.Height);
template.AddTemplate(importedPage, importedPage.BoundingBox.Left - rectangle.Left, importedPage.BoundingBox.Bottom - rectangle.Bottom);
templates.Add(name, template);
}
}
}
public interface ICutter
{
void Cut(string name, Rectangle rectangle);
}
Using this tool you can cut the stripes and paste them like this:
Rectangle pageSize = PageSize.A4; // The page size of the result file
int doc1page = 1; // The number of the page in the input PDF
float doc1split = 600; // The splitting y coordinate in the input PDF
int doc2page = 1; // The number of the page in the separate PDF
float doc2from = 700; // The top y coordinate of the table in the separate PDF
float doc2to = 600; // The bottom y coordinate of the table in the separate PDF
using (PdfReader reader1 = new PdfReader(...)) // The input PDF file
using (PdfReader reader2 = new PdfReader(...)) // The separate PDF file
using (Stream os = new FileStream(..., FileMode.Create)) // The stream to write the result to
{
Rectangle doc1box = reader1.GetPageSize(doc1page);
Rectangle doc1upper = new Rectangle(doc1box);
doc1upper.Bottom = doc1split;
Rectangle doc1lower = new Rectangle(doc1box);
doc1lower.Top = doc1split;
Rectangle doc2box = reader2.GetPageSize(doc2page);
Rectangle doc2inset = new Rectangle(doc2box);
doc2inset.Top = doc2from;
doc2inset.Bottom = doc2to;
float doc1lowerRemainHeight = pageSize.Height - doc1upper.Height - doc2inset.Height;
float doc1lowerOverflowHeight = doc1lower.Height - doc1lowerRemainHeight;
Rectangle doc1lowerRemain = new Rectangle(doc1lower);
doc1lowerRemain.Bottom = doc1lowerRemain.Top - doc1lowerRemainHeight;
Rectangle doc1lowerOverflow = new Rectangle(doc1lower);
doc1lowerOverflow.Top = doc1lowerRemain.Bottom;
CutAndPasteTool tool = new CutAndPasteTool(pageSize, os);
ICutter cutterDoc1 = tool.CreateCutter(reader1, doc1page);
cutterDoc1.Cut("Top1", doc1upper);
cutterDoc1.Cut("Bottom1Remain", doc1lowerRemain);
cutterDoc1.Cut("Bottom1Overflow", doc1lowerOverflow);
ICutter cutterDoc2 = tool.CreateCutter(reader2, doc2page);
cutterDoc2.Cut("Inset", doc2inset);
float y = pageSize.Top;
tool.Paste("Top1", 0, y-= doc1upper.Height);
tool.Paste("Inset", 0, y -= doc2inset.Height);
tool.Paste("Bottom1Remain", 0, y -= doc1lowerRemain.Height);
tool.NewPage();
y = pageSize.Top;
tool.Paste("Bottom1Overflow", 0, y -= doc1lowerOverflow.Height);
tool.Close();
}
Beware: This tool only works for content in the page content streams or content streams referenced from there, in particular not for content in annotations or XFA forms.
In case of annotations one can extend the solution to associate each annotation with one of those stripes (which might be difficult for border cases) and copy stripes and associated annotations together.
In case of XFA form tables this approach won't work unless you flatten the form first.
Furthermore, there now is some hidden text which nonetheless can be copied and pasted elsewhere. If that is not acceptable, one can replace the SimpleCutter by a more advanced ICutter implementation which removes (and not only hides) content outside of stripes, e.g. using the iText PdfCleanUpProcessor.

Related

iText 7 C# creating a pdf from a template and adding text to it

I have a one page pdf template and need to create a new document with several pages. Each page needs to be as the first page of the template. Then i need to add text to each page. The pages are copied but the text is not added.
This is my code:
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(stream));
PdfDocument cover = new PdfDocument(new PdfReader(templatePath));
//First copy the pages
var totalPages=5;
var coverPage = cover.GetPage(1);
for (int i = 0; i < totalPages; i++)
{
//If i do it to a blank page the text is visible
//pdfDoc.AddNewPage();
//I have tried both methods:
pdfDoc.AddPage(coverPage.CopyTo(pdfDoc));
//cover.CopyPagesTo(1, 1, pdfDoc);
}
//Now i try to add text
Document doc = new Document(pdfDoc);
var font = PdfFontFactory.CreateFont(fontPath);
for (int i = 1; i <= totalPages; i++)
{
//Edited
Rectangle pagesize = pdfDoc.GetPage(i).GetPageSize();
doc.ShowTextAligned(new Paragraph("HEADER").SetFont(font).SetFontSize(22), pagesize.GetLeft(), pagesize.GetBottom(), i, TextAlignment.LEFT, VerticalAlignment.BOTTOM, 0);
//doc.ShowTextAligned(new Paragraph("HEADER").SetFont(font), 100, 700, i, TextAlignment.CENTER, VerticalAlignment.TOP, 0);
}
doc.Close();
cover.Close();
pdfDoc.Close();
I have tried this options:
Canvas instead of document with no result (see code below)
If i use the AddNewPage() and not the cover page, then the text is added to the blank page (both document and canvas methods).
If i open and write directly to the template document the text is visible but the size is very small and position of the text is different compared to 2)
This is the canvas code inside the for instruction:
var canvas = new PdfCanvas(pdfDoc.GetPage(i));
canvas.BeginText()
.SetFontAndSize(font, 22) //Edited
.MoveText(100, 700)
.ShowText("HEADER")
.EndText();
//UPDATED
Following the solution contributed by #mkl, i have changed the way i add the pages:
var coverPage = cover.GetPage(1);
Rectangle coverSize = coverPage.GetPageSize();
for (int i = 0; i < totalPaginas; i++)
{
//Taken from this example: https://kb.itextpdf.com/home/it7kb/ebooks/itext-7-jump-start-tutorial-for-java/chapter-6-reusing-existing-pdf-documents
PdfPage page = pdfDoc.AddNewPage(PageSize.A4);
PdfCanvas canvas = new PdfCanvas(page);
AffineTransform transformationMatrix = AffineTransform.GetScaleInstance(
page.GetPageSize().GetWidth() / coverSize.GetWidth(),
page.GetPageSize().GetHeight() / coverSize.GetHeight());
canvas.ConcatMatrix(transformationMatrix);
var pageCopy = coverPage.CopyAsFormXObject(pdfDoc);
canvas.AddXObjectAt(pageCopy, 0, 0);
//pdfDoc.AddNewPage();
//pdfDoc.AddPage(coverPage.CopyTo(pdfDoc));
//cover.CopyPagesTo(1, 1, pdfDoc);
}
Now i can see the text added, but the font size is much smaller than if instead of copying i do "pdfDoc.AddNewPage()", why is it? i would like it to be the correct font size.
Your code works in my tests. Maybe (100,700) is outside of the visual page area, which would typically be the case if your template page does not have its lower left corner at (0,0).
This should put the text in the lower left corner:
Rectangle pagesize = pdfDoc.GetPage(i).GetPageSize();
doc.ShowTextAligned(new Paragraph("HEADER"), pagesize.GetLeft(),
pagesize.GetBottom(), i, TextAlignment.LEFT, VerticalAlignment.BOTTOM, 0);
If that works, you can work from the pagesize rectangle to calculate the appropriate position for the text.
The solution to why the text dont appear is updated in my question at the bottom.
The reason why the coordinates dont match and the size is so small is the source template pdf that was exported with a very high px/inch for high level printing. Reducing it to 72ppp was the fix.

How to scale text within a fixed rectangle with itext7?

I'm trying to make a pdf document with itext7 in c# which should have fixed rectangles containing varying text that should scale within the boundaries of the (invisible) rectangles.
I have tried to find if there's automatic scaling, but so far only found auto-scaling for formfields. Since the pdf will be used for plotting text, formfields are of no use.
Code below is a snippet placing a 'box' with fixed dimensions, where all the text should be shown scaled (on one line)
float fontSize = 22f;
Text lineTxt = new Text("A VERY LONG TEXT SHOULD BE SCALED").SetFont(lineFont).SetFontSize(fontSize);
iText.Kernel.Geom.Rectangle lineTxtRect = new iText.Kernel.Geom.Rectangle(100, posHeight - 200, (float)plotline.producttype_plotmaxwidthpts, (float)plotline.producttype_plotmaxheightpts);
Div lineDiv = new Div();
lineDiv.SetMaxHeight((float)plotline.producttype_plotmaxheightpts);
lineDiv.SetWidth((float)plotline.producttype_plotmaxwidthpts);
lineDiv.SetHeight((float)plotline.producttype_plotmaxheightpts);
lineDiv.SetVerticalAlignment(VerticalAlignment.MIDDLE);
lineDiv.SetBorder(new DashedBorder(1));
Paragraph linePara = new Paragraph().Add(lineTxt).
SetTextAlignment(iText.Layout.Properties.TextAlignment.CENTER).
SetBorder(new DottedBorder(1)).
SetMultipliedLeading(0.7f).
SetMaxHeight((float)plotline.producttype_plotmaxheightpts).
SetHeight((float)plotline.producttype_plotmaxheightpts);
lineDiv.Add(linePara);
new Canvas(PageCanvas, pdf, lineTxtRect).Add(lineDiv).SetBorder(new SolidBorder(1f));
Layout module of iText 7 allows you to simulate rendering of an element (by creating the renderer tree from the element and then using Layout method) and check whether it fits the given area (by checking LayoutResult object). Thus what you can do is check whether the text fits into your fixed rectangle with the given font size. Then you can just do a binary search on the font size.
Here is a sample code:
PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
Text lineTxt = new Text("A VERY LONG TEXT SHOULD BE SCALED");
iText.Kernel.Geom.Rectangle lineTxtRect =
new iText.Kernel.Geom.Rectangle(100,200,100,100);
Div lineDiv = new Div();
lineDiv.SetVerticalAlignment(VerticalAlignment.MIDDLE);
lineDiv.SetBorder(new DashedBorder(1));
Paragraph linePara =
new Paragraph()
.Add(lineTxt)
.SetTextAlignment(iText.Layout.Properties.TextAlignment.CENTER)
.SetBorder(new DottedBorder(1))
.SetMultipliedLeading(0.7f);
lineDiv.Add(linePara);
// 1 is the font size that is definitely small enough to draw all the text
float fontSizeL = 1;
// 20 is the maximum value of the font size you want to use
float fontSizeR = 20;
Canvas canvas =
new Canvas(
new PdfCanvas(pdfDocument.AddNewPage()),
pdfDocument,
lineTxtRect);
// Binary search on the font size
while (Math.Abs(fontSizeL - fontSizeR) > 1e-1) {
float curFontSize = (fontSizeL + fontSizeR) / 2;
lineDiv.SetFontSize(curFontSize);
// It is important to set parent for the current element renderer
// to a root renderer.
IRenderer renderer =
lineDiv.CreateRendererSubTree()
.SetParent(canvas.GetRenderer());
LayoutContext context =
new LayoutContext(
new LayoutArea(1, lineTxtRect));
if (renderer.Layout(context).GetStatus() == LayoutResult.FULL) {
// we can fit all the text with curFontSize
fontSizeL = curFontSize;
} else {
fontSizeR = curFontSize;
}
}
// Use the biggest font size that is still small enough to fit all the
// text.
lineDiv.SetFontSize(fontSizeL);
canvas.Add(lineDiv);
pdfDocument.Close();

PdfSmartCopy is copying the contents at the bottom (footer) of the new PDF file

I have a function which is cropping the specific part of the pdf file and adding it into the new Pdf file but the main problem that i am getting is that it is showing the cropped part of the page into the bottom (footer) of the newly created pdf file.
Here is the code..
public static void CropPdfFile(string sourceFilePath, string outputFilePath)
{
// Allows PdfReader to read a pdf document without the owner's password
PdfReader.unethicalreading = true;
// Reads the PDF document
using (PdfReader pdfReader = new PdfReader(sourceFilePath))
{
// Set which part of the source document will be copied.
// PdfRectangel(bottom-left-x, bottom-left-y, upper-right-x, upper-right-y)
PdfRectangle rect = new PdfRectangle(0f, 9049.172f, 594.0195f, 700.3f);
using (var output = new FileStream(outputFilePath, FileMode.CreateNew, FileAccess.Write))
{
// Create a new document
using (Document doc = new Document())
{
// Make a copy of the document
PdfSmartCopy smartCopy = new PdfSmartCopy(doc, output);
// Open the newly created document
doc.Open();
// Loop through all pages of the source document
for (int i = 4; i <= pdfReader.NumberOfPages; i++)
{
// Get a page
var page = pdfReader.GetPageN(i);
// Apply the rectangle filter we created
page.Put(PdfName.CROPBOX, rect);
page.Put(PdfName.MEDIABOX, rect);
// Copy the content and insert into the new document
smartCopy.SetLinearPageMode();
var copiedPage = smartCopy.GetImportedPage(pdfReader, i);
smartCopy.AddPage(copiedPage);
}
// Close the output document
doc.Close();
}
}
}
Please help me to solve this..
The size of a PDF page (expressed in user units) depends on the value of the mediabox. For instance: The media box of an A4 page is usually defined like this [0 0 595 842]
In this case, the origin of the coordinate system (0, 0) coincides with the lower-left corner. The coordinate of the upper right corner is (595, 842).
Another possible value for an A4 page would be [0 842 595 1684]. Same width (595 user units), same height (1684 - 842 = 842 user units), but the lower-left corner now has the coordinate (0, 842) and the coordinate of the upper-right corner is (595, 1684).
You write that you create a PdfRectangle using these parameters: (bottom-left-x, bottom-left-y, upper-right-x, upper-right-y). However, you're using these hard-coded values: 0f, 9049.172f, 594.0195f, 700.3f.
Your lower-left-y (9049.172) is at a higher position than your upper-right-y (700.3). This doesn't really make sense. Hence: you should consider changing that value to something that does make sense. What value that should be, is a question only you can answer since only you know the value of the MediaBox of the file you want to crop.
In your comment, you explain that your PDF is an A4 page. You can check this by using the PdfReader method named getPageSize(). If you want to crop the page so that you only see the header of your document, you need to use something like this:
PdfRectangle rect = new PdfRectangle(0f, 842 - x, 595, 842);
Where x is the height of the header. For instance, if the header is 100 user units, then you'd need:
PdfRectangle rect = new PdfRectangle(0f, 742, 595, 842);
It is unclear why you're always talking about 100px. If you want to convert pixels to points, please read Convert Pixels to Points
Using the formula mentioned there 100 pixels equals 75 points.

Determine the paper size of a PDF document

Is there any way to the determine the paper size of a PDF document in C#. I found a dll called PdfPrintNet that will allow me to specify the paper size, but it doesn't have any methods for determining the current paper size.
PdfReader reader = new PdfReader(m);
PdfImportedPage page = writer.GetImportedPage(reader, i);
//size information
int wid=page.PageSize.Width
int heigh=page.PageSize.Height
Go through this.
Might be helpful.
If you are working with the PdfPrintNet dll, you can use PdfPrint.PaperSize Property to get the parer-size of a pdf document.
Hope it helps :)
iTextSharp should be able to help you out here.
public float GetPageHeight(string PathToPDF)
{
var reader = new PdfReader(PathToPDF);
// A post script point is 0.352777778mm
const float postScriptPoints = (float)0.352777778;
// The height is returned in post script points from iTextSharp
float height = reader.GetPageSizeWithRotation(1).Height * postScriptPoints;
reader.Close();
return height;
}
public float GetPageWidth(string PathToPDF)
{
var reader = new PdfReader(PathToPDF);
// A post script point is 0.352777778mm
const float postScriptPoints = (float)0.352777778;
// The height is returned in post script points from iTextSharp
float width = reader.GetPageSizeWithRotation(1).Width * postScriptPoints;
reader.Close();
return width;
}
Code modified from How to check a PDFs page size with iTextSharp

Creating PDF with overflow layout via iTextSharp

I'm successfully creating a PDF document from a DataTable via iTextSharp, but I can't get the layout in a desireable format.
Though unlikely, the DataTable has the potential for dozens of columns. Imagine the following DataTable - each number represents the area that can fit on a page:
|------------|
| 1 : 2 : 3 |
|------------|
| 4 : 5 : 6 |
|------------|
This should export as a 6 page PDF document in the order I've numbered the sections. Instead, it's currently generating as a two page document with 40-some columns squished on each page with a font so small it literally has no detail.
The simplest way to explain what I want is: When a generated PDF prints, I want it to print like a very wide Excel sheet rather than squishing all the content together.
My code is as follows:
public static void ExportAsPDF(DataTable Table, IList<string> Columns, string filename)
{
int ColumnCount = Table.Columns.Count;
int RowCount = Table.Rows.Count;
iTextSharp.text.Table BodyTable = new iTextSharp.text.Table(ColumnCount, RowCount);
BodyTable.AutoFillEmptyCells = true;
foreach (string s in Columns)
{
BodyTable.AddCell(s);
}
foreach (object o in from DataRow row in Table.Rows from o in row.ItemArray select o)
{
BodyTable.AddCell(o.ToString());
}
Document doc = new Document();
PdfWriter.GetInstance(doc, HttpContext.Current.Response.OutputStream);
doc.Open();
doc.Add(BodyTable);
doc.Close();
HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.AddHeader("content-disposition", string.Format("attachment; filename={0}.pdf", filename));
HttpContext.Current.Response.End();
}
All input and help is much appreciated. Thanks
Okay, two options.
Draw everything yourself by hand with PdfContentByte and ColumnText for the text layout part.
Convince the normal iText Layout code that your page is wide enough to hold an entire row, then split those pages into pieces later. Not Pretty, but probably easier than the alternative.
First you need to define a page that is 3 times wider than normal.
Rectangle triplePageRect = new Rectangle(PageSize.LETTER);
float origWidth = triplePageRect.getWidth();
triplePageRect.setWidth(origWidth * 3f);
Document doc = new Document(triplePageRect);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter writer = PdfWriter.getInstance(doc, baos);
Then you draw your table pretty much the way you are now... BUT you must be sure that a column edge lines up with your two page edges so you can easily break up the pages later. Bonus points if you can create an empty row centered on where the page break will be so you have a margin.
//Your Code Here
Finally, you need to save your PDF, open it again, and chop it to ribbons. I'm thinking PdfStamper.
// write everything out to the baos.
doc.close();
// and suck it right back up again. Hurray for efficiency. Or something.
PdfReader reader = new PdfReader(baos.toByteArrayOrWhateverItsCalled());
PdfStamper stamper = new PdfStamper( reader, new FileOutputStream(outputPath));
// duplicate the pages. I'll assume only one page, but you'll get the idea.
PdfDictionary origPage = reader.getPageN(1);
for (int i = 0; i < 2; ++i) {
// initial size is irrelevant, we're going to change it, but !null
stamper.insertPage(2+i, PageSize.LETTER);
PdfDictionary newPageDict = reader.getPage(2 + i);
// copy the original page... note that this is a shallow copy
newPageDict.putAll(origPageDict);
// duplicate the page rect so each page will have its own copy
PdfArray pageRect = newPageDict.getAsArray(PdfName.MEDIABOX);
// also a shallow copy, but changes to this array will be localized to the page.
PdfArray newRect = new PdfArray(pageRect);
// page rects are defined as [llx lly urx ury], so we need to change 0 and 2.
newRect.set(0, new PdfNumber(origWidth * (i+1));
newRect.set(2, new PdfNumber(origWidth * (i+2));
}
//Smoke'em if you've got 'em folks, we're done.
stamper.close();
Damn I'm good. Oooh ooh! And modest! Let's not forget good looking, brave, courteous, witty...

Categories