Reputation: 1587
I have the following method which should stamp an image on the pdf file at the given coordinates, and return it with the layers still separated i.e. not flattened, I set the FormFlattening property but it doesn't work.
After some experimentation I found that when I called the getPdfLayers method the file would not be flattened, why is this so?
public static byte[] StampLayer(System.Drawing.Image image, int x, int y, string layername)
{
var iImage = iTextSharp.text.Image.GetInstance(image, ImageFormat.Tiff);
var reader = new PdfReader(_pdfFile);
using (var ms = new MemoryStream())
{
using (var stamper = new PdfStamper(reader, ms))
{
//Don't delete otherwise the stamper flattens the layers
var layers = stamper.GetPdfLayers();
stamper.FormFlattening = false;
var logoLayer = new PdfLayer(layername, stamper.Writer);
PdfContentByte cb = stamper.GetUnderContent(1);
cb.BeginLayer(logoLayer);
//300dpi
iImage.ScalePercent(24f);
iImage.SetAbsolutePosition(x, y);
cb.AddImage(iImage);
cb.EndLayer();
stamper.Close();
return (ms.GetBuffer());
}
}
}
iTextSharp version is: 5.5.6
I've tried png and jpg images, the result is the same.
I'm using this file to test.
Upvotes: 0
Views: 1063
Reputation: 96029
It looks like you found a bug in iText(Sharp). But there is an additional weakness in your code, too.
PdfStamperImp
(the class of stamper.Writer
) is derived from PdfWriter
. PdfWriter
keeps a collection of the layers of the PDF it writes:
protected Dictionary<IPdfOCG, object> documentOCG = new Dictionary<IPdfOCG,object>();
PdfStamperImp
only lazily initializes this member with the existing layers of a document, e.g. in its method GetPdfLayers
:
virtual public Dictionary<string,PdfLayer> GetPdfLayers()
{
if (documentOCG.Count == 0)
{
ReadOCProperties();
}
...
As you see it uses the count of the documentOCG
dictionary as indicator whether or not initialization has already happened.
Unfortunately, though,
var logoLayer = new PdfLayer(layername, stamper.Writer);
breaks this lazy initialization scheme: It executes
writer.RegisterLayer(this);
and RegisterLayer
is defined in PdfWriter
doing
documentOCG[layer] = null;
under the given circumstances.
Thus, after new PdfLayer(layername, stamper.Writer)
the documentOCG.Count
is greater 0 which prevents the lazy layer initialization and so effectively removes vital layer information during stamping unless initialization has already occurred before.
Your work-around
//Don't delete otherwise the stamper flattens the layers
var layers = stamper.GetPdfLayers();
essentially enforces initialization to take place before the PdfLayer
constructor is called.
This bug could be fixed by overriding RegisterLayer
in PdfStamperImp
(it would have to be made virtual of course); the override would have to first trigger lazy initialization itself. Actually lazy initialization should use an independent flag and check the dictionary count as a sanity check.
An analog bug exists in iText and can be reproduced using StampInLayer.java.
You return
return (ms.GetBuffer());
which is utterly wrong: The buffer usually is larger than the actual file, i.e. you return a PDF with a long tail of trash bytes. Use
return (ms.ToArray());
instead.
In your question and your code you assumed your problem to be form flattening and tried to intervene. Form flattening has nothing to do with your problem, though. Form flattening is about flattening (merging into content) AcroForm form field values, e.g. text fields or check boxes.
Upvotes: 5