Saturday, June 28, 2008

Wrap and Scale Image Text

Some time ago I posted an entry about an idea I had been batting around. The idea was how to automatically scale wrapped text to fit a given area. As I went to post it I got to thinking about the scaling algorithm and decided it was too inefficient. So I started to work on different approach. Around that time work took off again like a runaway train and ... well you know how it goes.

Anyway, I had some time this weekend and finally came up with a slightly better algorithm for sizing. It is strongly based on code from another entry Text Wrapping with Images. The previous entry explains how to use a LineBreakMeasurer object to wrap text. Now the code in this entry is still in need of some good testing. However, I thought I would offer it up for comments and suggestions, before another two years goes by ..

A few things to keep in mind. This algorithm is not well suited for use with large amounts of text. It is geared towards wrapping small to moderate chunks of text. Also, "italic" text can sometimes extend a few pixels outside the wrap area. I am still investigating possible work-arounds for this issue.

Included below is my CFC code, and an ugly test form that will demonstrate the various options. I will probably write a separate entry that describes the code in detail, for those interested. Otherwise, this will turn into the blog entry without end ..

Test Form


Parameters:





























































Image Width Used only to generate a sample image on the test page
Font name (Optional) Font to use for drawing the text
Image Height Used only to generate a sample image on the test page
Font size (Optional) Guess-timate of the starting font size.
X coordinate Start drawing the text at this x-position
Font Style (Optional) Style to use when drawing the text: Bold, Italic, etcetera. (Default: Plain)
Y coordinate Start drawing the text at this y-position
Text Align (Optional) Indicates how to align the text: Left, Center, Right. (Default: Left)
Wrap width The width of the desired wrap area
Text Color Hexidecimal text color
Wrap height The height of the desired wrap area
Display text The text to display on the image
Minimum increment This function allows fractional font sizes like 14.25, 16.50, etcetera. Use
this value to specify the smallest fractional size allowed. (Default: 1.0 - whole font sizes only)
Scale Text If "true", the text will be scaled to fit within the wrap width AND height.
Otherwise, the text will just be wrapped within the supplied width. (Default: true)
Ignore margins ** This is an experimental feature. If true, the top and bottom text margins will be trimmed
Use Antialising If true, use anti-aliasing to draw smoother text. (Default: false)



CFC Code

<cfcomponent hint="Utility for wrapping image text">

<cffunction name="ImageDrawWrappedText" returntype="struct">
<cfargument name="image" type="any" required="true" hint="ColdFusion image object">
<cfargument name="text" type="string" required="true" hint="The text to draw on the image. Minimum length is 1 character.">
<cfargument name="x" type="numeric" required="true" hint="The starting x coordinate. Minimum value is 0.">
<cfargument name="y" type="numeric" required="true" hint="The starting y coordinate. Minimum value is 0.">
<cfargument name="width" type="numeric" required="true" hint="Wrap the text within this width. Minimum value is 1.">
<cfargument name="height" type="numeric" required="true" hint="Wrap the text within this width. Minimum value is 1.">
<cfargument name="fontAttributes" type="struct" required="false" default="#structNew()#" hint="Text font, style and/or size">
<cfargument name="textColor" type="string" required="false" default="##000000" hint="The text color. Must be a hexidecimal value.">
<cfargument name="useAntialising" type="boolean" required="false" default="false" hint="If true, antialiasing will be used when drawing the text">
<cfargument name="align" type="string" required="false" default="left" hint="Alignment for the drawn text (Left, Right or center)">
<cfargument name="minIncrement" type="numeric" required="false" default="1" hint="Mininum increment for adjusting font size. Use 1.0 to force whole font sizes only. Allowed values: 0.25, 0.50, 1.0 ">
<cfargument name="autoFit" type="boolean" required="false" default="true" hint="If true, the font size will be adjusted to best fit the text into the given width and height">
<cfargument name="ignoreMargin" type="boolean" default="false" hint="If true, ignore top and bottom padding when measuring the text area">

<cfset var Local = {} >

<!---
Validate arguments
--->
<cfif NOT IsImage( arguments.image ) >
<cfthrow message="Image must be a ColdFusion image object" type="Invalid Argument">
</cfif>
<cfif NOT len(trim( arguments.text)) >
<cfthrow message="Text cannot be empty. Minimum length is 1 character" type="Invalid Argument">
</cfif>
<cfif arguments.x LT 0 OR arguments.y LT 0>
<cfthrow message="The x and y coordinates cannot be less than 0" type="Invalid Argument">
</cfif>
<cfif arguments.width LT 1 OR arguments.height LT 1>
<cfthrow message="Wrap width and height values cannot be less than 1" type="Invalid Argument">
</cfif>

