Adding links to PDF from FileMaker
As a Claris partner, we also resell FileMaker licenses. Buying the license from us has some extra benefit, one of them being a nicely formatted PDF document with all information about the license, including download links. In order to be able to generate this document from our FileMaker based CRM, I needed to find a way to include functional web links in it. So I wrote a custom function that does it, without needing any plug-ins or other external technologies.
The custom function simply takes a PDF saved from FileMaker, and adds a link to it. As long as you know what page and where on that page you want the link to be, you can modify your PDF that easily. If that’s all you need, you can just download my example here. Here’s a video explaining how to use it:
If you want to also know how I created it and how it works, read the whole story below…
The hard old way
In the past, creating the license information PDF for each customer was a tedious manual work. It didn’t take a lot of time for one license, usually less than 5 minutes, but when a new major version of FileMaker was released and we had to send a new document to every customer with a valid maintenance, it was a lot of work.
Automating this within our FileMaker-based CRM was the obvious next step. Except for one thing. FileMaker does not have a native support for including clickable links in the PDF it generates.
The wrong way
I originally intended to use a plug-in for the purpose, MBS was my top candidate. Then I got an idea. What if PDF that already contains links, added to a FileMaker layout, will maintain the links when the layout is saved as PDF?
So I tried to create a simple PDF with just a link on it, cropped it, added it to a FileMaker layout, and saved id as PDF. Then I opened the PDF in Preview, and was able to click the link. This seemed like a success, so I started working on turning this in to an elegant reusable solution. At that moment I was not aware of my big mistake yet…
My idea was to create a lightweight transparent PDF with just the link in it, which I would be then able to easily add to any layout, stretching it to the right size. I just needed to be able to modify the link’s target URL on the fly. So I searched for some technical information about the PDF format to learn how to modify the link inside it.
I started at Wikipedia, where I learned that PDF was standardized as ISO 32000 in 2008, and the technical documentation is publicly available from Adobe. The official documentation is, however, quite difficult to read, so I was happy to find a reference to an easy to understand Hello World example, showing how to create a PDF from scratch with just a text editor. Even though the original web page does no longer exist, the example is still available thanks to the non-profit Wayback Machine initiative.
Just a moment later, I had my transparent PDF with just a link in it, and two more moments later there was a custom FileMaker function generating that PDF dynamically with any link provided. After having all this done, I discovered my big mistake. All my previous tests used literal URLs as the clickable text on the layout. When I placed the PDF over a generic text or non-text objects, links stopped working. It turned out that I actually did not have the links in any of my test PDFs saved from FileMaker, and Preview was just smart enough to recognize the URLs and automatically made them clickable for my convenience.
Too far to go back
It would be natural to return back to the idea of using a plug-in, but I had gone too far already and thanks to the exercise I already knew how easy it is to add a link object to a PDF. So I decided to rather go a bit farther and, instead of creating a new PDF from scratch that contains only the link, create a custom function to add it to an existing PDF.
For this purpose I needed to decompose the already existing PDF first in order to add objects to it, and re-compose it with the added links. I didn’t need to make my solution compatible with any PDF out there, it was sufficient for me to make it work with PDFs generated by FileMaker. So I could afford to make some assumptions, such as that when something can be done multiple ways in PDF, I am fine to only support the way FileMaker does that.
I took advantage of the JSON functions to keep the decomposed PDF structured. The new While function, introduced in FileMaker 18, allowed me to implement the whole functionality solely within custom functions, making it easily portable to other databases. To avoid corrupting the PDF by converting binary data to text, I simply kept compressed stream objects encoded as hexadecimal.
Putting the links in place
The most difficult task remains to put each link in the right place. That’s because web link in PDF is just a special type of annotation, which is placed on specific position within a page, and has specific dimensions. It is simply an invisible (but can be made visible as well) rectangle which responds to clicks. There is no relationship the PDF format would keep track of, between the link and the object it is attached to, whether it’s a piece of text or a picture.
Here’s where the WYSIWYG nature of FileMaker helps a lot. Placement of any layout object in the exported PDF, measured in points (one point is 1/72 of inch) exactly matches the placement on the layout. PDF’s coordinates are measured from the bottom left corner of the page by default, while FileMaker layout’s coordinates are measured from the top left corner, so I had to extract the page height information from the decomposed PDF in order to properly calculate page coordinates from layout coordinates.
The only difficulty that remains on the solution’s side, is to correctly find out where the link should be, in FileMaker’s native coordinate system. We have the GetLayoutObjectAttribute function to get bounding rectangle of any object on the layout, but this function seems to reflect the current state being presented to the user, in screen coordinates. I discovered a better way to go is to use the FieldBounds function, which consistently returns coordinates as defined in Layout Mode, regardless of how the layout is presented to the user. It just cannot be used for other types of objects than fields.
Making it all work in practice
In order to keep my life easy when implementing this in a real solution, I decided on some simple constraints. I set my page margins to all zeros, so that position of any object on layout exactly matches its position on the page. I cannot use vertical sliding, otherwise position of anything below the object that has sliding enabled, cannot be reliably determined in a script. I hard-code each layout part’s height in my script because it would be too complicated to discover it dynamically. And I have to adjust the coordinates of anything appearing below the body part based on the number of records being shown.
See how I have done it in the attached example, and feel free to adapt it to your own solutions. Make sure to also let me know how you like it and how it has helped you.