Monday, April 19, 2010

Insert a Watermark with iTextSharp

iTextSharp is a very good open source library to manage file in pdf format. You can find many examples on how you can use this powerful library inside your application in this tutorial, but in this post I want to focus on a way to create a watermark and insert it inside in an existing pdf document.

This need come up from a project for my current customer who need to create a pdf document from a office file (Word, Excel and PowerPoint format) with a watermark reporting user name (user claiming the copy of the document). So, the approach I used to accomplish the requirement foresee the following steps:

  1. First, I create a bitmap with the text I want to render in the watermark;
  2. then, I insert the bitmap created inside my pdf document using iTextSharp library.

Bitmap creation is very easy using dot Net Framework classes. So, I developed the Watermark class with default constructor requiring the text to render:

   1:  public Watermark(string watermarkText)
   2:          {            
   3:              Text = watermarkText;
   4:              TextFont = new Font("Arial", 25, FontStyle.Bold);
   5:              Inizialize();
   6:          }

TextFont is an auto property that consumer can modify to change watermark font. In the constructor I also initialize the Watermark instance setting the resolution DPI, the scale factor and I calculate the bitmap size (width and height in pixel) using the following method:

   1:  private const int DEFAULT_SCALE_RATIO = 24;
   2:   
   3:  private void Inizialize()
   4:  {
   5:      ScaleFactor = DEFAULT_SCALE_RATIO;
   6:      Position = WatermarkPosition.Central;
   7:      BackGroundColor = Color.Transparent;
   8:      BrushColor = Color.LightGray;
   9:   
  10:      using (var bitmap = new Bitmap(1, 1))
  11:      {
  12:          bitmap.SetResolution(DPI, DPI);
  13:          using(var g = Graphics.FromImage(bitmap))
  14:          {
  15:              Height = Convert.ToInt32(g.MeasureString(Text, TextFont).Height);
  16:              Width = Convert.ToInt32(g.MeasureString(Text, TextFont).Width);
  17:          }
  18:      }
  19:  }

It is necessary to scale the bitmap because you could have resolution problems when you use it with iTextSharp library (see paragraph Impact on the resolution in iTextSharp tutorial).

To get the desired bitmap I created the following public methods:

  • one to get the image with original size
  • one to get scaled image
   1:  public Image GetImage()
   2:  {    
   3:      var bitmap = new Bitmap(Width, Height);
   4:      var image = CreateImage(bitmap, TextFont);
   5:      return image;
   6:   
   7:  }
   8:   
   9:  public Image GetScaledImage()
  10:  {
  11:      var bitmap = new Bitmap(ScaledWidth, ScaledHeight);
  12:      var image = CreateImage(bitmap, ScaledTextFont);
  13:      return image;
  14:  }

where ScaledWidth and ScaledHeight are:

   1:  public int ScaledWidth
   2:  {
   3:      get { return Width *100 / ScaleFactor; }
   4:  }
   5:   
   6:  public int ScaledHeight
   7:  {
   8:      get { return Height *100 / ScaleFactor; }
   9:  }

The last thing is the CreateImage method which really makes the work:

   1:  private const float DEFAULT_IMAGE_DPI = 72f;
   2:   
   3:  private Image CreateImage(Bitmap bitmap, Font font)
   4:  {
   5:      bitmap.SetResolution(DEFAULT_IMAGE_DPI, DEFAULT_IMAGE_DPI);
   6:      using(var g = Graphics.FromImage(bitmap))
   7:      {
   8:          SetGraphicsProperty(g);
   9:   
  10:          var sizef = g.MeasureString(Text, font);
  11:          g.Clear(BackGroundColor);
  12:   
  13:          var text = Text;
  14:          var width = bitmap.Width;
  15:   
  16:          if (Repeater)
  17:          {
  18:              while (width%sizef.Width > 0)
  19:              {
  20:                  text += " - " + Text;
  21:                  width -= Convert.ToInt32(sizef.Width);
  22:              }
  23:          }
  24:   
  25:          g.DrawString(text, font, new SolidBrush(BrushColor), 0, 0,
  26:                       new StringFormat(StringFormatFlags.FitBlackBox));
  27:          g.Flush();
  28:      }
  29:      return bitmap;
  30:  }

Repeater is an auto-property to create a bitmap with text repeated many times necessary to fill all the bitmap width.This is required, for example, when I want a watermark positioned along all document margins.

BackgroundColor and BrushColor are two auto-property which permit to customize watermark look & feel.

SetGraphicsProperty method just initializes some image properties (see here for more details):

private static void SetGraphicsProperty(Graphics g)
{
    // Set the System.Drawing.Graphics object property SmoothingMode to HighQuality 
    g.SmoothingMode = SmoothingMode.AntiAlias;
    // Set the System.Drawing.Graphics object property CompositingQuality to HighQuality
    g.CompositingQuality = CompositingQuality.HighQuality;
    // Set the System.Drawing.Graphics object property InterpolationMode to High
    g.InterpolationMode = InterpolationMode.High;
 
    g.PixelOffsetMode = PixelOffsetMode.HighQuality;
 
    g.TextRenderingHint = TextRenderingHint.AntiAlias;
}