<cfset Local.textColor = trim( reReplace( arguments.textColor, "##", "", "all") ) >
<cfif len(Local.textColor) NEQ 6 OR reFindNoCase(Local.textColor, "[^A-F0-9])")>
<cfthrow message="TextColor must be a six-digit hexidecimal value" type="Invalid Argument">
</cfif>

<cfset Local.align = Trim( arguments.align ) >
<cfif NOT listFindNoCase( "left,center,right", arguments.align )>
<cfthrow message="Invalid align value. Allowed values: left,center,right" type="Invalid Argument">
</cfif>

<cfif (arguments.minIncrement NEQ 0.25) AND (arguments.minIncrement NEQ 0.5) AND (arguments.minIncrement NEQ 1)>
<cfthrow message="Allowed minIncrement values are: 0.25, 0.5 and 1.0" type="Invalid Argument">
</cfif>

<cfscript>
// Initialize base variables
Local.retryCounter = 0;
Local.maxRetry = 1000;
Local.iterations = 0;
Local.isValidSize = true;


// Create java font that will be used used to draw the text
Local.graphics = ImageGetBufferedImage( arguments.image ).getGraphics();
Local.fontAttributes = duplicate( arguments.fontAttributes );
Local.fontSize = Local.fontAttributes.size;
Local.font = CreateJavaFont( argumentCollection = Local.fontAttributes );
Local.mustResize = arguments.autoFit;

// If autoFit is enabled, estimate mininum/maximum font range
if ( Local.mustResize ) {

Local.limits = EstimateFontSizeRange( graphics = Local.graphics,
font = Local.font,
text = arguments.text,
x = arguments.x,
y = arguments.y,
width = arguments.width,
height = arguments.height,
ignoreMargin = arguments.ignoreMargin
);

Local.iterations = Local.limits.iterations;
Local.fontSize = Local.limits.high;
Local.mustResize = Local.limits.low neq Local.limits.high;
}

///////////////////////////////////////////////////////////////////////////////////
// Using the range as a guide, determine actual font size required
///////////////////////////////////////////////////////////////////////////////////
if ( Local.mustResize ) {

Local.keepScaling = true;
while ( Local.keepScaling ) {

// Calculate the mid-point font size. Round it to the nearest increment value
Local.lastFontSize = Local.fontSize;
Local.midPoint = ABS(Local.limits.high - Local.limits.low) / 2;
Local.offset = ROUND(Local.midPoint / arguments.minIncrement) * arguments.minIncrement;

// Calculate the next font size. Ensure it is not smaller than minimum size
Local.fontSize = Local.limits.high - Local.offset;
Local.fontSize = MAX( Local.fontSize, Local.limits.low );
Local.font = Local.font.deriveFont( javacast("float", Local.fontSize) );

// Calculate the dimensions of the text at the current font size
Local.dimen = WrapImageText( graphics = Local.graphics,
text = arguments.text,
font = Local.font,
x = arguments.x,
y = arguments.y,
width = arguments.width,
ignoreMargin = arguments.ignoreMargin
);


Local.isInsideWrapArea = Local.dimen.height LTE arguments.height;

// If text fits within the desired wrap area, keep searching for a larger font size
if ( Local.isInsideWrapArea ) {
Local.limits.low = Local.fontSize;
}
// Otherwise, keep searching for a smaller font size
else {
Local.limits.high = Local.fontSize;
}


// Exit when no further sizing is possible
if ( Local.limits.high == Local.limits.low || Local.midPoint <= arguments.minIncrement) {
Local.keepScaling = false;
}

Local.retryCounter += 1;
if ( Local.retryCounter >= Local.maxRetry ) {
Local.keepScaling = false;
}
}

Local.fontSize = Local.limits.low;
Local.isValidSize = Local.dimen.width > 0 && Local.dimen.height > 0;

}


