PDF Merger with iTextSharp
PDF Merger 1.0 Technical Usage Guide
Before starting go-through article, you should have knowledge about following:
- Prerequisite
- Thread
As per Microsoft site “Threading is the ability to create applications that use more than one thread of execution. For example, a process can have a user interface thread that manages interactions with the user and worker threads that perform other tasks while the user interface thread waits for user input”.
For more details:
http://msdn.microsoft.com/en-us/library/aa645740%28v=vs.71%29.aspx
- IsynchronizeInvoke
As per Microsoft site “The ISynchronizeInvoke interface provides synchronous and asynchronous communication between objects about the occurrence of an event. Objects that implement this interface can receive notification that an event has occurred, and they can respond to queries about the event. In this way, clients can ensure that one request has been processed before they submit a subsequent request that depends on completion of the first”.
For more details:
http://msdn.microsoft.com/en-us/library/system.componentmodel.isynchronizeinvoke.aspx
- Delegate
As per Microsoft site “Delegate is a type that references a method. Once a delegate is assigned a method, it behaves exactly like that method. The delegate method can be used like any other method, with parameters and a return value”.
For more details:
http://msdn.microsoft.com/en-us/library/aa288459%28v=vs.71%29.aspx
- Variable Declaration
//this is the notify file merging process at specified interval private NotifyProgress m_clsNotifyDelegate; //Declaration of thread private Thread m_clsThread; //Provides a way to synchronously or asynchronously execute a delegate private ISynchronizeInvoke m_clsSynchronizingObject; //this is the definition of the progress delegate - it defines the "signature" of the routine... //NotifyProgress has four arguments TotalFiles: total number of files to merge ProcessFileIndex: currently merging file index TotalPages: total number of pages of all files PageIndex: currently merging file page index. public delegate void NotifyProgress(int TotalFiles, int ProcessFileIndex, int TotalPages, int PageIndex); |
- Properties
//Gets or Sets the Destination File Path. Where save generated file. public string DestinationFile { get;set; } //Processing or current File Index,File Name and Total Pages and Process Page Index. public int ProcessFileIndex { get;set; } public string ProcessFileName { get;set; } public int ProcessFilePages { get;set; } public int ProcessFilePageIndex { get;set; } //Total Files and Total Pages to be merged in a single file (pdf). public int TotalFiles { get;set; } public int TotalPages { get;set; } public int PageIndex { get;set; } |
- Constructor
You can create object of PDFMerger with or without default value like Synchronize Object and Notify Progress parameters.
//This class object will requires two parameter “synchronizing object” be passed to it – this tells the class what context “notify delegates” should run in
public PDFManager(ISynchronizeInvoke SynchronizingObject, NotifyProgress NotifyDelegate) { m_clsSynchronizingObject = SynchronizingObject; m_clsNotifyDelegate = NotifyDelegate; } |
- Public Methods
- AddFile
This method will add list of files into file list. File list contain all those files to be merged into single.
public void AddFile(string pathnname) { fileList.Add(pathnname); } |
- Execute
Execute method will combine all files into single (pdf) file.
public void Execute() { //Method will execute in background thread, it will continuous call “MeregDocs” method to update or notify to the users screen. m_clsThread = new System.Threading.Thread(MergeDocs); m_clsThread.Name = "PDF Mereger Background Thread"; m_clsThread.IsBackground = true; m_clsThread.Start(); } |
- Private Methods
- MergeDocs
This method is a heart of the entire merging process. It will merge all the files into single and save generated file at destination location. This method is very explanatory so no need write more for that.
private void MergeDocs() { string value = ""; setting = new SettingManager(Path.Combine(Application.StartupPath, "settings.ini")); //------------------------------------------------------------------------------------ //Step 1: Create a Docuement-Object //------------------------------------------------------------------------------------ Document document = new Document(); try { //------------------------------------------------------------------------------------ //Step 2: we create a writer that listens to the document //------------------------------------------------------------------------------------ PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(destinationfile, FileMode.Create)); //------------------------------------------------------------------------------------ //Step 3: Set Password Protection if you are set for it //------------------------------------------------------------------------------------ SetPasswordProtection(writer); //------------------------------------------------------------------------------------ //Step 4: Open the document //------------------------------------------------------------------------------------ document.Open(); PdfContentByte cb = writer.DirectContent; PdfImportedPage page; PdfReader reader; int rotation = 0; ProcessFileIndex = 0; TotalFiles = fileList.Count; TotalPages = 0; PageIndex = 0; foreach (string filename in fileList) { reader = new PdfReader(filename); //Gets the number of pages to process TotalPages += reader.NumberOfPages; } //Loops for each file that has been listed foreach (string filename in fileList) { //Create a reader for the document reader = new PdfReader(filename); ProcessFileIndex++; ProcessFileName = filename; //Gets the number of pages to process ProcessFilePages = reader.NumberOfPages; ProcessFilePageIndex = 0; while (ProcessFilePageIndex < ProcessFilePages) { ProcessFilePageIndex++; PageIndex++; NotifyUI(TotalFiles, ProcessFileIndex, TotalPages, PageIndex); Thread.Sleep(1); document.SetPageSize(reader.GetPageSizeWithRotation(1)); document.NewPage(); //Insert to Destination on the first page if (ProcessFilePageIndex == 1) { Chunk fileRef = new Chunk(" "); fileRef.SetLocalDestination(filename); document.Add(fileRef); } page = writer.GetImportedPage(reader, ProcessFilePageIndex); rotation = reader.GetPageRotation(ProcessFilePageIndex); if (rotation == 90 || rotation == 270) cb.AddTemplate(page, 0, -1f, 1f, 0, 0, reader.GetPageSizeWithRotation(ProcessFilePageIndex).Height); else cb.AddTemplate(page, 1f, 0, 0, 1f, 0, 0); //------------------------------------------------------------------------------------ //Step 4: Set Page Number and Formatting if you are set for it //------------------------------------------------------------------------------------ value = setting.Read("Page Number and Formatting", "AollowPageFormatting"); if (!string.IsNullOrEmpty(value) && Convert.ToBoolean(value) == true) { // we tell the ContentByte we're ready to draw text cb.BeginText(); // we draw some text on a perticular position BaseFont bf = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, false); cb.SetFontAndSize(bf, 12); value = setting.Read("Page Number and Formatting", "PageText"); string PageNumberText = ""; if (!string.IsNullOrEmpty(value)) PageNumberText = value; string isPageNumber = setting.Read("Page Number and Formatting", "PageNumber"); string isTotalPageNumber = setting.Read("Page Number and Formatting", "TotalPage"); if (!string.IsNullOrEmpty(isPageNumber) && Convert.ToBoolean(isPageNumber) == true && !string.IsNullOrEmpty(isTotalPageNumber) && Convert.ToBoolean(isTotalPageNumber) == true) { PageNumberText = string.Format("{0} {1} of {2}", PageNumberText, PageIndex, TotalPages); } else if (!string.IsNullOrEmpty(isPageNumber) && Convert.ToBoolean(isPageNumber) == true) { PageNumberText = string.Format("{0} {1}", PageNumberText, PageIndex); } else if (!string.IsNullOrEmpty(isTotalPageNumber) && Convert.ToBoolean(isTotalPageNumber) == true) { PageNumberText = string.Format("{0} {1}", PageNumberText, TotalPages); } Coordinates Coordinates = GetPageAlignment(PageNumberText, document); cb.SetTextMatrix(Coordinates.X, Coordinates.Y); cb.ShowText(PageNumberText); // we tell the contentByte, we've finished drawing text cb.EndText(); } } } } catch (Exception e) { MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { //------------------------------------------------------------------------------------ //Step 5: Close the merged pdf document //------------------------------------------------------------------------------------ document.Close(); //------------------------------------------------------------------------------------ //Step 6: Applying watermark Image of Text on PDF //------------------------------------------------------------------------------------ bool isWatermarkEnabled = Convert.ToBoolean(setting.Read("Watermark Image or Write Text", "IsWatermarkEnabled")); if (isWatermarkEnabled) { bool isImagePath = Convert.ToBoolean(setting.Read("Watermark Image or Write Text", "IsImagePath")); string filePath = setting.Read("Merege PDF Location", "PDFLocation"); if (string.IsNullOrEmpty(filePath) || !Directory.Exists(filePath)) { string newFilePath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "MergedFiles"); setting.Write("Merege PDF Location", "PDFLocation", newFilePath); MessageBox.Show(string.Format("Directory does not exists at {0}\n Default path is {1}", filePath, newFilePath), "Directory Not Found", MessageBoxButtons.OK); filePath = newFilePath; } //Step 6.1: Applying watermark Image if image option was selected filePath = Path.Combine(filePath, DateTime.Now.Ticks + ".pdf"); if (isImagePath) { string imagePath = setting.Read("Watermark Image or Write Text", "ImagePath"); if (imagePath != null && imagePath != "") { if (File.Exists(imagePath)) { AddWatermarkImage(DestinationFile, filePath, imagePath); File.Delete(DestinationFile); File.Copy(filePath, DestinationFile); File.Delete(filePath); } else { MessageBox.Show(string.Format("File could not be found at {0}", imagePath), "File Not Found", MessageBoxButtons.OK); } } } else { //Step 6.2: Applying watermark text if text option was selected string watermarkText = setting.Read("Watermark Image or Write Text", "WatermarkText"); string watermarkFont = setting.Read("Watermark Image or Write Text", "WatermarkFont"); string watermarkColor = setting.Read("Watermark Image or Write Text", "WatermarkColor"); iTextSharp.text.pdf.BaseFont font = ConvertStringToFont(watermarkFont); BaseColor color = ConvertStringToColor(watermarkColor); AddWatermarkText(sourceFile: DestinationFile, outputFile: filePath, watermarkText: watermarkText, watermarkFont: font, watermarkFontColor: color); File.Delete(DestinationFile); File.Copy(filePath, DestinationFile); File.Delete(filePath); } } //Step 7: Destroy PDF Merger object from the memory and release all the objects. m_clsNotifyDelegate = null; m_clsThread = null; m_clsSynchronizingObject = null; fileList.Clear(); } } |
- SetPasswordProtection
This method will protect your document with user name and password, without user name and password nobody can open it.
private PdfWriter SetPasswordProtection(PdfWriter writer) { try { string value = setting.Read("File Protection", "AllowFileProtection"); if (!string.IsNullOrEmpty(value) && Convert.ToBoolean(value) == true) { string password = setting.Read("File Protection", "Password"); string ownerPassword = "imdadhusen"; if (!string.IsNullOrEmpty(password)) { int Permission = 0; bool Strength = false; value = setting.Read("File Protection", "AllowCopy"); if (!string.IsNullOrEmpty(value)) Permission = 16; value = setting.Read("File Protection", "AllowPrinting"); if (!string.IsNullOrEmpty(value)) Permission += 2052; value = setting.Read("File Protection", "EncryptionStrength"); if (!string.IsNullOrEmpty(value) && value == "strength128bits") Strength = true; writer.SetEncryption(Strength, password, ownerPassword, Permission); } } } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } return writer; } |
- ConvertStringToFont
This method will set Watermark Text font as per selected by you.
private BaseFont ConvertStringToFont(string fontName) { iTextSharp.text.pdf.BaseFont font = null; try { if (string.IsNullOrEmpty(fontName) == false) { switch (fontName.ToUpper()) { case "COURIER": case "HELVETICA": font = BaseFont.CreateFont(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(fontName), BaseFont.CP1252, BaseFont.NOT_EMBEDDED); break; case "TIMES": font = BaseFont.CreateFont(BaseFont.TIMES_BOLD, BaseFont.CP1252, BaseFont.NOT_EMBEDDED); break; } } } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } return font; } |
- ConvertStringToColor
This method will set font color of your Watermark Text
private BaseColor ConvertStringToColor(string colorName)
BaseColor color = null; if (string.IsNullOrEmpty(colorName.ToLower()) == false) { Color c = Color.FromName(colorName.ToLower()); color = new BaseColor(c.R, c.G, c.B); } return color; } |
- NotifyUI
This is the method that determines how to interact with the calling thread, whether by delegate or event. It will update status of merging process on the users screen.
private void NotifyUI(int TotalFiles, int ProcessFileIndex, int TotalPages, int PageIndex) { try { object[] args = { TotalFiles, ProcessFileIndex, TotalPages, PageIndex }; //call the delegate, specifying the context in which to run... m_clsSynchronizingObject.Invoke(m_clsNotifyDelegate, args); } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } |
- GetPageAlignment
This method will display Custom Page Number at specific position or alignment (top, middle, bottom) or (left, center and right)
private Coordinates GetPageAlignment(string PageNumberText, Document document) { Coordinates Coordinates = new PDFLibrary.Coordinates(); try { frmSettings frmsetting = new frmSettings(); PageNumberSize PageNumberSize = frmsetting.GetPageNumberSize(PageNumberText); float TMargin = document.TopMargin; float RMargin = document.RightMargin; float BMargin = document.BottomMargin; float LMargin = document.LeftMargin; setting = new SettingManager(Path.Combine(Application.StartupPath, "settings.ini")); iTextSharp.text.Rectangle pageSize = document.PageSize; string value = setting.Read("Page Number and Formatting", "VerticalAlignment"); switch (value) { case "top": Coordinates.Y = pageSize.Height - (PageNumberSize.Height + TMargin); break; case "middle": Coordinates.Y = (pageSize.Height - (PageNumberSize.Height + TMargin)) / 2; break; case "bottom": Coordinates.Y = BMargin - PageNumberSize.Height; break; } value = setting.Read("Page Number and Formatting", "HorizontalAlignment"); switch (value) { case "left": Coordinates.X = LMargin; break; case "center": Coordinates.X = (pageSize.Width - (PageNumberSize.Width + RMargin)) / 2; break; case "right": Coordinates.X = pageSize.Width - (PageNumberSize.Width + RMargin); break; } } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } return Coordinates; } |
- AddWatermarkImage
Using this method you may add water mark Image on every pages. So it will protect your data from theft.
private void AddWatermarkImage(string sourceFile, string outputFile, string watermarkImage) { try { using (Stream inputPdfStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read)) using (Stream inputImageStream = new FileStream(watermarkImage, FileMode.Open, FileAccess.Read, FileShare.Read)) using (Stream outputPdfStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write, FileShare.None)) { PdfReader reader; string userPassword = "imdadhusen"; //Imdadhusen is a master Password but you can unlock with User Password also. string ownerPassword = "imdadhusen"; try { reader = new PdfReader(sourceFile); } catch (BadPasswordException) { System.Text.Encoding enc = System.Text.Encoding.ASCII; Byte[] myByteArray = enc.GetBytes(userPassword); reader = new PdfReader(sourceFile, myByteArray); } var stamper = new PdfStamper(reader, outputPdfStream); string value = setting.Read("File Protection", "AllowFileProtection"); if (!string.IsNullOrEmpty(value) && Convert.ToBoolean(value) == true) { userPassword = setting.Read("File Protection", "Password"); if (!string.IsNullOrEmpty(userPassword)) { int Permission = 0; bool Strength = false; value = setting.Read("File Protection", "AllowCopy"); if (!string.IsNullOrEmpty(value)) Permission = 16; value = setting.Read("File Protection", "AllowPrinting"); if (!string.IsNullOrEmpty(value)) Permission += 2052; value = setting.Read("File Protection", "EncryptionStrength"); if (!string.IsNullOrEmpty(value) && value == "strength128bits") Strength = true; stamper.SetEncryption(Strength, userPassword, ownerPassword, Permission); } } iTextSharp.text.Rectangle rect = null; float X = 0; float Y = 0; int pageCount = 0; PdfContentByte underContent = null; rect = reader.GetPageSizeWithRotation(1); pageCount = reader.NumberOfPages; reader.Close(); iTextSharp.text.Image img = iTextSharp.text.Image.GetInstance(watermarkImage); if (img.Width > rect.Width || img.Height > rect.Height) { img.ScaleToFit(rect.Width, rect.Height); X = (rect.Width - img.ScaledWidth) / 2; Y = (rect.Height - img.ScaledHeight) / 2; } else { X = (rect.Width - img.Width) / 2; Y = (rect.Height - img.Height) / 2; } img.SetAbsolutePosition(X, Y); for (int i = 1; i <= pageCount; i++) { underContent = stamper.GetUnderContent(i); underContent.AddImage(img); } stamper.Close(); inputPdfStream.Close(); } } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } |
- AddWatermarkText
Using this method you may add water mark Text on every pages. So it will protect your data from theft.
private void AddWatermarkText(string sourceFile, string outputFile, string watermarkText, iTextSharp.text.pdf.BaseFont watermarkFont = null, float watermarkFontSize = 48, BaseColor watermarkFontColor = null, float watermarkFontOpacity = 0.3f, float watermarkRotation = 45f) { using (Stream inputPdfStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read)) using (Stream outputPdfStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write, FileShare.None)) { PdfReader reader; string userPassword = "imdadhusen"; //Imdadhusen is a master Password but you can unlock with User Password also. string ownerPassword = "imdadhusen"; try { reader = new PdfReader(sourceFile); } catch (BadPasswordException) { System.Text.Encoding enc = System.Text.Encoding.ASCII; Byte[] myByteArray = enc.GetBytes(userPassword); reader = new PdfReader(sourceFile, myByteArray); } var stamper = new PdfStamper(reader, outputPdfStream); string value = setting.Read("File Protection", "AllowFileProtection"); if (!string.IsNullOrEmpty(value) && Convert.ToBoolean(value) == true) { userPassword = setting.Read("File Protection", "Password"); if (!string.IsNullOrEmpty(userPassword)) { int Permission = 0; bool Strength = false; value = setting.Read("File Protection", "AllowCopy"); if (!string.IsNullOrEmpty(value)) Permission = 16; value = setting.Read("File Protection", "AllowPrinting"); if (!string.IsNullOrEmpty(value)) Permission += 2052; value = setting.Read("File Protection", "EncryptionStrength"); if (!string.IsNullOrEmpty(value) && value == "strength128bits") Strength = true; stamper.SetEncryption(Strength, userPassword, ownerPassword, Permission); } } iTextSharp.text.Rectangle rect = null; PdfGState gstate = null; int pageCount = 0; PdfContentByte underContent = null; rect = reader.GetPageSizeWithRotation(1); if (watermarkFont == null) watermarkFont = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED); if (watermarkFontColor == null) watermarkFontColor = BaseColor.BLUE; gstate = new PdfGState(); gstate.FillOpacity = watermarkFontOpacity; gstate.StrokeOpacity = watermarkFontOpacity; pageCount = reader.NumberOfPages; for (int i = 1; i <= pageCount; i++) { underContent = stamper.GetUnderContent(i); var _with1 = underContent; _with1.SaveState(); _with1.SetGState(gstate); _with1.SetColorFill(watermarkFontColor); _with1.BeginText(); _with1.SetFontAndSize(watermarkFont, watermarkFontSize); _with1.SetTextMatrix(30, 30); _with1.ShowTextAligned(Element.ALIGN_CENTER, watermarkText, rect.Width / 2, rect.Height / 2, watermarkRotation); _with1.EndText(); _with1.RestoreState(); } stamper.Close(); inputPdfStream.Close(); } } |
- Classes
- Coordinates
This class has only two properties which can hold information about X and Y co-ordinates for displaying page number.
public class Coordinates { public float X { get; set; } public float Y { get; set; } } |
- Usage of PDF Merger
PDF Merger is a command line utility, that will combine multiple files (.pdf) into single file (.pdf). Using this utility you can protect your document (.pdf) with several features like.
- Password protection.
- Protect from copy and print.
- Custom page number and alignment.
- Watermark content protection.
- Image
- Text
- Add name space to your project
using PDFMerger; |
- Declare PDF Merger object
PDFLibrary.PDFManager merge; |
- Assign Isynchronize object and Notify delegate object within constructor.
merge = new PDFLibrary.PDFManager(this, new PDFLibrary.PDFManager.NotifyProgress(DelegateProgress)); |
- Create delegate progress function to notify UI at specific interval
This method contains four parameters TotalFiles (which contain total number of (pdf) files, ProcessFileIndex (will contain curruntly processing file number), TotalPages (total number of pages of all files (.pdf) and PageIndex (page number of currently processing file).
private void DelegateProgress(int TotalFiles, int ProcessFileIndex, int TotalPages, int PageIndex) { try { if (merge != null && merge.TotalPages > 0) { this.Invoke((MethodInvoker)delegate { int pagepercent = merge.ProcessFilePageIndex * 100 / merge.ProcessFilePages; TextProgressBar pb = (TextProgressBar)lstFiles.GetEmbeddedControl(3, merge.ProcessFileIndex - 1); pb.Text = string.Format("{0:00} %", pagepercent); pb.Value = pagepercent; int percent = merge.PageIndex * 100 / merge.TotalPages; ProgressStripItem statusProgrss = (ProgressStripItem)tsStatus.Items[1]; statusProgrss.TextProgressBar.Value = percent; statusProgrss.TextProgressBar.Text = string.Format("{0:00}%", percent); DisplayStatusMessage(string.Format("File Name:{0}, File {1:00} of {2:00}, Page {3:00} of {4:00}", Path.GetFileName(merge.ProcessFileName), merge.ProcessFileIndex, merge.TotalFiles, merge.ProcessFilePageIndex, merge.ProcessFilePages)); if (percent >= 100) { DisplayStatusMessage(string.Format("File merged successfully at {0}", merge.DestinationFile)); for (int i = 0; i < lstFiles.Items.Count; i++) { lstFiles.RemoveEmbeddedControl(lstFiles.GetEmbeddedControl(3, i)); } ClearList(); merge = null; } }); } else { ((ProgressStripItem)tsStatus.Items[1]).TextProgressBar.Text = string.Format("{0:00}%", 0); DisplayStatusMessage(string.Format("File {0:00} of {1:00}, Page {2:00} of {3:00}", 0, 0, 0, 0)); } } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } |
- Setting up destination folder path, where store generated or merged file (.pdf). If destination path does not exists then it will automatically created.
merge.DestinationFile = “D:/MergedFiles/”; |
- Execute merge proces.
merge.Execute(); |
- Settings
In the above code you will get SettingManager object in the several methods. This is a class (.ini) which store information about users setting for example.
- File Protection Setting
- Encryption Strength
- Password
- Allow Copy
- Allow Printing
- Page Number and Formatting
- Vertical Alignment
- Horizontal Alignment
- Page Number Format
- Watermark Image or Text
- Watermark Image Path
- Watermark Text Value
- Font Name
- Font Color
- Merged PDF Location
Using following code you can Get and Set of your global settings in (.ini) file.
- Declaration
SettingManager setting = new SettingManager(Path.Combine(Application.StartupPath, "settings.ini")); |
- Read Setting
string value = setting.Read("Page Number and Formatting", "AollowPageFormatting"); |
- Save Setting
setting.Write("File Protection", "Password", “imdadhusen”); |
- Comments and Feedback
Creating and maintaining PDFMerger library has required – and still does – a considerable amount of work and effort.
PDFMerger is free, and I hope that you find it useful. If you’d like to support future development and new product features, please make a your valuable comments, feedback, suggestions or appreciation via Comments, mail, messages or rating.
These efforts are used to cover and improve product efficiency.
- Thank you very much for using PDFMerger
