Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Dan Skaggs
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Dan Skaggs@dskaggs )

Testing Foreign And Unicode Character Rendering With The CFDocument Tag In ColdFusion

By Ben Nadel on
Tags: ColdFusion

Yesterday, I was working with Hugo Bonacci to figure out why certain foreign characters weren't rendering in the ColdFusion CFDocument tag. Character encoding, Unicode, and fonts (in general) are a bit of fuzzy spot in my mental model. But, I do know that not all fonts can render all Unicode characters. To help learn more about the problem of rendering fonts, I wanted to put together a small test harness that would attempt to render foreign characters using all of the fonts available to the ColdFusion CFDocument tag.

The test is fairly simple - I gather up all of the fonts available to ColdFusion; then, I loop over the fonts and attempt to render a CFDocument tag using each font name.

The test didn't go quite as smoothly as I had hoped. The CFDocument tag can't actually consume each of the fonts in the font list. Many of the fonts lead to a java.lang.NullPointerException error (which completely crashes the CFDocument tag). As such, I ended up rendering each font in its own CFDocument tag inside of its own iframe. This way, any error would be contained within a single HTTP request (to said iframe).

Also, I noticed that every font with a "-" in it seemed to fail. As such, when I was computing the collection of unique font names, I stripped-off any "-" suffix. I am not sure if this is "correct"; but, at least it reduced the number of rendering errors that I was getting.

Here is the top-level test page that gathers all of the fonts and outputs the list of iframes:

  • <cfscript>
  •  
  • // In order to get the fonts, we have to log into the CF Admin using the
  • // Administrator API.
  • adminGateway = createObject( "component", "cfide.adminapi.administrator" );
  • adminGateway.login( "********************************" );
  • runtimeGateway = createObject( "component", "cfide.adminapi.runtime" );
  •  
  • // Grab the system fonts and the user fonts.
  • systemFonts = runtimeGateway.getFonts().systemFonts;
  • userFonts = runtimeGateway.getFonts().userFonts;
  •  
  •  
  • // The font list is a little confusing. Half of the fonts seem to completely
  • // error out for me. And, of those fonts, the vast majority of them seemed to
  • // be fonts that contained a "-". As such, this step merges the System Fonts
  • // and the User Fonts while, at the same time, stripping the "- suffix" from
  • // the font list.
  • // --
  • // NOTE: I am using listFirst() for all fonts, regardless of whether or not
  • // there is a "-" present since the lack of "-" will just grab the entire
  • // font name as the "first item" in the list.
  • uniqueFonts = {};
  •  
  • // Merge in the system fonts.
  • for ( fontName in systemFonts ) {
  •  
  • uniqueFonts[ listFirst( fontName, "-" ) ] = true;
  •  
  • }
  •  
  • // Merge in the user fonts.
  • for ( fontName in userFonts ) {
  •  
  • uniqueFonts[ listFirst( fontName, "-" ) ] = true;
  •  
  • }
  •  
  •  
  • // Now that we have a map of the font names, let's convert it to a sorted
  • // array for a slightly better user experience.
  • fontNames = structKeyArray( uniqueFonts );
  •  
  • arraySort( fontNames, "textnocase" );
  •  
  • </cfscript>
  •  
  •  
  • <cfcontent type="text/html; charset=utf-8" />
  • <cfoutput>
  •  
  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • ColdFusion CFDocument Font Testing
  • </title>
  •  
  • <style type="text/css">
  •  
  • iframe {
  • border: 1px solid ##000000 ;
  • height: 50px ;
  • margin: 10px 0px 10px 0px ;
  • overflow: hidden ;
  • width: 100% ;
  • }
  •  
  • </style>
  • </head>
  • <body style="width: 875px ;">
  •  
  • <h1>
  • ColdFusion CFDocument Font Testing
  • </h1>
  •  
  • <cfloop index="fontName" array="#fontNames#">
  •  
  • <!---
  • Render each font inside its own iframe. This way, if the CFDocument
  • tag can't handle the given font, and explodes, the damage is contained.
  • --->
  • <iframe
  • title="Font: #htmlEditFormat( fontName )#"
  • src="./document.cfm?fontName=#urlEncodedFormat( fontName )#">
  • </iframe>
  •  
  • </cfloop>
  •  
  • </body>
  • </html>
  •  
  • </cfoutput>

As you can see, I'm using the ColdFusion Administrator API to programmatically log into the Admin and then gather the list of fonts. Each of the fonts is then rendered (or attempt to be rendered) inside the following iframe:

  • <!---
  • Since our test page has UTF-8 characters embedded within it, we have to tell
  • ColdFusion how to parse this page for proper character interpretation.
  • --->
  • <cfprocessingdirective pageEncoding="utf-8" />
  •  
  • <cfparam name="url.fontName" type="string" />
  •  
  • <cfoutput>
  •  
  • <!---
  • Not all fonts can be used inside of the CFDocument tag. The attempt to use
  • certain fonts will result in a NullPointer exception. As such, we need to
  • wrap the usage in a try / catch so we can show a nicer message to the user.
  • --->
  • <cftry>
  •  
  • <cfdocument
  • format="pdf"
  • fontembed="true"
  • marginBottom="0"
  • marginLeft="0"
  • marginRight="0"
  • marginTop="0"
  • pageHeight="2">
  •  
  • <p style="font-family: '#url.fontName#' ; font-size: 20px ;">
  •  
  • <strong>#url.fontName#</strong>:
  •  
  • <!--- UNICODE CHARACTERS REMOVED - NOT SUPPORTED BY MY BLOG - PARTY FOUL :( --->
  •  
  • </p>
  •  
  • </cfdocument>
  •  
  • <!--- Catch any failure to use the given font. --->
  • <cfcatch>
  •  
  • <p style="font-family: '#url.fontName#' ; font-size: 20px ; color: ##CC0000 ;">
  •  
  • <strong>#htmlEditFormat( url.fontName )#</strong>:
  • &mdash;
  • #htmlEditFormat( cfcatch.message )#
  •  
  • </p>
  •  
  • </cfcatch>
  •  
  • </cftry>
  •  
  • </cfoutput>

In this case, I am using a hard-coded list of characters that Hugo and I knew were causing a problem. This way, it was easier to see which fonts would work and which would not. And, when we run the above code, we get the following page output:


 
 
 

 
 Testing to see which fonts can render which unicode characters in ColdFusion's CFDocument tag. 
 
 
 

As you can see, some fonts completely fail, some fonts render some of the Unicode characters, and other fonts fully support the specific characters that we were trying to render. Of course, just because a font renders these particular characters, it doesn't mean that it will render all Unicode characters. It can only render the characters for which it has glyphs.

In our particular case, the problem ended up being that our ColdFusion JVMs only had like 10 system fonts available, none of which could render all of the Unicode characters in question. Our working solution, at this time, is to install the "Open Sans" font and use that to render the CFDocument tag content. Not only does this match the InVision App branding, it also seems to have fairly solid support for Unicode characters.




Reader Comments

Hi Ben,

In a related question, I have an application that is writing out .TXT files using CFFILE with "charset='utf-8'".

With special french characters, they display fine when you save and view in an editor, but when the user opens the text file in a browser, they display funny. Checking one of the files out in Notepad ++ it doesn't have a BOM at the front of the binary data.

Is there anyway for CF to add the BOM?

I'm also already using the CFPROCESSINGDIRECTIVE tag at the top of the page that generates the code.

Reply to this Comment

@Michael,

Hmm, that's a super interesting question. I know your comment is kind of old - did you get it resolved? I'd have to experiment with this. I've never run into this personally (since I don't write many text files).

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.