if ( Local.isValidSize ) {

// Preserve original image settings
Local.RenderingHints = createObject("java", "java.awt.RenderingHints");
Local.origTextHint = Local.graphics.getRenderingHint( Local.RenderingHints.KEY_TEXT_ANTIALIASING );
Local.origColor = Local.graphics.getColor();

// Apply the selected anti-aliasing setting
if ( arguments.useAntialising ) {
Local.graphics.setRenderingHint( Local.RenderingHints.KEY_TEXT_ANTIALIASING,
Local.RenderingHints.VALUE_TEXT_ANTIALIAS_ON
);
}
else {
Local.graphics.setRenderingHint( Local.RenderingHints.KEY_TEXT_ANTIALIASING,
Local.RenderingHints.VALUE_TEXT_ANTIALIAS_OFF
);
}

// Finally, draw the text on the image
Color = createObject("java", "java.awt.Color");
Local.graphics.setColor( Color.decode( arguments.textColor ) );
Local.font = Local.font.deriveFont( javacast("float", Local.fontSize) );
Local.dimen = WrapImageText( graphics = Local.graphics,
text = arguments.text,
font = Local.font,
x = arguments.x,
y = arguments.y,
width = arguments.width,
align = arguments.align,
drawText = true,
ignoreMargin = arguments.ignoreMargin

);

// Restore original image settings
Local.graphics.setColor( Local.origColor );
Local.graphics.setRenderingHint( Local.RenderingHints.KEY_TEXT_ANTIALIASING,
Local.origTextHint
);

// Return the new settings
Local.fontAttributes.size = javacast("string", Local.fontSize);
Local.dimen.font = Local.fontAttributes;
Local.dimen.iterations = Local.iterations + Local.retryCounter;
}
</cfscript>

<cfif NOT Local.isValidSize>
<cfthrow message="Unable to fit text into the supplied dimensions">
</cfif>


<cfreturn Local.dimen >
</cffunction>

<cffunction name="WrapImageText" returntype="struct" output="true">
<cfargument name="graphics" type="any" required="true" hint="Underlying graphics object from the ColdFusion image">
<cfargument name="text" type="string" required="true" hint="The text string to measure">
<cfargument name="font" type="any" required="true" hint="Java font object containing the desired style, font and size">
<cfargument name="x" type="numeric" required="true" hint="Starting x coordinate">
<cfargument name="y" type="numeric" required="true" hint="Starting y coordinate">
<cfargument name="width" type="numeric" required="true" hint="Wrap the text within this width">
<cfargument name="align" type="string" required="false" default="right" hint="Text alignment (Left, Right or Center).">
<cfargument name="drawText" type="boolean" default="false">
<cfargument name="ignoreMargin" type="boolean" default="false" hint="If true, ignore top and bottom padding when measuring the text area">

<cfset var Local = structNew()>

<cfscript>
////////////////////////////////////////////////////////////
// Initialize objects used to break text into lines
////////////////////////////////////////////////////////////
Local.TextAttribute = createObject("java", "java.awt.font.TextAttribute");
Local.string = createObject("java", "java.text.AttributedString").init( arguments.text );
Local.string.addAttribute( Local.TextAttribute.FONT, arguments.font );
Local.iterator = Local.string.getIterator();
Local.measurer = createObject("java", "java.awt.font.LineBreakMeasurer");
Local.measurer = Local.measurer.init( Local.iterator,
arguments.graphics.getFontRenderContext()
);


//////////////////////////////////////////////////////////////
// Initialize coordinates, dimensions, etcetera
//////////////////////////////////////////////////////////////
Local.lines = [];
Local.posY = arguments.y;
Local.posX = arguments.x;
Local.maxWidth = 0;
Local.maxLineHeight = 0;
Local.heightPadding = 0;
Local.leftMargin = arguments.x;
Local.rightMargin = arguments.width + arguments.x;
Local.alignment = trim( arguments.align );
Local.LEFT_ALIGN = "left";
Local.RIGHT_ALIGN = "right";
Local.CENTER_ALIGN = "center";

