Nando @ Aria Media

( learning Clojure ColdFusion Lucee Javascript jQuery Angular CSS Linux Apache HTML5 & etc )

Generating Accurate PDFs Using Cfdocument

| Comments

I’ve been developing a PDF, generated within a CFML application, that needs very accurate placement of chunks of text so that it can be printed on a standard form. It also needs to use a specified font, OCR-B, so that a line of text on the printed form can be machine scanned. I’ve managed to get this working much better than expected on both ACF and Lucee, in a cross-compatible way, so I thought I’d write everything up to remember the details down the line.

I was at first thinking I might be able to use the new cfhtmltopdf tag, but quickly dropped that idea: I couldn’t get the PDF Service needed to use this tag to work on my Mac; it seems to be an enterprise only feature; and I wasn’t so sure this approach would be compatible to use within Lucee. After looking around over the fence for a bit at potential solutions outside of CFML, nothing hit me as particularly appealing, so I dug into getting cfdocument to work as best I could. First the tag attributes.

Attributes

1
<cfdocument format="pdf" pagetype="A4" orientation="portrait" fontEmbed="true" unit="cm" localUrl="true" saveAsName="#pdfFileName#" marginLeft="0" margintop="0" marginright="0" marginbottom="0">

FontEmbed=“true” is essential so that anyone can print the PDF with the OCR-B code line correctly displayed, even if they don’t have this font on their system. LocalUrl is set to true to easily pull in a logo image from the local file system. Note the margins are set to 0, because I’m using a <div> tag to define the size of the page to control the tendency of the 2 different PDF generation engines used in ACF and Lucee to scale the layout, differently.

Layout

Nested directly within the cfdocument tag is a div tag that sets the page width, with position:relative so it remains within the page flow, and acts as the parent tag within which all layout divs that position text are nested and proportionally scaled against:

1
<div style="position:relative; top:0mm; left:0mm; width:210mm;">

Then within that parent div tag are nested the various div tags, absolutely positioned, containing the blocks of text and images that make up the PDF content. Here are a few examples to demonstrate:

1
2
3
4
5
6
7
8
9
10
11
<div class="address"  style="position:absolute; top:13mm; left:100mm; ">
  #biller.address1# · #biller.zipcode# #biller.city#
</div>
<!--- client address --->
<div class="text" style="position:absolute; top:45mm; left:130mm">
  #invoice.getClient().getName()#<br>
  <cfif len( trim( invoice.getClient().getAddress1() ) )>#invoice.getClient().getAddress1()#<br></cfif>
  <cfif len( trim( invoice.getClient().getAddress2() ) )>#invoice.getClient().getAddress2()#<br></cfif>
  <cfif len( trim( invoice.getClient().getAddress3() ) )>#invoice.getClient().getAddress3()#<br></cfif>
  #invoice.getClient().getCountryCode()#-#invoice.getClient().getZipCode()# #invoice.getClient().getCity()#
</div>

What I really like about this approach is that each of the text blocks winds up very close to the top and left dimensions specified, and the ACF and Lucee outputs are nearly identical.

Without the parent div tag specifying the width, the results between the 2 engines are vastly different, and positioning the elements is much more a question of trial and error than simply entering the top and left positions as measured with a ruler (and perhaps tweaking them by a few milimeters if necessary). Also without the parent div tag to control how layout elements scale, changing the dimension of one absolutely positioned div within the PDF can easily alter the position or size of other divs, which can be very frustrating if you have 20 or 30 elements that all need to be precisely positioned.

Feel free to experiment with paddings or margins on the parent div if you want - I suspect it would work just as well (but haven’t tried). For myself, I found it easier simply to measure placement from the edge of the page.

I haven’t tried this myself, yet, but I have it from a reliable source that a parent div tag in the layout specifying the page width, as above, will fix page break issues if a table extends over more than one page.

Fonts

