Wednesday, May 2, 2012

PDF Layer Merger

 

Recently I’ve created a simple .NET tool, to merge 2 PDF files, one as a layer on top of the other.

The reason behind this, was to be able to digitally remark/comment/mark-up on a PDF file using AutoCAD.  People tend to print out a PDF file, use some highlighters and draw some clouds and comments on this PDF, only to re-scan this afterwards.
This is the way we used to work, but today we live in a digital era…

The tool has been built in VB.NET targeting the NET 2.0 framework, with the use of the excellent PDF library iTextSharp (based on iText).

image


Snippet of the actual transformation function:

  
'''
''' Combines two source pdf's to one single pdf.
'''

''' The file-location incl. the name of the original pdf.
''' The file-location incl. the name of the mark-up pdf.
''' The file-location incl. the name of the generated combined pdf.
''' The name which the added mark-up layer will have in the new pdf.
''' The transformation which is executed on the original pdf: rotation, scaleX, scaleY, offsetX, offsetY
''' Return boolean indication whether the combination of the pdf's was successful.
'''
Public Function PDFCombineLayer(ByVal sourceOriginal As String, ByVal sourceMarkup As String, ByVal destination As String, Optional ByVal NewLayer As String = "SOURCE-DRAWING", Optional ByVal Transform As String = "0;1;1;0;0") As Boolean
Dim oPdfOriginal As iTextSharp.text.pdf.PdfReader = Nothing
Dim oPdfMarkup As iTextSharp.text.pdf.PdfReader = Nothing
Dim oPdfStamper As iTextSharp.text.pdf.PdfStamper = Nothing
Dim PDFDirectContent As iTextSharp.text.pdf.PdfContentByte
Dim iNumberOfPages As Integer = Nothing
Dim iPage As Integer = 0
Dim matrix1 As Drawing2D.Matrix = Nothing
Dim matrix2 As Drawing2D.Matrix = Nothing

Try
'Read the source pdf-files and prepare new file.
oPdfOriginal = New iTextSharp.text.pdf.PdfReader(sourceOriginal)
oPdfMarkup = New iTextSharp.text.pdf.PdfReader(sourceMarkup)
oPdfStamper = New iTextSharp.text.pdf.PdfStamper(oPdfMarkup, New IO.FileStream(destination, IO.FileMode.Create, IO.FileAccess.Write))

'Count pages in original pdf and create new pdf wih same amount of pages.
iNumberOfPages = oPdfOriginal.NumberOfPages
oPdfStamper.GetPdfLayers()

'Make transformation matrix
If Transform = "" Then Transform = "0;1;1;0;0" 'Note transform: rotation(degrees), scaleX, scaleY, offsetX, offsetY
Dim ar() As String = Split(Transform, ";")
If Not ar.Length = 5 Then ar = New String() {0, 1, 1, 0, 0}
If ar(1) <= 0 Or ar(2) <= 0 Then Throw New ArgumentOutOfRangeException("transform", "Make sure that scaling factors of the transformation are larger than zero.")
matrix1 = New Drawing2D.Matrix
matrix1.Rotate(-CSng(ar(0)))
matrix1.Scale(CSng(ar(1)), CSng(ar(2)))
matrix1.Translate(CSng(ar(3)), CSng(ar(4)))
matrix2 = New Drawing2D.Matrix

'Add the new layer to each of the pdf-pages.
If NewLayer = "" Then NewLayer = "SOURCE-DRAWING"
Dim PDFNewLayer As New iTextSharp.text.pdf.PdfLayer(NewLayer, oPdfStamper.Writer)
Dim PDFLayerMem As New iTextSharp.text.pdf.PdfLayerMembership(oPdfStamper.Writer)
PDFLayerMem.AddMember(PDFNewLayer)
Do While (iPage < iNumberOfPages)
'Get new page
iPage += 1
Dim iPageOriginal As iTextSharp.text.pdf.PdfImportedPage = oPdfStamper.Writer.GetImportedPage(oPdfOriginal, iPage)
PDFDirectContent = oPdfStamper.GetUnderContent(iPage) 'add page under existing pdf in new layer
matrix2 = matrix1.Clone 'Cloning is necessary, otherwise all adaptations to matrix2 are also done in matrix1.

'Compensate for the orientation of the original pdf-file.
Dim iRotation As Single = oPdfOriginal.GetPageRotation(iPage)
Select Case iRotation
Case Is = 90
matrix2.Rotate(-iRotation)
matrix2.Translate(-oPdfOriginal.GetPageSize(iPage).Width, 0)
Case Is = 180 'Not tested yet.
matrix2.Rotate(-iRotation)
matrix2.Translate(-oPdfOriginal.GetPageSize(iPage).Width, -oPdfOriginal.GetPageSize(iPage).Height)
Case Is = 270 'Not tested yet.
matrix2.Rotate(-iRotation)
matrix2.Translate(0, oPdfOriginal.GetPageSize(iPage).Height)
Case Else
End Select

'Add the new layer ot this page.
PDFDirectContent.BeginLayer(PDFNewLayer)
PDFDirectContent.AddTemplate(iPageOriginal, matrix2)
PDFDirectContent.EndLayer()
Loop
oPdfStamper.SetFullCompression()
Return True
Catch ex As Exception
Return False
Finally
oPdfMarkup.Close()
oPdfOriginal.Close()
oPdfStamper.Close()
matrix1.Dispose()
matrix2.Dispose()
End Try
End Function

Code has been posted on Google Code http://code.google.com/p/pdflayermerger/


Many thanks to iText for the excellent library and to Lukasz Swiatkowski for the glass button component (http://www.codeproject.com/Articles/17695/Creating-a-Glass-Button-using-GDI).

2 comments:

  1. Thank you sooo much for sharing this code! And I completely agree w/ u on "Knowledge needs to be shared to enlighten the world" :)

    btw, not sure if you are aware of this now, but a good practice is to use 'Using' when dealing with files and filestreaming. Using automatically and safely closes the stream and/or document. Just my 2 cents ;)

    - Prateek

    ReplyDelete
  2. This online pdf merger or pdf combiner or pdf joiner will easily merge pdf files together with use of any software or any registration. pdf combiner

    ReplyDelete