while ( Local.measurer.getPosition() < Local.iterator.getEndIndex() ) {
// Get starting and ending character positions
Local.startAt = Local.measurer.getPosition();
Local.layout = Local.measurer.nextLayout( javacast("float", arguments.width) );
Local.endAt = Local.measurer.getPosition();
Local.bounds = Local.layout.getBounds();

// Calculate the y coordinate of this line
Local.lastPosY = Local.posY;
Local.posY = Local.posY + Local.layout.getAscent();
Local.lineHeight = Local.layout.getAscent() + Local.layout.getDescent() + Local.layout.getLeading();

// Deduct the top padding from first line of text
if ( arguments.ignoreMargin && (Local.startAt == 0) ) {

Local.boundY = Local.posY + Local.layout.getBounds().getY() - 1;
Local.topPadding = Local.boundY - Local.lastPosY;

Local.lineHeight -= Local.topPadding;
Local.posY -= Local.topPadding;

}


/////////////////////////////////////////////////////////////////////
// Calculate the x coordinate of this line based on alignment
/////////////////////////////////////////////////////////////////////
if ( Local.layout.isLeftToRight()) {

if ( Local.alignment == Local.RIGHT_ALIGN ) {
Local.posX = Local.rightMargin - Local.layout.getVisibleAdvance();
}
else if ( Local.alignment == Local.CENTER_ALIGN ) {
Local.posX = ( Local.leftMargin + Local.rightMargin - Local.layout.getVisibleAdvance()) / 2;
}
else {
// Otherwise, use left alignment
Local.posX = arguments.x;
}

}
else {

if ( Local.alignment == Local.LEFT_ALIGN ) {
Local.posX = arguments.x + Local.maxWidth - Local.layout.getAdvance();
}
else if ( Local.alignment == Local.CENTER_ALIGN ) {
Local.posX = ( Local.leftMargin + Local.rightMargin + Local.layout.getAdvance()) / 2 - Local.layout.getAdvance();
}
else {
// Otherwise, use right alignment
Local.posX = Local.leftMargin + ( Local.layout.getVisibleAdvance() - layout.getAdvance() );
}

}


// Save positioning details of current line of text
Local.lineHeight = Local.layout.getAscent() + Local.layout.getDescent() + Local.layout.getLeading();
Local.currentLine = { x = Local.posX,
y = Local.posY,
startAt = Local.startAt + 1,
endAt = Local.measurer.getPosition() - Local.startAt
};
arrayAppend( Local.lines, Local.currentLine );


// Draw the current line of text
if ( arguments.drawText ) {
Local.layout.draw( arguments.graphics, Local.posX, Local.posY );
}

// Track the maximum width of all lines
Local.currentWidth = Local.layout.getBounds().getWidth();
if ( Local.maxWidth < Local.currentWidth ) {
Local.maxWidth = Local.currentWidth;
}

// Calculate the ending y position
Local.posY += Local.layout.getDescent() + Local.layout.getLeading();
Local.isLastLine = (Local.measurer.getPosition() == Local.iterator.getEndIndex());

// Deduct bottom padding from the last line
if ( arguments.ignoreMargin && Local.isLastLine ) {
Local.finalY = Local.lastPosY + Local.layout.getAscent() + Local.bounds.getY() + Local.bounds.getHeight();
Local.bottomPadding = Local.posY - Local.finalY - 1;
Local.lineHeight -= Local.bottomPadding;
Local.posY -= Local.bottomPadding;
}

// Track the maximum line height
if ( Local.lineHeight > Local.maxLineHeight ) {
Local.maxLineHeight = Local.lineHeight;
}

}

/////////////////////////////////////////////////////////
// Save the final results
/////////////////////////////////////////////////////////
Local.results = { x = arguments.x,
y = arguments.y,
width = int( Local.maxWidth ),
height = int( Local.posY - arguments.y ),
lineHeight = Local.maxLineHeight,
floatWidth = Local.maxWidth,
floatHeight = Local.posY - arguments.y,
lines = Local.lines,
totalLines = arrayLen( Local.lines )
};

</cfscript>

<cfreturn Local.results>
</cffunction>

<cffunction name="CreateJavaFont" returntype="any" access="public" output="true"
hint="Creates a java.awt.Font object using the supplied text attributes. All attributes are optional.">

<cfargument name="font" type="string" required="false" hint="Font name (example: Arial)">
<cfargument name="style" type="string" required="false" default="plain" hint="Font style (Plain, Italic, Bold, BoldItalic)">
<cfargument name="size" type="numeric" required="false" hint="Font size in points">
<cfset var Local = {} >

<cfscript>
// Get the default font properties
Local.Font = createObject("java", "java.awt.Font");
Local.DefaultFont = Local.Font.init( javacast("null", "") );
Local.prop = { font = Local.DefaultFont.getName(),
style = Local.DefaultFont.getStyle(),
size = Local.DefaultFont.getSize()
};

// Extract the supplied font name
if ( structKeyExists(arguments, "font") ) {
Local.prop.font = arguments.font;
}

// Extract the supplied font size
if ( structKeyExists(arguments, "size") ) {
Local.prop.size = arguments.size;
}

