iText7 flips text - bug? - itext7

Consider a 1-page PDF document named in.pdf that looks like this:
We execute the following code in C#:
using (var reader = new PdfReader(#"C:\<path>\in.pdf"))
using (var writer = new PdfWriter(#"C:\<path>\out.pdf"))
using (var pdfDoc = new PdfDocument(reader, writer))
{
var page = pdfDoc.GetFirstPage();
var pdfCanvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdfDoc);
using (var canvas = new Canvas(pdfCanvas, new Rectangle(400, 300)))
{
canvas.Add(new Paragraph("new text\r\nnew text")
.SetFontColor(new DeviceRgb(255, 0, 0))
.SetFontSize(50));
}
pdfCanvas.Release();
}
We get new text appearing under the original text, as expected:
Now we execute the same code with one change - we create PdfCanvas as follows:
var pdfCanvas = new PdfCanvas(page);
Result is still as expected - new text is above the original text:
Finally, we execute the same code with PdfCanvas created as follows:
var pdfCanvas = new PdfCanvas(page.NewContentStreamAfter(), page.GetResources(), pdfDoc);
New text is above the original text as expected, but it's flipped!
Is this a bug? Getting the same flipped output creating PdfCanvas like this:
var pdfCanvas = new PdfCanvas(page.GetFirstContentStream(), page.GetResources(), pdfDoc);
//OR
var pdfCanvas = new PdfCanvas(page.GetLastContentStream(), page.GetResources(), pdfDoc);
Using nuget package itext7 v7.1.15.

Is this a bug?
No. It's matching your code and a feature of the PdfCanvas(PdfPage page) constructor.
Each PDF content stream contains a sequence of instructions that change the current graphics state and/or draw something. If the original creator of the respective content stream did not take care to clean up the graphics state, instructions you add to it or to a following content stream of the same page are subject to those graphics state changes.
In your case the original page content stream apparently at the end has a graphics state with the current transformation matrix set to a reflection. Depending on how you add your new content, your additions also are subject to that reflection transformation:
var pdfCanvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdfDoc);
Here you add a new content stream before all existing content streams. Thus, your additions to pdfCanvas are not subject to any graphics state changes and you get upright text.
var pdfCanvas = new PdfCanvas(page);
The constructor you use here, PdfCanvas(PdfPage page), calls another constructor, PdfCanvas(PdfPage page, bool wrapOldContent), which for wrapOldContent == true wraps the existing content in an envelope of a save-graphics-state / restore-graphics-state instruction pair, to restore the original graphics state after the existing content. PdfCanvas(PdfPage page) uses a true value for wrapOldContent under certain conditions, in particular if you are manipulating an existing PDF and the page in question already has a non-empty content stream.
In your case, therefore, the original content is wrapped in such an envelope and the graphics state is restored thereafter. So your additions are not subject to any graphics state changes and you get upright text.
var pdfCanvas = new PdfCanvas(page.NewContentStreamAfter(), page.GetResources(), pdfDoc);
var pdfCanvas = new PdfCanvas(page.GetFirstContentStream(), page.GetResources(), pdfDoc);
var pdfCanvas = new PdfCanvas(page.GetLastContentStream(), page.GetResources(), pdfDoc);
The PdfCanvas constructors you use here do not add any such envelope. Thus, your additions are subject to graphics state changes in the original content and you get mirrored text.

Related

iText PDF using .getSplitRenderer for Table renderer

In iText PDF 7, I am using the .layout method of the Table renderer to determine whether a table will break across a page.
However, when I add the .getSplitRenderer (returned from the layout result object) as a child of the Documents's renderer, I get this error: "java.lang.IndexOutOfBoundsException".
I'm using iText PDF version 7.1.7 in its Java incarnation. The last three entries in the stacktrace are:
java.util.ArrayList$SubList.rangeCheck(ArrayList.java:1225)
java.util.ArrayList$SubList.get(ArrayList.java:1042)
com.itextpdf.layout.renderer.TableBorders.processAllBordersAndEmptyRows(TableBorders.java:139)
Here is a bare-bones version of the code that triggers the error:
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PdfWriter pdfWriter = new PdfWriter(outputStream);
PdfDoc pdfDoc = new PdfDocument(pdfWriter);
PageSize pageSize = new PageSize(612, 792);
Document doc = new Document(pdfDoc, pageSize);
Table table = new Table([50, 50, 50]);
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 3; j++) {
Cell cell = new Cell();
cell.setHeight(100);
table.addCell(cell);
}
}
LayoutContext context = new LayoutContext(doc.getRenderer().getCurrentArea().clone());
TableRenderer tableRenderer = (TableRenderer)table.createRendererSubTree();
LayoutResult result = tableRenderer.setParent(doc.getRenderer()).layout(context);
if (result.getStatus() == result.PARTIAL) {
tableRenderer = (TableRenderer) result.getSplitRenderer();
doc.getRenderer().addChild(tableRenderer); // this is where the error occurs
}
When you add a child to the DocumentRenderer it will layout and draw it automatically. It is not possible to layout a renderer several times in most cases (although what can be improved here is the exception type and message).
If you want to draw the part that fits immediately you can use the following line:
tableRenderer.draw(new DrawContext(pdfDocument, new PdfCanvas(pdfDocument.getPage(doc.getRenderer().getCurrentArea().getPageNumber()))));
Complete if expression:
if (result.getStatus() == LayoutResult.PARTIAL) {
tableRenderer = (TableRenderer) result.getSplitRenderer();
tableRenderer.draw(new DrawContext(pdfDocument, new PdfCanvas(pdfDocument.getPage(doc.getRenderer().getCurrentArea().getPageNumber()))));
}
It might have some drawbacks in complex cases though, so if you are dealing with complex layout or tagged documents I would recommend using binary search to determine the amount of content that still fits and add that content as an element to Document instance still.
An approach that is between those two is adding the table completely and then removing the extra pages from PdfDocument. In this case keep in mind that you will have to recreate the DocumentRenderer because it does not keep track of low level events like page removal from PdfDocument.

