DCNYAM
DCNYAM

Reputation: 12126

PDF Add Text and Flatten

I'm developing a web application that displays PDFs and allows users to order copies of the documents. We want to add text, such as "unpaid" or "sample", on the fly when the PDF is displayed. I have accomplished this using itextsharp. However, the page images are easily separated from the watermark text and extracted using a variety of freeware programs.

How can I add the watermark to the pages in the PDF, but flatten the page images and watermark together so that the watermark becomes part of the pdf page image, thereby preventing the watermark from being removed (unless the person wants to use photoshop)?

Upvotes: 1

Views: 1600

Answers (2)

Chris Haas
Chris Haas

Reputation: 55427

If I were you I would go down a different path. Using iTextSharp (or another library) extract each page of a given document to a folder. Then use some program (Ghostscript, Photoshop, maybe GIMP) that you can batch and convert each page to an image. Then write your overlay text onto the images. Finally use iTextSharp to combine all of the images in each folder back into a PDF.

I know this sounds like a pain but you should only have to do this once per document I assume.

If you don't want to go down this route, let me get you going on what you need to do to extract images. Much of the code below comes from this post. At the end of the code I'm saving the images to the desktop. Since you've got raw bytes so you could also easily pump those into a System.Drawing.Image object and write them back into a new PdfWriter object which is sounds like you are familiar with. Below is a full working WinForms app targetting iTextSharp 5.1.1.0

Option Explicit On
Option Strict On

Imports iTextSharp.text
Imports iTextSharp.text.pdf
Imports System.IO
Imports System.Runtime.InteropServices

Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ''//File to process
        Dim InputFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "SampleImage.pdf")

        ''//Bind a reader to our PDF
        Dim R As New PdfReader(InputFile)

        ''//Setup some variable to use below
        Dim bytes() As Byte
        Dim obj As PdfObject
        Dim pd As PdfDictionary
        Dim filter, width, height, bpp As String
        Dim pixelFormat As System.Drawing.Imaging.PixelFormat
        Dim bmp As System.Drawing.Bitmap
        Dim bmd As System.Drawing.Imaging.BitmapData

        ''//Loop through all of the references in the file
        Dim xo = R.XrefSize
        For I = 0 To xo - 1
            ''//Get the object
            obj = R.GetPdfObject(I)
            ''//Make sure we have something and that it is a stream
            If (obj IsNot Nothing) AndAlso obj.IsStream() Then
                ''//Case it to a dictionary object
                pd = DirectCast(obj, PdfDictionary)
                ''//See if it has a subtype property that is set to /IMAGE
                If pd.Contains(PdfName.SUBTYPE) AndAlso pd.Get(PdfName.SUBTYPE).ToString() = PdfName.IMAGE.ToString() Then
                    ''//Grab various properties of the image
                    filter = pd.Get(PdfName.FILTER).ToString()
                    width = pd.Get(PdfName.WIDTH).ToString()
                    height = pd.Get(PdfName.HEIGHT).ToString()
                    bpp = pd.Get(PdfName.BITSPERCOMPONENT).ToString()

                    ''//Grab the raw bytes of the image
                    bytes = PdfReader.GetStreamBytesRaw(DirectCast(obj, PRStream))

                    ''//Images can be encoded in various ways. /DCTDECODE is the simplest because its essentially JPEG and can be treated as such.
                    ''//If your PDFs contain the other types you will need to figure out how to handle those on your own
                    Select Case filter
                        Case PdfName.ASCII85DECODE.ToString()
                            Throw New NotImplementedException("Decoding this filter has not been implemented")
                        Case PdfName.ASCIIHEXDECODE.ToString()
                            Throw New NotImplementedException("Decoding this filter has not been implemented")
                        Case PdfName.FLATEDECODE.ToString()
                            ''//This code from https://stackoverflow.com/questions/802269/itextsharp-extract-images/1220959#1220959
                            bytes = pdf.PdfReader.FlateDecode(bytes, True)
                            Select Case Integer.Parse(bpp)
                                Case 1
                                    pixelFormat = Drawing.Imaging.PixelFormat.Format1bppIndexed
                                Case 24
                                    pixelFormat = Drawing.Imaging.PixelFormat.Format24bppRgb
                                Case Else
                                    Throw New Exception("Unknown pixel format " + bpp)
                            End Select
                            bmp = New System.Drawing.Bitmap(Int32.Parse(width), Int32.Parse(height), pixelFormat)
                            bmd = bmp.LockBits(New System.Drawing.Rectangle(0, 0, Int32.Parse(width), Int32.Parse(height)), System.Drawing.Imaging.ImageLockMode.WriteOnly, pixelFormat)
                            Marshal.Copy(bytes, 0, bmd.Scan0, bytes.Length)
                            bmp.UnlockBits(bmd)
                            Using ms As New MemoryStream
                                bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg)
                                bytes = ms.GetBuffer()
                            End Using
                        Case PdfName.LZWDECODE.ToString()
                            Throw New NotImplementedException("Decoding this filter has not been implemented")
                        Case PdfName.RUNLENGTHDECODE.ToString()
                            Throw New NotImplementedException("Decoding this filter has not been implemented")
                        Case PdfName.DCTDECODE.ToString()
                            ''//Bytes should be raw JPEG so they should not need to be decoded, hopefully
                        Case PdfName.CCITTFAXDECODE.ToString()
                            Throw New NotImplementedException("Decoding this filter has not been implemented")
                        Case PdfName.JBIG2DECODE.ToString()
                            Throw New NotImplementedException("Decoding this filter has not been implemented")
                        Case PdfName.JPXDECODE.ToString()
                            Throw New NotImplementedException("Decoding this filter has not been implemented")
                        Case Else
                            Throw New ApplicationException("Unknown filter found : " & filter)
                    End Select

                    ''//At this points the byte array should contain a valid JPEG byte data, write to disk
                    My.Computer.FileSystem.WriteAllBytes(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), I & ".jpg"), bytes, False)
                End If
            End If

        Next

        Me.Close()
    End Sub
End Class

Upvotes: 2

Marc B
Marc B

Reputation: 360662

The whole page would have to be rendered as an image. Otherwise you're got "text objects" (the individual words/letters of the text), and the watermark object (the overlay image), which will always be distinct/separate parts of the page.

Upvotes: 1

Related Questions