// Extract and convert the supplied font style
if ( structKeyExists(arguments, "style") ) {

switch (arguments.style) {
case "bold":
Local.prop.style = Local.Font.BOLD;
break;
case "italic":
Local.prop.style = Local.Font.ITALIC;
break;
case "bolditalic":
Local.prop.style = BitOr( Local.Font.BOLD, Local.Font.ITALIC);
break;
case "plain":
Local.prop.style = Local.Font.PLAIN;
break;
default:
Local.prop.style = Local.Font.PLAIN;
break;
}

}

// Create a java font using the current properties
Local.javaFont = Local.Font.init( javacast("string", Local.prop.font),
javacast("int", Local.prop.style),
javacast("int", Local.prop.size)
);
</cfscript>

<cfreturn Local.javaFont>
</cffunction>

<cffunction name="EstimateFontSizeRange" returntype="struct" output="true"
hint="Estimate minimum/maximum font size for wrapping text within a given area" >

<cfargument name="graphics" type="any" required="true" hint="Underlying graphics object from BufferedImage">
<cfargument name="font" type="any" required="true" hint="Java Font object">
<cfargument name="text" type="string" required="true" hint="The text to draw on the image. Minimum length is 1 character.">
<cfargument name="x" type="numeric" required="true" hint="The starting x coordinate. Minimum value is 0.">
<cfargument name="y" type="numeric" required="true" hint="The starting y coordinate. Minimum value is 0.">
<cfargument name="width" type="numeric" required="true" hint="Width of the desired wrap area. Minimum value is 1.">
<cfargument name="height" type="numeric" required="true" hint="Height of the desired wrap area. Minimum value is 1.">
<cfargument name="ignoreMargin" type="boolean" default="false" hint="If true, ignore top and bottom padding when measuring the text area">

<cfset var Local = {} >

<cfscript>
Local.retryCounter = 0;
Local.maxRetry = 1000;


// Calculate the dimensions of the text at the current font size
Local.dimen = WrapImageText(
graphics = arguments.graphics,
text = arguments.text,
font = arguments.font,
x = arguments.x,
y = arguments.y,
width = arguments.width
);


/////////////////////////////////////////////////////////////////////////////
// Determine if text dimensions fit within the desired area
/////////////////////////////////////////////////////////////////////////////
Local.sizeCompare = RectCompare( Local.dimen.width, Local.dimen.height, arguments.width, arguments.height );
Local.fontSize = arguments.font.getSize();
Local.sizeIncrement = 10;
Local.estimate = { low = Local.fontSize,
high = Local.fontSize,
compare = Local.sizeCompare,
iterations = 0
};

// If the text area needs resizing ...
if ( Local.sizeCompare != 0 ) {

// Area is too large
if ( Local.sizeCompare == 1) {
Local.offsetFactor = -1;
Local.estimate.low = 1;
Local.estimate.high = Local.fontSize;

}
// Area is too small
else {
Local.offsetFactor = 1;
Local.estimate.low = Local.fontSize;
Local.estimate.high = 1000;
}

Local.font = arguments.font.deriveFont( javacast("float", Local.fontSize) );
Local.offsetValue = Local.sizeIncrement * Local.offsetFactor;
Local.keepScaling = true;

while ( Local.keepScaling ) {

// Calculate text dimensions at the next font size
Local.fontSize = Local.fontSize + Local.offsetValue;
Local.font = Local.font.deriveFont( javacast("float", Local.fontSize) );
Local.dimen = WrapImageText( graphics = arguments.graphics,
text = arguments.text,
font = Local.font,
x = arguments.x,
y = arguments.y,
width = arguments.width,
ignoreMargin = arguments.ignoreMargin
);

Local.isInsideWrapArea = Local.dimen.height LTE arguments.height;

//
// ENLARGE
//
if ( Local.sizeCompare == -1 ) {

// Keep searching for a larger font size ...
if ( Local.isInsideWrapArea ) {
Local.estimate.low = Local.fontSize;
}
// We have reached the upper limit. Store final value and exit
else {
Local.estimate.high = Local.fontSize;
Local.keepScaling = false;
}
}
//
// REDUCE
//
else {

// We have reached the lower limit. Store final value and exit
if ( Local.isInsideWrapArea ) {
Local.estimate.low = Local.fontSize;
Local.keepScaling = false;
}
// Keep searching for a smaller font size ...
else {
Local.estimate.high = ABS( Local.fontSize );

}
}

Local.retryCounter += 1;
if ( Local.fontSize <= 0 || ( Local.retryCounter >= Local.maxRetry) ) {
Local.estimate.low = 1;
Local.keepScaling = false;
}
}

Local.estimate.iterations = Local.retryCounter;
}
</cfscript>