Issue scaling the first page of a PDF using iText7 for .NET

I'm trying to scale the first page of a PDF using iText7 for .NET. The rest of the pages should remain untouched.
The method below works if the PDF contains one page, but if there's multiple pages, the first (supposed to be scaled) page is blank, while the remaining pages is added correctly.
What am I missing here?
public byte[] ScaleFirstPagePdf(byte[] pdf)
{
using (var inputStream = new MemoryStream(pdf))
using (var outputStream = new MemoryStream(pdf))
using (var srcPdf = new PdfDocument(new PdfReader(inputStream)))
using (var destPdf = new PdfDocument(new PdfWriter(outputStream)))
{
for (int pageNum = 1; pageNum <= srcPdf.GetNumberOfPages(); pageNum++)
{
var srcPage = srcPdf.GetPage(pageNum);
var srcPageSize = srcPage.GetPageSizeWithRotation();
if (pageNum == 1)
{
var destPage = destPdf.AddNewPage(new PageSize(srcPageSize));
var canvas = new PdfCanvas(destPage);
var transformMatrix = AffineTransform.GetScaleInstance(0.5f, 0.5f);
canvas.ConcatMatrix(transformMatrix);
var pageCopy = srcPage.CopyAsFormXObject(destPdf);
canvas.AddXObject(pageCopy, 0, 0);
}
else
{
destPdf.AddPage(srcPage.CopyTo(destPdf));
}
}
destPdf.Close();
srcPdf.Close();
return outputStream.ToArray();
}
}
I couldn't reproduce the blank page issue with this code, but definitely the files that are generated in this way can be problematic.
The issue is that you are sharing a byte buffer between two memory streams - one used for reading and another one for writing, simultaneously.
Simply using another buffer or relying on the default MemoryStream implementation solved the issue for me, and should do so for you as well because there doesn't seem to be anything suspicious about your code apart from the problem I mentioned.
Here is how you should create the output stream:
using (var inputStream = new MemoryStream(pdf))
using (var outputStream = new MemoryStream())
If you still experience issues even after this tweak then the problem is definitely file-specific and I doubt you could get any help without sharing the file.

Missing font resource dictionary after stamping text in iText 7 [duplicate]

This question already has an answer here:
Why font is not being embedded to the PDF using content stream before in Itext7?
(1 answer)
Closed 4 years ago.
I have an existing PDF that doesn't contain any fonts (image only). I want to stamp some additional text onto the first page using low level canvas operations. When I do this in iText 7, the resulting PDF is missing the Fonts resource dictionary entry (which results in an NPE when parsing the resulting file).
Do I have to do something besides canvas.setFontAndSize() to get the font resource to get added to the output?
Here's a unit test that recreates the issue:
public class CheckFontResourceInclusion {
#Test
public void test() throws Exception {
// create a document to stamp
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try(PdfDocument doc = new PdfDocument(new PdfWriter(baos))){
doc.addNewPage();
}
// stamp it
StampingProperties stampProps = new StampingProperties();
PdfFont font = PdfFontFactory.createFont();
ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
try(PdfDocument doc = new PdfDocument(new PdfReader(new ByteArrayInputStream(baos.toByteArray())), new PdfWriter(resultStream), stampProps)){
PdfPage page = doc.getPage(1);
PdfCanvas canvas = new PdfCanvas(page.newContentStreamAfter(), new PdfResources(), doc);
canvas.beginText();
canvas.setTextRenderingMode(2);
canvas.setFontAndSize(font, 42);
canvas.setTextMatrix(1, 0, 0, -1, 100, 100);
canvas.showText("TEXT TO STAMP");
canvas.endText();
}
// parse text
try(PdfDocument doc = new PdfDocument(new PdfReader(new ByteArrayInputStream(resultStream.toByteArray())))){
LocationTextExtractionStrategy strat = new LocationTextExtractionStrategy();
PdfCanvasProcessor processor = new PdfCanvasProcessor(strat);
processor.processPageContent(doc.getPage(1));
Assert.assertEquals("TEXT TO STAMP", strat.getResultantText());
}
}
}
Here's the resulting failure:
java.lang.NullPointerException
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor$SetTextFontOperator.invoke(PdfCanvasProcessor.java:811)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor.invokeOperator(PdfCanvasProcessor.java:456)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor.processContent(PdfCanvasProcessor.java:285)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor.processPageContent(PdfCanvasProcessor.java:306)
at
The error is the same as in this earlier question: You use a throw-away resources object, so the font resource is missing in the result.
You can fix this by using the actual page resources. Simply replace
PdfCanvas canvas = new PdfCanvas(page.newContentStreamAfter(), new PdfResources(), doc);
by
PdfCanvas canvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), doc);

