GetTextDimensions() For Finding ColdFusion Image Text Dimensions
I have added a user defined function to the imageUtils.cfc ColdFusion image manipulation component. This function, GetTextDimensions(), takes a string and a font-properties struct and determines the height and width dimensions of the rendered text in a ColdFusion image. This can be used to more precisely layout text elements of a generated ColdFusion image. In fact, this functionality is what is powering my ImageDrawTextarea() and the imageUtils.cfc DrawTextarea() functions.
<cffunction
name="GetTextDimensions"
access="public"
returntype="struct"
output="false"
hint="Give the string and the font properties, the width and height of the text is calculated. If the font properties struct is missing any values, ColdFusion's default values will be used.">
<!--- Define arguments. --->
<cfargument
name="Text"
type="string"
required="true"
hint="The text whose dimensions we are going to calculate."
/>
<cfargument
name="FontProperties"
type="struct"
required="true"
hint="The font properties used to calculate the text dimensions."
/>
<!--- Define the local scope. --->
<cfset var LOCAL = {} />
<!---
Create an canvas to work with. We won't actually need
to draw on this, we just need it to get access to some
of the default values that ColdFusion uses plus we can
get access to some of the utility methods.
--->
<cfset LOCAL.Image = ImageNew( "", 1, 1, "rgb" ) />
<!---
From our temporary ColdFusion image, get the underlying
Java AWT object. This will allow us access to properties
that we will need to space and style the font.
--->
<cfset LOCAL.Graphics = ImageGetBufferedImage( LOCAL.Image )
.GetGraphics()
/>
<!---
Get the font that is currently set in the image. From
this, we will be able to default the properties of our
text attributes (any that were not set explicitly).
--->
<cfset LOCAL.CurrentFont = LOCAL.Graphics.GetFont() />
<!---
Now, we are going to check to see if the passed in
font properties has all the properties that we need to
properly render the new font. If it does not, then we
are going to use the ColdFusion default values that are
supplied with our temporary canvas.
--->
<!--- Check for a defined size.--->
<cfif NOT StructKeyExists( ARGUMENTS.FontProperties, "Size" )>
<!--- Get size from current font. --->
<cfset ARGUMENTS.FontProperties.Size = LOCAL.CurrentFont.GetSize() />
</cfif>
<!--- Check for a defined font. --->
<cfif NOT StructKeyExists( ARGUMENTS.FontProperties, "Font" )>
<!--- Get font name from current font. --->
<cfset ARGUMENTS.FontProperties.Font = LOCAL.CurrentFont.GetFontName() />
</cfif>
<!--- Check for a defined style. --->
<cfif NOT StructKeyExists( ARGUMENTS.FontProperties, "Style" )>
<!---
When it comes to defaulting the style, we need to
build not only the font attributes, but also the
font style argument for creating our new font (for
the Font Metrics). Because of that, we will be
building a bit-mask for the style.
Because the Styles are just constants, we can pull
them out of our Current Font object.
--->
<cfif (
LOCAL.CurrentFont.IsBold() AND
LOCAL.CurrentFont.IsItalic()
)>
<!--- Set the style. --->
<cfset ARGUMENTS.FontProperties.Style = "bolditalic" />
<!--- Set the bit mask. --->
<cfset LOCAL.FontStyleMask = BitOR(
LOCAL.CurrentFont.BOLD,
LOCAL.CurrentFont.ITALIC
) />
<cfelseif LOCAL.CurrentFont.IsBold()>
<!--- Set the style. --->
<cfset ARGUMENTS.FontProperties.Style = "bold" />
<!--- Set the bit mask. --->
<cfset LOCAL.FontStyleMask = LOCAL.CurrentFont.BOLD />
<cfelseif LOCAL.CurrentFont.IsItalic()>
<!--- Set the style. --->
<cfset ARGUMENTS.FontProperties.Style = "italic" />
<!--- Set the bit mask. --->
<cfset LOCAL.FontStyleMask = LOCAL.CurrentFont.ITALIC />
<cfelse>
<!--- Set the style. --->
<cfset ARGUMENTS.FontProperties.Style = "plain" />
<!--- Set the bit mask. --->
<cfset LOCAL.FontStyleMask = LOCAL.CurrentFont.PLAIN />
</cfif>
<cfelse>
<!--- Set the plain font mask. --->
<cfset LOCAL.FontStyleMask = LOCAL.CurrentFont.PLAIN />
</cfif>
<!---
ASSERT: At this point, we have fully set up our font
properties struct (filling in any gaps that the user
missed), as well as create a style bit-mask based on
those properties. We now have enough information to
create a new Java Font object.
--->
<!---
Now that we have our Font attributes all paramed, we
need to create a new Font object (that will be used to
get the Font Metrics for the passed-in text).
--->
<cfset LOCAL.NewFont = CreateObject(
"java",
"java.awt.Font"
).Init(
JavaCast( "string", ARGUMENTS.FontProperties.Font ),
JavaCast( "int", LOCAL.FontStyleMask ),
JavaCast( "int", ARGUMENTS.FontProperties.Size )
)
/>
<!---
Now that we have our new Font set up, get the
Font Metrics for our graphic in the context of
the new Font.
--->
<cfset LOCAL.FontMetrics = LOCAL.Graphics.GetFontMetrics(
LOCAL.NewFont
) />
<!---
Using the Font Metrics, get the bounds of the
current text line with the addition of the next
word.
--->
<cfset LOCAL.TextBounds = LOCAL.FontMetrics.GetStringBounds(
JavaCast( "string", ARGUMENTS.Text ),
LOCAL.Graphics
) />
<!--- Now that we have the font metrics, let's create the dimensions return value. --->
<cfset LOCAL.Return = {
Width = Ceiling( LOCAL.TextBounds.GetWidth() ),
Height = Ceiling( LOCAL.TextBounds.GetHeight() )
} />
<!--- Return text dimensions. --->
<cfreturn LOCAL.Return />
</cffunction>
It returns a structure that has Width and Height keys. Here, you can see it in action:
<!--- Set text string. --->
<cfset strText = "Hey there, bad momma!" />
<!--- Set font properties. --->
<cfset objProperties = {
Font = "Times New Roman",
Size = "18"
} />
<!--- Get text dimensions. --->
<cfset objDimensions = GetTextDimensions(
strText,
objProperties
) />
<!--- Dump out dimensions. --->
<cfdump
var="#objDimensions#"
label="Dimensions for: #strText#"
/>
Running the above code, we get the following CFDump output:
Want to use code from this post? Check out the license.
Reader Comments
As always, your posts are immensely helpful. Thank you for sharing your hard work.
There is a bug in the bit where it sets the LOCAL.FontStyleMask value if the user provided a Style. The FontStyleMask is always getting set to PLAIN, regardless of the Style the user passes in. Instead of setting the FontStyleMask inside the block where ' NOT StructKeyExists("Style") ', you can move the bit mask parts out into a separate block following that and base it off of the now corrected ARGUMENTS.FontProperties.Style.
@Jim,
Thank you for your debugging. I will definitely look into that.