<cfreturn Local.estimate>
</cffunction>

<cffunction name="RectCompare" returntype="boolean" access="public" output="false">
<cfargument name="width1" type="numeric" required="true">
<cfargument name="height1" type="numeric" required="true">
<cfargument name="width2" type="numeric" required="true">
<cfargument name="height2" type="numeric" required="true">
<cfset var comp = 0>

<cfscript>
// Rectangles are equal
if ( (arguments.width1 == arguments.width2) && (arguments.height1 == arguments.height2) ) {
comp = 0;
}
// Width and height of first rectangle is smaller than second rectangle
else if ( (arguments.width1 <= arguments.width2) && (arguments.height1 <= arguments.height2) ) {
comp = -1;
}
// Width OR height of first rectangle is larger than second rectangle
else {
comp = 1;
}
</cfscript>

<cfreturn comp>
</cffunction>

</cfcomponent>



Test Form

<cfsavecontent variable="variables.sampleText">
Zathras is used to being beast of burden for others. A sad life, and probably a sad death, but at least there is symmetry
|If you're going through hell, keep going.
|December is the toughest month of the year. Others are July, January, September, Aprll, November, May, March, June, October, August, and February.
|Whatever you do, you need courage. Whatever course you decide upon, there is always someone to tell you that you are wrong. There are always difficulties arising that tempt you to believe your critics are right. To map out a course of action and follow it to an end requires some of the same courage that a soldier needs. Peace has its victories, but it takes brave men and women to win them.
|The harder the conflict, the more glorious the triumph. What we obtain too cheap, we esteem too lightly; it is dearness only that gives everything its value. I love the man that can smile in trouble, that can gather strength from distress and grow brave by reflection. 'Tis the business of little minds to shrink; but he whose heart is firm, and whose conscience approves his conduct, will pursue his principles unto death.
|If I were personally to define religion I would say that it is a bandage that man has invented to protect a soul made bloody by circumstance
|All good is hard. All evil is easy. Dying, losing, cheating, and mediocrity is easy. Stay away from easy.
|For one human being to love another: that is perhaps the most difficult of our tasks; the ultimate, the last test and proof, the work for which all other work is but preparation.
|What a piece of work is a man, how noble in reason, how infinite in faculties, in form and moving how express and
admirable, in action how like an angel, in apprehension how like
a god! the beauty of the world, the paragon of animals—and yet,
to me, what is this quintessence of dust? Man delights not me—
nor woman neither, though by your smiling you seem to say so
|Can anybody remember when the times were not hard and money not scarce?
|Neither a borrower nor a lender be; For loan oft loses both itself and friend, and borrowing dulls the edge of husbandry
|It was one of those perfect English autumnal days which occur more frequently in memory than in life.
|I dote on his very absence
|ColdFusion 4.5: This new tag adds SQL bind parameter support to CFQUERY. The tag is coded inline in the SQL statement, so it is very easy to use. It was developed to improve performance, maintenance, and security of data queries, specifically:

* Server-side caching for Oracle is enhanced and work toward active SQL statement caching by ColdFusion has begun.
* Long text fields can be updated from a SQL statement. In previous releases, the developer was required to use a form and the CFINSERT and CFUPDATE tags to do this.
* A malicious user can no longer attach multiple SQL statements to a SQL statement substitution variable.

If the CFQUERY data source does not support bind parameters, then ColdFusion will execute the SQL statement with the substituted parameters for CFINSERT, CFUPDATE and CFGRIDUPDATE as in previous releases. For example, the Sybase native driver does not support query parameters, so the parameters are substituted into the SQL string. As a result, a ColdFusion developer wishing to take advantage of SQL bind parameters does not have to write one set of statements for the data sources that support it and another set for data sources that don't.

ColdFusion developers should be aware of the security implications of this feature and use best security practices. In the case of a database that does not support query parameters, the validated parameter value is substituted back into the SQL string - character data is enclosed in single quotes by ColdFusion when it is substituted back into the string. Numeric and date data types are validated to ensure that the data value can be converted to numeric data or a date respectively. Character and other data types are checked to make sure the value does not exceed the MAXLENGTH for the parameter, if it is specified.

