Search code examples
c#itextitext7

iTextSharp replace image with another PDF


I have a PDF file with some images, which I want to replace with some other PDF. The code goes through the pdf and gets the image references:

PdfDocument pdf = new PdfDocument(new PdfReader(args[0]), new PdfWriter(args[1]));
for(int i=1; i<=pdf.GetNumberOfPages(); ++i)
{
    PdfDictionary pageDict  = pdf.GetPage(i).GetPdfObject();
    PdfDictionary resources = pageDict.GetAsDictionary(PdfName.Resources);
    PdfDictionary xObjects  = resources.GetAsDictionary(PdfName.XObject);

    foreach (PdfName imgRef in xObjects.KeySet())
    {
        // image reference
    }
}

For all my images I have a corresponding PDF which I would like to replace the image with. What I tried is to Put the other PDF (which is always a single page) as object by:

PdfDocument other = new PdfDocument(new PdfReader("replacement.pdf"));
xObjects.Put(imgRef, other.GetFirstPage().GetPdfObject().Clone());

But while closing the PdfDocument an exception is thrown:

iText.Kernel.PdfException: 'Pdf indirect object belongs to other PDF document. Copy object to current pdf document.'

How can I achieve to replace the image with (the content of) another PDF?


Update

I also tried a few other approaches, which maybe improved results. To overcome the previous error message, I copy the page to the original pdf by:

var page = other.GetFirstPage().CopyTo(pdf);

However, replacing the xObject doesn't work:

xObjects.Put(imgRef, page.GetPdfObject());

Results in a corrupted PDF.


Solution

  • To just copy the original page into another document to be used as an image replacement, you can use PdfPage#CopyAsFormXObject.

    So let's assume we have this PDF as a template and we want to replace the image of a desert with the contents of another PDF:

    template

    Let's also assume the PDF that we want to use as a replacement looks as follows:

    insertion

    The issue is that if we blindly replace the original image with the contents of the PDF, chances are we will get something like this:

    potential result

    So we will get a feeling that everything worked well while we still have a bad visual result. The issue is that coordinates work a bit differently for plain raster images and vector XObjects (PDF replacements). So we also need to adjust the transformation matrix (/Matrix key) of our newly created XObject.

    So the code could look like this:

    PdfDocument pdf = new PdfDocument(new PdfReader(@"template.pdf"), new PdfWriter(@"out.pdf"));
    for(int i=1; i<=pdf.GetNumberOfPages(); ++i) {
        PdfDictionary pageDict  = pdf.GetPage(i).GetPdfObject();
        PdfDictionary resources = pageDict.GetAsDictionary(PdfName.Resources);
        PdfDictionary xObjects  = resources.GetAsDictionary(PdfName.XObject);
    
        IDictionary<PdfName, PdfStream> toReplace = new Dictionary<PdfName, PdfStream>();
        
        foreach (PdfName imgRef in xObjects.KeySet()) {
            PdfStream previousXobject = xObjects.GetAsStream(imgRef);
            PdfDocument imageReplacementDoc =
                new PdfDocument(new PdfReader(@"insert.pdf"));
            PdfXObject imageReplacement = imageReplacementDoc.GetPage(1).CopyAsFormXObject(pdf);
            toReplace[imgRef] = imageReplacement.GetPdfObject();
            adjustXObjectSize(imageReplacement);
            imageReplacementDoc.Close();
        }
    
        foreach (var x in toReplace) {
            xObjects.Put(x.Key, x.Value);
        }
    }
    pdf.Close();
    

    UPD: Implementation of adjustXObjectSize(thanks mkl):

    private void adjustXObjectSize(PdfXObject pageXObject) {
        float scaleXobject = 1 / Math.Max(pageXObject.GetWidth(), pageXObject.GetHeight());
        AffineTransform transform = new AffineTransform();
        transform.Scale(scaleXobject, scaleXobject);
        float[] matrix = new float[6];
        transform.GetMatrix(matrix);
        pageXObject.GetPdfObject().Put(PdfName.Matrix, new PdfArray(matrix));
    }
    

    And the visual result after running the above code on the samples I described would look like this:

    result