Merge documents with DynaPDF
Recently we got a request to merge documents of several types. Basically PDF files and images. Clients want to merge report from FileMaker with attachments like image files of repaired items and additional certificates as PDF documents. The start file has a layout with six container fields for input. It could have been a portal with a sub table for the fields, but for some reason the client didn't do that. So six fields may or may not contain some content.
We start with a script by checking if DynaPDF needs initialization. Either on startup or first time you use our plugin's DynaPDF functions, please locate where the library is and call DynaPDF.Initialize. Our examples look for the library file in the same folder, but you can change that. Newer plugins for a few years allow you to just pass the file name instead of a full path. And if the file name is the default one, you can leave it away. Then our plugin will look for dynapdf.dylib (macOS), dynapdf.dll (Windows) or dynapdf.so (Linux) in the same folder as the plugin file.
# Initialize DynaPDF if needed
If [ MBS("DynaPDF.IsInitialized") ≠ 1 ]
Perform Script [ Specified: From list ; “InitDynaPDF” ; Parameter: ]
End If
For DynaPDF we have a context object, which you reference in FileMaker scripts with a number and we usually store the value in $pdf. This way you can have multiple scripts using DynaPDF functions in parallel. The context for DynaPDF includes the current working PDF and the import PDF, if you have one open.
# Clear current PDF document
Set Variable [ $pdf ; Value: MBS("DynaPDF.New") ]
For our example we need to loop over the containers to import. You could easily switch to loop over records of a found set or over records in a portal of related records. But for now we just pick the fields by name. For that we build the name using the prefix and append the container number.
# Loop over the six container fields we have here
Set Variable [ $count ; Value: 6 ]
Set Variable [ $index ; Value: 1 ]
Loop
# build container name and query value
Set Variable [ $containerFieldName ; Value: "Random Document Aggregation::Container " & $index ]
After we got the field name, we can query the value:
Set Variable [ $containerValue ; Value: GetField ( $containerFieldName) ]
When you have container fields, always have your scripts check if they are empty. Clients may want five fields as they need that once a year, but otherwise just use two or three. Also they may give each container a meaning and so container 2 may be empty, while 3 has a value.
If [ not IsEmpty ( $containerValue ) ]
Since containers may contain a file, an image or a PDF, we have to check what is inside. Images and PDF documents could be added by importing them as files. You note these when you have no preview, but the icon showing for the container. So we check the file name and we get the list of data types inside the container with Container.GetTypes function.
# now check what is inside by looking on types and/or file name
Set Variable [ $containerName ; Value: GetAsText ( $containerValue ) ]
Set Variable [ $containerTypes ; Value: MBS("Container.GetTypes"; $containerValue) ]
First we check if the container contains a PDF document. Either the container types include "PDF" or the name contains the file extension .pdf. There is a little chance the client may have an image file with PDF file extension, but we don't handle such an anomaly here. When we have a PDF, we can open it from the container into DynaPDF. This is the time, when the plugin requests the PDF data from FileMaker, so it gets transferred from storage into memory. For a hosted solution, FileMaker may transfer the container over the network to the FileMaker Pro instance. The plugin takes the data and then DynaPDF reads the directory quickly.
# Try PDF
If [ Position ( $containerTypes ; "PDF" ; 1 ; 1 ) > 0 or Position ( $containerName ; ".pdf" ; 1 ; 1 ) > 0 ]
Set Variable [ $r ; Value: MBS("DynaPDF.OpenPDFFromContainer"; $pdf; $containerValue) ]
If [ MBS("IsError") ]
Show Custom Dialog [ "Failed to open PDF " & $index ; $r ]
Else
Once the PDF is open, we can check if it has pages and we can import all pages with DynaPDF.ImportPDFFile function. We have to pass where we like to get the new pages into the working PDF. So we query the page count we have already and add one to append the new pages on the end.
# check if we have pages to import and then append them to the PDF
If [ MBS( "DynaPDF.GetImportPageCount"; $PDF ) > 0 ]
Set Variable [ $destpage ; Value: MBS("DynaPDF.GetPageCount"; $pdf) + 1 ]
Set Variable [ $r ; Value: MBS("DynaPDF.ImportPDFFile"; $pdf; $destpage) ]
If [ MBS("IsError") ]
Show Custom Dialog [ "Failed to import pages from PD…" ; $r ]
End If
End If
End If
Else
For images, we check if we have HEIC of HEIF files. These are image files from iPhones and they can't directly be read into GraphicsMagick or DynaPDF, so we need to covert them to PNG. Then we can continue using the PNG below:
# if we have HEIC or HEIF, please convert
If [ Position ( $containerName ; ".HEIC" ; 1 ; 1 ) > 0 or Position ( $containerName ; ".HEIF" ; 1 ; 1 ) > 0 ]
Set Variable [ $containerValue ; Value: MBS("Container.ReadImage"; $containerValue; "PNG"; "image.png" ) ]
End If
Next we use DynaPDF to read the image format. This allows DynaPDF to preflight the image and query the width and height of the image for us. If DynaPDF can't read the image file, we get an error back.
# image containers
Set Variable [ $r ; Value: MBS("DynaPDF.ReadImageFormat"; $pdf; $containerValue) ]
If [ MBS("IsError") ]
Show Custom Dialog [ "Failed to read image " & $index ; $r ]
Else
# calculate zoom factor to scale proportional to something like A4
Set Variable [ $ImageWidth ; Value: GetAsNumber (LeftValues ( $r ; 1 )) ]
Set Variable [ $imageHeight ; Value: GetAsNumber (MiddleValues ( $r ; 2; 1 )) ]
The image sizes may be huge. If we convert a 4000x3000 pixel image into the same size as point, this would be 6 pages wide and 4 pages height. So we calculate the scale factor on how much we would have to scale down the image to fit it on a normal page. We use 842 points here for A4 format. The image is scaled proportionally. And don't worry if you use other page formats. The PDF viewer will scale the image to paper size anyway. The size we use here for the image is just for how big the page appears in the viewer relative to the other pages.
Set Variable [ $PageHeight ; Value: 842 // DIN A4 ]
Set Variable [ $Factor ; Value: Min( $PageHeight / $ImageHeight; $PageHeight / $ImageWidth) ]
When you add a picture with DynaPDF, you can pass through JPEG images and avoid recompression.
# no recompression if possible!
Set Variable [ $r ; Value: MBS("DynaPDF.SetSaveNewImageFormat"; $pdf; 0) ]
With the size and the scale factor, we can calculate the size where to draw the image into the PDF page.
# calculate where the image goes
Set Variable [ $x ; Value: 0 ]
Set Variable [ $y ; Value: 0 ]
Set Variable [ $w ; Value: $ImageWidth * $factor ]
Set Variable [ $h ; Value: $ImageHeight * $factor ]
Now it is time to create a new page in the PDF with DynaPDF.AppendPage function. Then we set the page size and insert the image on the page.
# and insert on a page with matching size
Set Variable [ $r ; Value: MBS("DynaPDF.AppendPage"; $pdf) ]
Set Variable [ $r ; Value: MBS("DynaPDF.SetPageHeight"; $pdf; $h) ]
Set Variable [ $r ; Value: MBS("DynaPDF.SetPageWidth"; $pdf; $w) ]
Set Variable [ $r ; Value: MBS("DynaPDF.InsertImage"; $pdf; $containerValue; $x; $y; $w; $h) ]
Optionally, we can add text above the image. Here are three lines to do that. First the page size needs to be a bit height. PDF usually has bottom-up coordinates, so the page starts on the bottom and y coordinates go up. We didn't switch that to top-down coordinates with DynaPDF.SetPageCoords function, we can just add 50 points to the page size and get more space on the top. Then we set the font and draw a text above it. This could be a kind of label for thepicture.
Set Variable [ $r ; Value: MBS("DynaPDF.SetPageHeight"; $pdf; $h+ 50) ]
Set Variable [ $r ; Value: MBS("DynaPDF.SetFont"; $pdf; "Helvetica"; 0; 20) ]
Set Variable [ $r ; Value: MBS("DynaPDF.WriteFTextEx"; $pdf; 0; $h+50; $w; 50; "center"; "Hello World") ]
Time to close the page and continue the loop.
Set Variable [ $r ; Value: MBS("DynaPDF.EndPage"; $pdf) ]
End If
End If
End If
#
# next
Set Variable [ $index ; Value: $index + 1 ]
Exit Loop If [ $index > $count ]
End Loop
If you like to add page numbers, you may copy the "Add Page Numbers" script from our Merge example file.
Perform Script [ Specified: From list ; “Add Page Numbers” ; Parameter: $pdf ]
At the end, we can save the new PDF into a container field. Pass the new file name here,. This file name is used when exporting the field content later.
# show final PDF
Set Field [ Random Document Aggregation::Final Combined PDF ; MBS("DynaPDF.Save"; $pdf; "Merged.pdf") ]
Set Variable [ $r ; Value: MBS("DynaPDF.Release"; $pdf) ]
With this solution, another client is happy. Feel free to copy and extend to suite your needs.
Let us know if you have questions.