At present, the ColdFusion Sybase11 driver does not support SQL bind parameters because the Sybase client libraries do not provide that support. The OLEDB driver dynamically determines support. The ColdFusion ODBC, DB2, Informix, Oracle 7 and Oracle 8 drivers support SQL bind parameters.

The CFSQLTYPE attribute is needed for the CFDATAACCESS bind calls. The data conversions are actually done by the database client software, which doesn't necessarily know the target data type. The CFINSERT and CFUPDATE tags determine this information by accessing the database catalog for the target table. This is not an option for CFQUERY since it has no information concerning what the target tables or columns are. The database APIs for ODBC and Oracle support discovery of the param types, however, for ODBC this a level 2 feature and not supported by many ODBC drivers. The param type discovery is expensive. Given the expense of the runtime determination of the param type and the fact that it would limit the param support to fewer ODBC we should use the CFSQLTYPE param in the tag.

CFSQLTYPE also enables data validation. Developers can do less validation coding since the CFQUERYPARAM values are validated. Three optional parameters - MAXLENGTH, Scale, and Null are available for decimal data types. If the data source does not support bind parameters the the ColdFusion SQL executor uses CFSQLTYPE to determine if it needs to enclose the substituted value in single quotes.
</cfsavecontent>


<cfparam name="form.minIncrement" default="1">
<cfparam name="form.imageWidth" default="500">
<cfparam name="form.imageHeight" default="300">
<cfparam name="form.x" default="25">
<cfparam name="form.y" default="25">
<cfparam name="form.wrapWidth" default="300">
<cfparam name="form.wrapHeight" default="150">
<cfparam name="form.fontName" default="">
<cfparam name="form.fontSize" default="14">
<cfparam name="form.fontStyle" default="">
<cfparam name="form.increment" default="1">
<cfparam name="form.textAlign" default="left">
<cfparam name="form.textColor" default="">
<cfparam name="form.text" default="">
<cfparam name="form.autoFit" default="1">
<cfparam name="form.useAntialising" default="1">
<cfparam name="form.ignoreMargin" default="1">

<cfset variables.environ = createObject("java", "java.awt.GraphicsEnvironment").getLocalGraphicsEnvironment()>
<cfset variables.fontNames = variables.environ.getAvailableFontFamilyNames()>
<cfset variables.incrementArray = listToArray("0.25,0.5,1.0")>
<cfset variables.textArray = listToArray( variables.sampleText, "|")>
<cfset variables.fontStyles = listToArray("Default,Plain,Italic,Bold,BoldItalic")>
<cfset variables.textAlignments = listToArray("Left,Center,Right")>
<cfset variables.hexArray = listToArray("00,33,66,99,CC,FF")>

<h1>ImageTextUtil.cfc - Test Form</h1>

<h3>Options:</h3>
<cfoutput>
<form action="#CGI.SCRIPT_NAME#" method="post">
<table width="90%">
<tr><td width="20%">Image Width</td>
<td width="30%"><input type="text" name="imageWidth" value="#form.imageWidth#"></td>
<td>Font name</td>
<td><select name="fontName">
<option value="">Default</option>
<cfloop array="#variables.fontNames#" index="variables.i">
<option value="#i#" <cfif form.fontName is variables.i>selected</cfif>>#i#</option>
</cfloop>
</select>
</td>
</tr>
<tr><td>Image Height</td>
<td><input type="text" name="imageHeight" value="#form.imageHeight#"></td>
<td>Font size</td>
<td><input type="text" name="fontSize" value="#form.fontSize#"></td>
</tr>
<tr><td>x coordinate</td>
<td><input type="text" name="x" value="#form.x#"></td>
<td>Font Style</td>
<td><select name="fontStyle">
<cfloop array="#variables.fontStyles#" index="variables.i">
<option value="#variables.i#" <cfif form.fontStyle is variables.i>selected</cfif>>#variables.i#</option>
</cfloop>
</select>
</td>
</tr>
<tr><td>y coordinate</td>
<td><input type="text" name="y" value="#form.y#"></td>
<td width="20%">Text Align</td>
<td width="30%"><select name="textAlign">
<cfloop array="#variables.textAlignments#" index="variables.i">
<option value="#variables.i#" <cfif form.textAlign is variables.i>selected</cfif>>#variables.i#</option>
</cfloop>
</select>
</td>
</tr>
<tr><td>Wrap width</td>
<td><input type="text" name="wrapWidth" value="#form.wrapWidth#"></td>
<td>Text Color</td>
<td>
<select name="textColor">
<cfloop array="#variables.hexArray#" index="variables.r">
<cfloop array="#hexArray#" index="variables.b">
<cfloop array="#hexArray#" index="variables.g">
<cfset variables.c = "##"& variables.r & variables.b & variables.g>
<option value="#variables.c#" style="background-color: #variables.c#;" <cfif form.textColor EQ variables.c>selected</cfif>>#variables.c#</option>
</cfloop>
</cfloop>
</cfloop>
</select>