Specifying a font for a block of text is simple. You can use a CSS style declaration within a style block that is nested within the cfdocument tag, or you can place the font specification directly in a style attribute of an html tag like `

. Here are the styles I used for the above text blocks (and again, the style block must be nested within the cfdocument tag, or the PDF generator won’t see it):

1
2
3
4
5
6
7
8
9
10
11
12
13
<cfdocument format="pdf" pagetype="A4" orientation="portrait" fontEmbed="true" unit="cm" localUrl="true" saveAsName="#pdfFileName#" marginLeft="0" margintop="0" marginright="0" marginbottom="0">
<style>
  div.text {
      font: 9pt Arial;
      line-height: 13pt;
  }

  div.address {
      font: 9pt Arial;
      color: #033B7D;
  }
  ...
</style>

As of this writing, available CSS attributes remain limited. There is a list of CSS attributes that work in the ACF documentation for cfdocument, reproduced here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
The cfdocument tag supports the following CSS styles:

background
background-attachment
background-color
background-image
background-position
background-repeat
border
border-bottom
border-bottom-color
border-bottom-style (solid border only)
border-bottom-width
border-color
border-left
border-left-color
border-left-style (solid border only)
border-left-width
border-right
border-right-color
border-right-style (solid border only)
border-right-width
border-spacing
border-style (solid border only)
border-top
border-top-color
border-top-style (solid border only)
border-top-width
border-width
bottom
clear
clip
color
content (strings, counters only)
counter-increment
counter-reset
cursor
display
float
font
font-family
font-size
font-style
font-weight
height
left
letter-spacing
line-height
list-style-type
margin
margin-bottom
margin-left
margin-right
margin-top
outline
outline-color
outline-style (solid, dotted, dashed only)
outline-width
padding
padding-bottom
padding-left
padding-right
padding-top
page-break-after
page-break-before
page-break-inside
position
right
text-align (left, right, and center)
text-decoration
text-indent
top
unicode-bidi
vertical-align
visibility
white space (normal, nowrap only)
width
z-index

Custom fonts

Scattered around the internet I found a variety of comments suggesting that getting a “custom” font to work within cfdocument isn’t at all easy. Once you know how, it’s actually fairly simple. For both ACF and Lucee, it’s a question of getting the correct font file in the right place, with the right permissions, and restarting ACF/Lucee.

For ACF, log into the administrator and go to the Font Management Panel. There you will see all the fonts available to use in cfdocument … except that as of this writing, the OTF fonts won’t work, even tho’ the Font Management panel claims they are useable in PDFs. Unless something changes in the future, forget OTF fonts for cfdocument. I tried a bunch that already seemed to be recognized, none worked.

At the top of the font panel, you’ll see a feature that allows you to add fonts “Register New Font(s) with ColdFusion”. I couldn’t get this to work reliably - I think the validation routine on it isn’t well designed. Go ahead and try if you like, but I found it easier to simply find a path to a font directory already recognized by ACF from the list of fonts installed ( I chose a directory with TTF files, just to be sure ), copy the TTF font files you want to use to that directory, change the owner/group and permissions to match the other font files in that directory (if necessary on your OS), restart ACF, log back into the administrator, go to the Font Management panel again and look for your fonts. In the left columns of the font table there are 3 names, Font Family, Font Face, Postscript Name. From my experience, using the font face name in your CSS specification should work. ( If not, try the other names - I saw a post suggesting that. )

To be clear, after you get the font installed in a directory that ACF recognizes as a font directory, one way or another, you’ll need to find the name you need to use in your CSS. The file name often does not match the font name.

For Lucee, the process is different. ;-) Go to your Lucee installation directory, and within the /lib/ directory underneath it, find the fonts.jar file. Copy it to a working directory, rename the copy to fonts.zip and unzip it. ( A .jar file is essentially a ZIP file ). In the unzipped fonts directory that results, add your custom font files, again use only TTFs, and then open the pd4fonts.properties file you find in there and following the pattern you see, add the names of the fonts. In Lucee, you can choose the name, but to be cross compatible with ACF, I used the same name that ACF picked up from the font file. Here’s what my pd4fonts.properties looked like after I added the OCR-B font I needed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#this is an autogenerated file. please remove manually any references to copyrighted fonts
#Fri Apr 17 20:00:52 CEST 2009
Arial=arial.ttf
Arial\ Bold=arialbd.ttf
Arial\ Bold\ Italic=arialbi.ttf
Arial\ Italic=ariali.ttf
Courier\ New=cour.ttf
Courier\ New\ Bold=courbd.ttf
Courier\ New\ Bold\ Italic=courbi.ttf
Courier\ New\ Italic=couri.ttf
Times\ New\ Roman=times.ttf
Times\ New\ Roman\ Bold=timesbd.ttf
Times\ New\ Roman\ Bold\ Italic=timesbi.ttf
Times\ New\ Roman\ Italic=timesi.ttf
OCRB=ob______.ttf

I added the last line, OCRB=ob______.ttf, following the pattern FontFaceName=fontFileName.ttf.

Note that spaces in the font name are escaped with backslashes, so those look like this Font\ Face\ Name=fontFileName.ttf.

Ok, save pd4fonts.properties when you’re done, zip the fonts directory up into fonts.zip, rename it to fonts.jar, copy it back where it came from in the /lib/ directory, overwriting … no wait! Don’t just overwrite the old fonts.jar. Keep a copy of it, just in case … replace the old fonts.jar with the new fonts.jar, change the owner/group permissions to match the old file, and then restart Lucee.

The name to use in your CSS for the font is the name you placed in the pd4fonts.properties file.

Of course, the above approach for Lucee ( 4.5 ) might certainly change in the future. But for now, it works like this.

A big thanks to Michael Hnat for pointing me in the right direction regarding Lucee with his very helpful blog post.

Images

There is a logo image at the top of this PDF that was scaling up when rendered in Lucee in a way that caused it to be misplaced. The top left position was correct, but it was about 50% too big, pixelated, and overran other text. After a bunch of reading that indicated modifying the image print size or resolution would not help, I tried adding a css style declaration to the image that specified a size in mm, and it worked! Thanks to faxi05 for the suggestion.

1
<img src="logos/imageName.png" style="width:78mm; height:14mm" />

… and it’s cross compatible with ACF!

Comments