How to Add Watermarks to PDF using C#
As I mentioned in a previous post about adding your own converters using our fancy new plug-in architecture, we prioritise new functionality based on the number of customers requesting a particular feature. The feature I will be discussing today is one that has been on our radar for a long time, Watermarking.
We have previously provided some sample code to watermark documents using our Workflow Power Pack and although that solution is working well for many of our customers, it was only an interim solution that doesn’t come close to providing the functionality in our new watermarking engine, which is totally awesome
Note that at this moment we have only added native support for watermarking to the back-end conversion engine. Although it can be accessed using the Workflow Power Pack, we have not yet released a dedicated Workflow Action to control watermarking from SharePoint, which will be released in the next version.
Right, so back to the topic at hand. Listed below is an overview of our new Watermarking architecture as well as some sample code. More information and a full description of the object model can be found in the PDF Converter Services’s comprehensive User & Developer Guide. Note that you need version 3.5 or newer to make use of the new watermarking functionality.
The Muhimbi Document Conversion Service contains a very flexible system for applying watermarks to documents. Multiple watermarks can be applied to the same page and watermarks can be applied to page ranges or certain page types such as odd, even, portrait or landscape.
Watermarks are passed as part of the ConversionSettings object, a parameter of the Convert method. An overview of the watermarking related Web Services interface is provided below. For full details see the full User & Developer Guide.
Individual Element Types
As all individual elements / shapes inherit from the Element class, they largely share the same properties.
The supported element types are as follows.
-
Line: Represents a single line.
-
Rectangle: Represents a rectangle..
-
Ellipse: Represents an ellipse.
-
Rtf: Represents a piece of text encoded in RTF format.
-
Image: Represents an image. The following image types are supported: BMP, JPG, GIF, PNG, TIFF, WMF, EMF and EMF+.
-
Pdf: Represents an existing PDF file that is used as the watermark.
-
Text: Represents a text box that allows plain text to be specified with full control over horizontal and vertical alignment, font face and size as well as word wrapping. This field also allows field codes such as page number to be embedded (see below).
Embedding merge fields in the Text element
The Text element allows merge codes to be embedded such as the number of pages or the current date. This makes it very simple to use watermarks to automatically generate headers and footers on each page, while taking orientation and page interval (Odd / Even pages) into account.
The following merge fields are available for use:
-
{LONG_DATE}: The long representation of the current date.
-
{LONG_TIME}: The long representation of the current time.
-
{DATE}: The short representation of the current date.
-
{TIME}: The short representation of the current time.
-
{PAGE}: The number of the current page in the PDF file.
-
{NUMPAGES}: The total number of pages in the PDF file.
Date and time fields are formatted using the locale used by the account the Document Conversion Service is running under. More details about merge fields is available here.
Sample Code
As described previously, the PDF Conversion Service contains a powerful watermarking engine that can be used to add visible and invisible watermarks to pages as well as adding headers, footers and other recurring items.
The following C# example shows how to decorate a document with the following watermarks:
-
Add the word ‘Confidential’ in the background of the cover page.
-
Add page numbers in the right-hand side of the footer as well as a horizontal line to all even pages.
-
Add page numbers in the left-hand side of the footer as well as a horizontal line to all odd pages.
The sample code expects the path of the PDF file on the command line. If the path is omitted then the first MS-Word file found in the current directory will be used.
Follow the steps described below to create the sample watermarking application. Please note that this sample code is also available via the Windows Start Menu.
-
Create a new Visual Studio C# Console application named Watermarking.
-
Add a Service Reference to the following URL and specify ConversionService as the namespace
https://localhost:41734/Muhimbi.DocumentConverter.WebService/?wsdl
-
Paste the following code into Program.cs.
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.ServiceModel; using Watermarking.ConversionService; namespace Watermarking { class Program { // ** The URL where the Web Service is located. Amend host name if needed. static string SERVICE_URL = "https://localhost:41734/Muhimbi.DocumentConverter.WebService/"; static void Main(string[] args) { DocumentConverterServiceClient client = null; try { // ** Determine the source file and read it into a byte array. string sourceFileName = null; if (args.Length == 0) { string[] sourceFiles = Directory.GetFiles( Directory.GetCurrentDirectory(), "*.doc"); if (sourceFiles.Length > 0) sourceFileName = sourceFiles[0]; else { Console.WriteLine("Please specify a document to convert and watermark."); Console.ReadKey(); return; } } else sourceFileName = args[0]; byte[] sourceFile = File.ReadAllBytes(sourceFileName); // ** Open the service and configure the bindings client = OpenService(SERVICE_URL); //** Set the absolute minimum open options OpenOptions openOptions = new OpenOptions(); openOptions.OriginalFileName = Path.GetFileName(sourceFileName); openOptions.FileExtension = Path.GetExtension(sourceFileName); // ** Set the absolute minimum conversion settings. ConversionSettings conversionSettings = new ConversionSettings(); conversionSettings.Fidelity = ConversionFidelities.Full; conversionSettings.Quality = ConversionQuality.OptimizeForPrint; // ** Get the list of watermarks to apply. conversionSettings.Watermarks = CreateWatermarks(); // ** Carry out the conversion. Console.WriteLine("Converting file " + sourceFileName); byte[] convFile = client.Convert(sourceFile, openOptions, conversionSettings); // ** Write the converted file back to the file system with a PDF extension. string destinationFileName = Path.GetDirectoryName(sourceFileName) + @"\" + Path.GetFileNameWithoutExtension(sourceFileName) + "." + conversionSettings.Format; using (FileStream fs = File.Create(destinationFileName)) { fs.Write(convFile, 0, convFile.Length); fs.Close(); } Console.WriteLine("File converted to " + destinationFileName); // ** Open the generated PDF file in a PDF Reader Process.Start(destinationFileName); } catch (FaultException<WebServiceFaultException> ex) { Console.WriteLine("FaultException occurred: ExceptionType: " + ex.Detail.ExceptionType.ToString()); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } finally { CloseService(client); } Console.ReadKey(); } /// <summary> /// Create the watermarks. /// </summary> /// <returns>An array of watermarks to apply</returns> public static Watermark[] CreateWatermarks() { List<Watermark> watermarks = new List<Watermark>(); // ** Specify the default settings for properties Defaults wmDefaults = new Defaults(); wmDefaults.FillColor = "#000000"; wmDefaults.LineColor = "#000000"; wmDefaults.FontFamilyName = "Arial"; wmDefaults.FontSize = "10"; // **************** 'Confidential' Text *************** // ** 'Confidential' watermark for front page Watermark confidential = new Watermark(); confidential.Defaults = wmDefaults; confidential.StartPage = 1; confidential.EndPage = 1; confidential.Rotation = "-45"; confidential.Width = "500"; confidential.Height = "500"; confidential.HPosition = HPosition.Center; confidential.VPosition = VPosition.Middle; confidential.ZOrder = -1; // ** Create a new Text element that goes inside the watermark Text cfText = new Text(); cfText.Content = "Confidential"; cfText.FontSize = "40"; cfText.FontStyle = FontStyle.Bold | FontStyle.Italic; cfText.Width = "500"; cfText.Height = "500"; cfText.Transparency = "0.10"; // ** And add it to the list of watermark elements. confidential.Elements = new Element[] { cfText }; // ** And add the watermark to the list of watermarks watermarks.Add(confidential); // **************** Watermark for Odd pages *************** Watermark oddPages = new Watermark(); oddPages.Defaults = wmDefaults; oddPages.StartPage = 3; oddPages.PageInterval = 2; oddPages.Width = "600"; oddPages.Height = "50"; oddPages.HPosition = HPosition.Right; oddPages.VPosition = VPosition.Bottom; // ** Add a horizontal line Line line = new Line(); line.X = "1"; line.Y = "1"; line.EndX = "600"; line.EndY = "1"; line.Width = "5"; // ** Add a page counter Text oddText = new Text(); oddText.Content = "Page: {PAGE} of {NUMPAGES}"; oddText.Width = "100"; oddText.Height = "20"; oddText.X = "475"; oddText.Y = "15"; oddText.LineWidth = "-1"; oddText.FontStyle = FontStyle.Regular; oddText.HAlign = HAlign.Right; // ** And add it to the list of watermark elements oddPages.Elements = new Element[] { line, oddText }; // ** And add the watermark to the list of watermarks watermarks.Add(oddPages); // **************** Watermark for Even pages *************** Watermark evenPages = new Watermark(); evenPages.Defaults = wmDefaults; evenPages.StartPage = 2; evenPages.PageInterval = 2; evenPages.Width = "600"; evenPages.Height = "50"; evenPages.HPosition = HPosition.Left; evenPages.VPosition = VPosition.Bottom; // ** No need to create an additional line,re-use the previous one // ** Add a page counter Text evenText = new Text(); evenText.Content = "Page: {PAGE} of {NUMPAGES}"; evenText.Width = "100"; evenText.Height = "20"; evenText.X = "25"; evenText.Y = "15"; evenText.LineWidth = "-1"; evenText.FontStyle = FontStyle.Regular; evenText.HAlign = HAlign.Left; // ** And add it to the list of watermark elements evenPages.Elements = new Element[] { line, evenText }; // ** And add the watermark to the list of watermarks watermarks.Add(evenPages); return watermarks.ToArray(); } /// <summary> /// Configure the Bindings, endpoints and open the service using the specified address. /// </summary> /// <returns>An instance of the Web Service.</returns> public static DocumentConverterServiceClient OpenService(string address) { DocumentConverterServiceClient client = null; try { BasicHttpBinding binding = new BasicHttpBinding(); // ** Use standard Windows Security. binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly; binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows; // ** Increase the client Timeout to deal with (very) long running requests. binding.SendTimeout = TimeSpan.FromMinutes(30); binding.ReceiveTimeout = TimeSpan.FromMinutes(30); // ** Set the maximum document size to 50MB binding.MaxReceivedMessageSize = 50 * 1024 * 1024; binding.ReaderQuotas.MaxArrayLength = 50 * 1024 * 1024; binding.ReaderQuotas.MaxStringContentLength = 50 * 1024 * 1024; // ** Specify an identity (any identity) in order to get it past .net3.5 sp1 EndpointIdentity epi = EndpointIdentity.CreateUpnIdentity("unknown"); EndpointAddress epa = new EndpointAddress(new Uri(address), epi); client = new DocumentConverterServiceClient(binding, epa); client.Open(); return client; } catch (Exception) { CloseService(client); throw; } } /// <summary> /// Check if the client is open and then close it. /// </summary> /// <param name="client">The client to close</param> public static void CloseService(DocumentConverterServiceClient client) { if (client != null && client.State == CommunicationState.Opened) client.Close(); } } }
-
Make sure the output folder contains an MS-Word file.
-
Compile and execute the application.
As all this functionality is exposed via a Web Services interface, it works equally well from Java and other web services enabled environments.
This code is merely an example of what is possible, feel free to adapt it to you own needs, add more complex watermarks or your company logo to each page. The possibilities are endless.
Clavin is a Microsoft Business Applications MVP who supports 1,000+ high-level enterprise customers with challenges related to PDF conversion in combination with SharePoint on-premises Office 365, Azure, Nintex, K2, and Power Platform mostly no-code solutions.