iTextSharp : Cannot attach PageEvent on a PdfSmartCopy writer

This code using ItextSharp 5.5.10:
var msOutput = new MemoryStream();
var document = new Document(PageSize.A4, 0, 0, 0, 20);
var writer = new PdfSmartCopy(document, msOutput);
writer.PageEvent = new MyHeaderFooterEvents();
Throws "Operation is not valid due to the current state of the object." when assigning the "writer.PageEvent" (also fails when doing a parameterless new Document()).
When this code works perfectly:
var outputStream = new MemoryStream();
var document = new Document(PageSize.A4, leftMargin, rightMargin, topMargin, bottomMargin);
var writer = PdfWriter.GetInstance(document, outputStream);
writer.PageEvent = new MyHeaderFooterEvents();
Any idea ?
The Pdf[Smart]Copy classes are intended for read-only usage. It's documented in the raw source code:
/// Setting page events isn't possible with Pdf(Smart)Copy.
/// Use the PageStamp class if you want to add content to copied pages.
Note to the iText development team - if XML Documentation Comments using the <summary> tag are used instead of the current style, comments will show up in Visual Studio IntelliSense.

iTextSharp Image Bring To Front

Building a document generation system for our web app and am branding the document as required. The document is designed in powerpoint and printed to via NitroPdf. The first page is a large image essentially, with a white area in the image.
I am attempting to place the branding logo in the whitespace allocated. Positioning is ok, however, my branding image is appearing behind the PDF'd document full page image.
Having googled, i can't seem to find a 'z-index' type function... would have thought i wouldn't be the only one with the issue?
Section of code adding the image is as follows:
image.ScaleToFit(width, height);
image.SetDpi(300, 300);
// Position the logo.
image.SetAbsolutePosition(fromLeft, fromBottom);
// Add the image.
document.Add(image);
It is very strange that you would need the following line to add an image to an existing PDF:
document.Add(image);
It's as if you're using PdfWriter instead of PdfStamper, which would be very strange.
Perhaps you overlooked the documentation or maybe you didn't search StackOverflow before you started writing your code: How can I insert an image with iTextSharp in an existing PDF?
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
class Program
{
static void Main(string[] args)
{
using (Stream inputPdfStream = new FileStream("input.pdf", FileMode.Open, FileAccess.Read, FileShare.Read))
using (Stream inputImageStream = new FileStream("some_image.jpg", FileMode.Open, FileAccess.Read, FileShare.Read))
using (Stream outputPdfStream = new FileStream("result.pdf", FileMode.Create, FileAccess.Write, FileShare.None))
{
var reader = new PdfReader(inputPdfStream);
var stamper = new PdfStamper(reader, outputPdfStream);
var pdfContentByte = stamper.GetOverContent(1);
Image image = Image.GetInstance(inputImageStream);
image.SetAbsolutePosition(100, 100);
pdfContentByte.AddImage(image);
stamper.Close();
}
}
}
You may have found examples where GetUnderContent() is used. This adds content under the existing content. If you want the content to cover the existing content, you need GetOverContent() as is shown in the code sample.
Maybe it's a bit late, but I've faced with the same issue and I've solved with a workaround with Paragraphs (hereunder the code in Visual Basic):
Public Class PDF
Public Doc As Document
Public Writer As PdfWriter
Public Cb As PdfContentByte
Public Sub setFrontImage(ByVal _appendImg As String, align As Integer, x As Integer, y As Integer, ByVal w As Integer, h As Integer, _leading As Integer)
Dim ct As New ColumnText(Cb)
Dim ph As Phrase
Dim ch As Chunk
Dim p As Paragraph = new Paragraph()
Dim image As iTextSharp.text.Image = iTextSharp.text.Image.GetInstance(_appendImg)
image.ScaleAbsolute(w, h)
p.Add(new Chunk(image,x,y))
ct.SetSimpleColumn(p,x, y, w, h, _leading, align)
ct.Go()
End Sub
End Class
I saw you used the absolute position to put your logo up your image so am I, consider to modify the usage of Chunk with width and height if you don't need to fit it inside a restricted space.

Resources