MarkovskI
MarkovskI

Reputation: 1587

Itextsharp form gets flattened even when the FormFlattening property is false

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

Answers (1)

mkl
mkl

Reputation: 96029

It looks like you found a bug in iText(Sharp). But there is an additional weakness in your code, too.

The bug

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.

The issue in your code

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.

A misunderstanding

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

Related Questions