Earlier this week, I talked about proxying Gravatar photos as a way to serve extra aggressive Cache-Management
headers in ColdFusion. One other good thing about proxying Gravatar is that I can exert extra management over what occurs when the given consumer does not have a Gravatar picture. That means, as a substitute of utilizing the present, default Arnold Schwarzenegger avatar, I would be capable of generate a per-user customized avatar. As a primary step on this exploration, I needed to see if I may use the CFImage
tag / picture features in Adobe ColdFusion 2021 to generate name-based photos.
Putting in a Font: Roboto Mono
While you draw textual content to a picture in ColdFusion, you may’t simply use any arbitrary font – you may solely use fonts that the JVM (Java Digital Machine) is aware of about. Moreover, the JVM appears to have to know in regards to the font at start-up time. Which suggests, in case you set up a brand new font on the system, it’s a must to restart the ColdFusion course of to ensure that stated font to be consumable.
ASIDE: Paul Klinkenberg has a put up on dynamically registering fonts at runtime in order that you do not have to truly restart the ColdFusion course of. Sadly, this appear to be a Lucee-only conduct. For causes which might be past me, his method works high-quality within the newest Lucee CFML, however throws an error within the newest Adobe ColdFusion.
I made a decision to make use of Roboto Mono from Google Fonts. Regionally, in my Dockerized growth atmosphere, I downloaded the font recordsdata and added this line to my Dockerfile
:
ADD ./fonts /usr/share/fonts/truetype
This copies my ./fonts/roboto_mono
folder into my Adobe ColdFusion 2021 CommandBox picture.
In manufacturing, which is each Home windows and not Dockerized, I merely copied the .ttf
font recordsdata into my c:WindowsFonts
folder (and restarted the ColdFusion Software Server service).
Drawing Centered Textual content to the ColdFusion Picture
While you draw textual content to an Picture in ColdFusion, you present the {x,y}
coordinate that the textual content will begin at. Which is ok in case you simply need your textual content to be right-aligned. In my case, nevertheless, I would like the textual content to be centered inside the picture, each vertically and horizontally. To do that, I have to “measure” the textual content because it will probably be rendered to the picture; after which, alter the {x,y}
coordinates of the textual content based mostly on stated measurement.
Up to now, I’ve checked out measuring picture textual content dimensions as a way to render wrapped textual content in a ColdFusion picture. Nevertheless, on this case, I am taking a cue from Ray Camden, who used the java.awt.font.TextLayout
class to search out the bounding field of a given textual content worth.
Within the following ColdFusion element, AvatarGenerator.cfc
, I’ve encapsulated this measurement logic in a personal methodology known as, measureText()
. This takes the picture
, the textual content I need to render, and the font properties I intend to make use of (ie, the Font title and the dimensions). This methodology returns the bounding field measurements, which I then use within the picture.drawText()
name:
element
accessors = true
output = false
trace = "I generate easy, initials-based avatars."
{
// Outline properties for dependency-injection.
property scratchDisk;
// ---
// PUBLIC METHODS.
// ---
/**
* I generate an initials-based avatar and return the picture binary. The picture is all the time
* generated as a JPG as a way to match what Gravatar makes use of.
*/
public binary perform generateAvatar(
required string initials,
required numeric measurement
) {
var imageData = withTempDirectory(
( tempDirectory ) => {
var picture = imageNew( "", measurement, measurement, "rgb", "212121" );
picture.setDrawingColor( "ffffff" );
picture.setAntialiasing( true );
var fontProperties = {
font: "Roboto Mono Common",
measurement: ( repair( measurement / 3 ) - 2 )
};
// Since we do not know what textual content goes to be handed into the perform,
// we have to "measure" the textual content that will probably be rendered to the picture when
// utilizing the given textual content worth and font properties. We will then use this
// to middle the textual content inside the canvas measurement.
var bounds = measureText( picture, initials, fontProperties );
// Heart textual content horizontally.
var x = ( ( measurement / 2 ) - ( bounds.width / 2 ) - bounds.xOffset );
// Heart textual content vertically.
var y = ( ( measurement / 2 ) + ( bounds.peak / 2 ) );
picture.drawText( initials, x, y, fontProperties );
// Save the picture object to disk (Digital File System on this case, for
// quick file I/O operations).
var imagePath = ( tempDirectory & "/avatar.jpg" );
picture.write( imagePath, 0.80 );
return( fileReadBinary( imagePath ) );
}
);
return( imageData );
}
// ---
// PRIVATE METHODS.
// ---
/**
* I get the bounding field dimensions of the given textual content as it will seem written to
* the given picture.
*/
non-public struct perform measureText(
required any picture,
required string textual content,
required struct fontProperties
) {
var awtContext = picture.getBufferedImage()
.getGraphics()
.getFontRenderContext()
;
// CAUTION: When decoding a font definition, you should utilize both an area (" ") or
// a touch ("-") delimiter. However, you can not mix-and-match the 2 characters. As
// such, when you have a Font title which has areas in it (ex, "Roboto Mono"), you
// MUST USE the sprint delimiter as a way to stop Java from parsing the font title
// as a multi-item record. On this case, be aware that I'm utilizing the "-" as a result of I do know
// my font title does not comprise a touch.
var awtFont = createObject( "java", "java.awt.Font" )
.decode( "#fontProperties.font#-#fontProperties.measurement#" )
;
var bounds = createObject( "java", "java.awt.font.TextLayout" )
.init( textual content, awtFont, awtContext )
.getBounds()
;
return({
width: bounds.width,
peak: bounds.peak,
xOffset: bounds.x,
yOffset: bounds.y
});
}
/**
* I execute the given callback, passing in a brief listing that can be utilized for
* transient file IO. The non permanent listing is deleted after the callback has been
* executed.
*/
non-public any perform withTempDirectory( required perform callback ) {
var folderPath = ( scratchDisk & "https://www.bennadel.com/" & createUuid() );
attempt {
directoryCreate( folderPath );
return( callback( folderPath ) );
} lastly {
directoryDelete( folderPath, true );
}
}
}
This ColdFusion element has a single public methodology, generateAvatar()
, which takes the consumer’s initials, corresponding to “BN” for “Ben Nadel”, and a picture dimension after which renders the picture and returns the picture binary. Which suggests, I can pipe the binary response on to the CFContent
tag’s variable
attribute:
<cfscript>
// NOTE: I am utilizing RAM disk (Digital File System) because the scratch disk for the picture
// operations in order that I get quick I/O efficiency.
generator = new AvatarGenerator()
.setScratchDisk( "ram://" )
;
cfcontent(
sort = "picture/jpeg",
variable = generator.generateAvatar( "BN", 120 )
);
</cfscript>
Now, if I run this ColdFusion code, we are able to see the “BN” avatar being generated immediately and getting streamed to the browser:
In fact, you do not need to be producing photos on-the-fly on a regular basis. So, one thought is perhaps to render these to disk first after which serve them up; or, I would simply depend on the CDN (Content material Supply Caching) to cache them and stop pointless processing. However, that is a thought for a special put up.
Wish to use code from this put up?
Try the license.