Now that we have our watermark, we can work on PDF document where we want insert it. To accomplish this I create the PDFDocument class:

   1:  using iTextSharp.text;
   2:  using iTextSharp.text.pdf;
   3:   
   4:  public class PDFDocument
   5:  {
   6:   
   7:      private PDFDocument(Guid Id)
   8:      {
   9:          ID = Id;
  10:          Watermarks = new List<IWatermark>();
  11:      }
  12:   
  13:      public PDFDocument(Guid id, byte[] binaryFile, string fileName) : this(id)
  14:      {
  15:          binary = binaryFile;
  16:          var pdfReader = new PdfReader(binaryFile);
  17:          if(pdfReader.NumberOfPages == 0 ) 
  18:              throw new Exception("Document does not contain any page!");
  19:          var pageSize = pdfReader.GetPageSizeWithRotation(1);
  20:          PageWidth = Convert.ToInt32(pageSize.Width);
  21:          PageHeight = Convert.ToInt32(pageSize.Height);
  22:          PageRotationInDegree = pageSize.Rotation;
  23:          FileName = fileName;
  24:      }
  25:      
  26:  ...
  27:   
  28:  }

The constructor of my entity require the id, the binary data and the file name.
Note that here I’m using iTextSharp library and, in particular, the PDFReader class in iTextSharp.text.pdf namespace; in this way I can set some useful properties about document in question, such as page width, height and rotation. I also define an auto property to add desired watermarks to my pdf document:

    public IList<Watermark> Watermarks
    {
        get;
        set;
    }

And now the main point of the class. With GetDocumentWithWatermark method, I process the pdf document to insert the real watermark picture:

   1:  public byte[] GetDocumentWithWatermark(bool protect)
   2:  {
   3:      using (var outputStream = new MemoryStream())
   4:      {
   5:      var pdfReader = new PdfReader(binary);
   6:      var pdfStamper = new PdfStamper(pdfReader, outputStream);
   7:   
   8:      foreach (var watermark in Watermarks)
   9:      {
  10:          using (var bitmap = watermark.GetScaledImage())
  11:          {
  12:          var image = Image.GetInstance(bitmap, BaseColor.WHITE );
  13:          
  14:          var numberOfPages = pdfStamper.Reader.NumberOfPages;
  15:   
  16:          for (var i = 1; i <= numberOfPages; i++)
  17:          {
  18:              var pageSize = pdfStamper.Reader.GetPageSizeWithRotation(i);
  19:              PdfContentByte waterMarkContent = null;
  20:              switch (watermark.Position)
  21:              {
  22:                  case WatermarkPosition.Right:
  23:                      image.RotationDegrees = 90;
  24:                      image.Rotate();
  25:                      image.ScalePercent(watermark.ScaleFactor);
  26:                      image.SetAbsolutePosition(pageSize.Width - 
  27:                                                  image.ScaledWidth, 0);
  28:                      waterMarkContent = pdfStamper.GetOverContent(i);
  29:                      break;
  30:                  case WatermarkPosition.Central:
  31:                      image.RotationDegrees = WATERMARK_ANGLE;
  32:                      image.Rotate();
  33:                      image.ScalePercent(watermark.ScaleFactor);
  34:                      image.SetAbsolutePosition(pageSize.Width/2 - 
  35:                                                  image.ScaledWidth/2,
  36:                                                pageSize.Height/2 - 
  37:                                                  image.ScaledHeight/2);
  38:                      waterMarkContent = pdfStamper.GetUnderContent(i);
  39:                      break;
  40:                  case WatermarkPosition.Left:
  41:                      image.RotationDegrees = 90;
  42:                      image.Rotate();
  43:                      image.ScalePercent(watermark.ScaleFactor);
  44:                      image.SetAbsolutePosition(0, 0);
  45:                      waterMarkContent = pdfStamper.GetOverContent(i);
  46:                      break;
  47:              }
  48:   
  49:              if (waterMarkContent != null) 
  50:                  waterMarkContent.AddImage(image, true);
  51:          }
  52:          }
  53:      }
  54:      pdfStamper.Close();
  55:      pdfReader.Close();
  56:      return outputStream.ToArray();
  57:      }

 

as you can see I use iTextSharp class such as PdfReader and PdfStamper to manipulate the document and then, in the switch code segment, I create and position the image according to I want a central, left or right watermark. Note that I call two different method of pdfStamper instance:

  • GetUnderContent method: allow to put my watermark a level lower the current document layout. This is the best solution when I set central watermark and I want that document text and image are over the watermark
  • GetOverContent method: allow to set watermark upper all document content. Because left and right watermark stay on document margin, could be very useful put it over all content especially when you are manipulating pdf file coming from a PowerPoint document conversion which can have non-transparent background image.

Note that I have repeated the operation for all the page inside the pdf document because page layout could be change (horizontal or vertical page layout).

References:

  1. iTextSharp library
  2. iTextSharp tutorial
  3. http://www.codeproject.com/Articles/71961/Creating-a-Simple-Sheet-Designer-in-Csharp.aspx
kick it on DotNetKicks.com
Save to delicious 0 saves

No comments:

Post a Comment