</td>
</tr>
<tr><td>Wrap height</td>
<td><input type="text" name="wrapHeight" value="#form.wrapHeight#"></td>
<td>Display text</td>
<td><select name="text" style="width: 300;">
<cfif len(form.text)>
<option value="#form.text#">[#len(form.text)# chars] #form.text#</option>
</cfif>
<cfloop array="#variables.textArray#" index="variables.i">
<option value="#variables.i#">[#len(variables.i)# chars] #variables.i#</option>
</cfloop>
</select>
</td>
</tr>
<tr><td>Minimum increment</td>
<td><select name="minIncrement">
<cfloop array="#variables.incrementArray#" index="variables.i">
<option value="#variables.i#" <cfif form.minIncrement is variables.i>selected</cfif>>#variables.i#</option>
</cfloop>
</select>
</td>
<td>Scale Text</td>
<td><input type="radio" name="autoFit" value="1" <cfif form.autoFit>checked</cfif>>Yes
<input type="radio" name="autoFit" value="0" <cfif not form.autoFit>checked</cfif>>No
</td>
</tr>
<tr><td>Ignore margins <span class="experiment">**</span></td>
<td><input type="radio" name="ignoreMargin" value="1" <cfif form.ignoreMargin>checked</cfif>> Yes
<input type="radio" name="ignoreMargin" value="0" <cfif not form.ignoreMargin>checked</cfif>> No
</td>
<td>Use Antialising</td>
<td><input type="radio" name="useAntialising" value="1" <cfif form.useAntialising>checked</cfif>> Yes
<input type="radio" name="useAntialising" value="0" <cfif not form.useAntialising>checked</cfif>> No
</td>
<tr><td colspan="3"><span class="experiment">** experimental feature</span></td>
<td><input type="submit" value="Create Image" name="testThis"></td>
</tr>
</table>
</form>
</cfoutput>

<cfif structKeyExists(form, "testThis")>

<!---
Parse the selected font attributes
--->
<cfset fontAttributes = {} >
<cfif form.fontName is not "Default">
<cfset fontAttributes.font = form.fontName>
</cfif>
<cfif isNumeric(form.fontSize)>
<cfset fontAttributes.size = form.fontSize>
</cfif>
<cfif form.fontStyle is not "Default">
<cfset fontAttributes.style = form.fontStyle>
</cfif>


<!---
Create a new image object
--->
<cfset img = ImageNew( "", form.imageWidth, form.imageHeight, "rgb", "##cccccc" )>

<!---
Gather the selected properties into a single structure
--->
<cfset prop = { image = img,
text = form.text,
x = form.x,
y = form.y,
width = form.wrapWidth,
height = form.wrapHeight,
fontAttributes = fontAttributes,
textColor = form.textColor,
useAntialising = form.useAntialising,
ignoreMargin = form.ignoreMargin,
align = form.textAlign,
minIncrement = form.minIncrement,
autoFit = yesNoFormat(form.autoFit)
} >

<!---
Draw the wrapped text on the image
--->
<cfset util = createObject("component", "dev.images.ImageTextUtil")>
<cfset dimen = util.ImageDrawWrappedText( argumentCollection = prop )>

<!---
Draw an outline of the desired wrap area
--->
<cfset ImageSetDrawingColor( img, "red" )>
<cfset imageDrawRect(img, form.x, form.y, form.wrapWidth, form.wrapHeight)>

<h3>Results:</h3>
<!---
Display the final settings
--->
<cfoutput>
iterations = #dimen.iterations#<br>
font size = #dimen.font.size#<br>
width = #dimen.width#<br>
height = #dimen.height#<br>
</cfoutput>

<!---
Display the wrapped text image
--->
<cfimage action="writeToBrowser" source="#img#">
</cfif>
<hr>

0 comments:

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep