OT: This is Unicode ?
While visiting the unicode.org site, I came across this unexpected title from 1992. (Yes, it really is about unicode. Who says unicode is a dry topic ;).
Kiss Your ASCII Goodbye (John Dvorak, PC Magazine/1992)
.. in the middle of the night
While visiting the unicode.org site, I came across this unexpected title from 1992. (Yes, it really is about unicode. Who says unicode is a dry topic ;).
So I was comparing two windows implementations of tail, and came across something unexpected. While I was checking one of my tests, I noticed the results contained less lines than I requested. The base files definitely contained more than enough lines. So I knew that was not problem. After digging deeper, I discovered the lines returned were not even from the end of the file! They were from the middle.
Well, come to find out the file I was testing contained some funky characters. One of them being ascii character 26. Apparently the Windows Resource Kit version of tail.exe interprets ascii 26 as some sort of EOF (end of file) marker. Consequently tail.exe uses that marker as its starting point. Well, naturally that skews the results because that position is not necessarily the actual end of the file.
To see it for yourself, create a simple file containing 50 lines, with the ascii character 26 inserted every ten (10) lines.
<cfset specialChar = chr(26) />
<cfset newLine = chr(10) />
<cfsavecontent variable="data">
<cfoutput>
<cfloop from="1" to="50" index="x">Log file line #x# <cfif not x mod 10>#specialChar#</cfif>#newLine#</cfloop>
</cfoutput>
</cfsavecontent>
<cfset filePath = ExpandPath("./testLogFile.log") />
<cfset FileWrite( filePath, trim(data) ) />
<cfset grabLines = 20 />
<cfexecute name='"c:\program files\Windows Resource Kits\tools\tail.exe"'
arguments='-#grabLines# #filePath#'
variable='output'
timeout='20' />
<!--- convert lines to array --->
<cfset linesFound = listToArray(output, chr(10), true) />
<cfset actualLines = listToArray( FileRead( filePath ), chr(10), true) />
<cfoutput>
<p><strong>Log File:</strong> #filePath# </p>
<p><strong>Requested Lines:</strong> #grabLines# </p>
<p><strong>Lines Found:</strong> #arrayLen(linesFound)#</p>
<p><strong>Actual Lines:</strong> #arrayLen(actualLines)#</p>
</cfoutput>
Does anyone know of any good resources (online, books, etcetera) on JAI? Frankly the sun docs leave a lot to be desired. If anyone can recommend a good book on the topic it would be greatly appreciated.
...Read MoreA while back I saw a thread on the adobe forums about an issue with Blowfish encryption. The issue being the results from ColdFusion's encrypt function seemed to differ from other languages. Hardly uncommon given the "delicate" nature of encryption. What grabbed my attention was that the results from ColdFusion and Java were also different. Not what I would have expected.
Take the simple example below. It sure seems like the same text and key are used for both CF and Java encryption. Yet the results are different. However, if you compare the java results with those from the online tools (mentioned in the adobe thread) they are all the same. The odd one out here is ColdFusion.
Note: These examples were chosen for simplicity. They are NOT a good example of strong encryption (usage of ECB, etcetera ...)
ColdFusion
<cfset myText = "guess this!1 2 3" /> <cfset myKey = generateSecretKey("Blowfish", 32) /> <cfset cfEncrypted = Encrypt(myText, myKey, "Blowfish/ECB/NoPadding", "Hex") /> <!--- results (usingtags because the blogger eats line breaks!) ---> <strong>ColdFusion:</strong><hr /> <cfoutput> <p><strong>MyText</strong> : #myText#</p> <p><strong>MyKey</strong> : #myKey#</p> <p><strong>CF Encrypted</strong> : #cfEncrypted#</p> </cfoutput>
<!--- generate a secret key ---> <cfset SecretKeySpec = createObject("java", "javax.crypto.spec.SecretKeySpec") /> <cfset myKeyBytes = CharsetDecode(myKey, "utf8") /> <cfset keySpec = SecretKeySpec.init(myKeyBytes, "Blowfish") /> <!--- get a cipher instance for encrypting ---> <cfset Cipher = createObject("java", "javax.crypto.Cipher") /> <cfset encryptor = Cipher.getInstance("Blowfish/ECB/NoPadding") /> <cfset encryptor.init(Cipher.ENCRYPT_MODE, keySpec) /> <!--- do the encryption ---> <cfset myTextBytes = CharsetDecode(myText, "utf8") /> <cfset javaEncrypted = encryptor.doFinal(myTextBytes) /> <!--- results ---> <cfoutput> <p><strong>Java Encrypted</strong> : #BinaryEncode(javaEncrypted, "hex")# </p> </cfoutput>
<cfset myText = "guess this!1 2 3" /> <cfset myKey = generateSecretKey("Blowfish", 32) /> <cfset magicKey = BinaryEncode(CharsetDecode(myKey, "utf8"), "base64") /> <cfset cfEncrypted = Encrypt(myText, magicKey, "Blowfish/ECB/NoPadding", "Hex") />
<!--- ENCRYPT WITH COLDFUSION ---> <cfset algorithm = "Blowfish/CBC/PKCS5Padding"> <cfset myText = "can you guess this?" /> <cfset myKey = generateSecretKey("Blowfish", 32) > <cfset myIV = CharsetDecode("12345678", "utf8")> <cfset cfEncrypted = Encrypt(myText, myKey, algorithm, "base64", myIV) /> <!--- results (using <p> tags because the blogger eats line breaks!) ---> <strong>ColdFusion (Encrypted):</strong><hr /> <cfoutput> <p><strong>MyText</strong> : #myText#</p> <p><strong>MyKey</strong> : #myKey#</p> <p><strong>MyIV</strong> : <cfdump var="#myIV#"></p> <p><strong>CF Encrypted</strong> : #cfEncrypted#</p> </cfoutput> </p> <!--- ENCRYPT WITH JAVA ---> <cfset SecretKeySpec = createObject("java", "javax.crypto.spec.SecretKeySpec") /> <cfset myKeyBytes = BinaryDecode(myKey, "base64") /> <cfset keySpec = SecretKeySpec.init(myKeyBytes, "Blowfish") /> <cfset ivSpec = createObject("java", "javax.crypto.spec.IvParameterSpec").init( myIV )> <cfset Cipher = createObject("java", "javax.crypto.Cipher") /> <cfset encryptor = Cipher.getInstance( algorithm ) /> <cfset encryptor.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec) /> <cfset myTextBytes = CharsetDecode(myText, "utf8") /> <!--- convert encrypted byte array to base64 string ---> <cfset javaEncrypted = BinaryEncode( encryptor.doFinal(myTextBytes), "base64") /> <!--- results (using <p> tags because the blogger eats line breaks!) ---> <strong>Java Encrypted:</strong><hr /> <cfoutput> <p><strong>myTextBytes (utf8)</strong> : #CharsetEncode(myTextBytes, "utf8")#</p> <p><strong>keySpec</strong> : #BinaryEncode(keySpec.getEncoded(), "base64")#</p> <p><strong>ivSpec (raw)</strong> : <cfdump var="#ivSpec.getIV()#"></p> <p><strong>ivSpec (utf8)</strong> : #CharsetEncode(ivSpec.getIV(), "utf8")#</p> <p><strong>Java Encrypted</strong> : #javaEncrypted# </p> </cfoutput>
<!--- DECRYPT *COLDFUSION* RESULT WITH JAVA ---> <cfset SecretKeySpec = createObject("java", "javax.crypto.spec.SecretKeySpec") /> <cfset myKeyBytes = BinaryDecode(myKey, "base64") /> <cfset keySpec = SecretKeySpec.init(myKeyBytes, "Blowfish") /> <cfset ivSpec = createObject("java", "javax.crypto.spec.IvParameterSpec").init( myIV )> <cfset Cipher = createObject("java", "javax.crypto.Cipher") /> <cfset decryptor = Cipher.getInstance( algorithm ) /> <cfset decryptor.init(Cipher.DECRYPT_MODE, keySpec, ivSpec) /> <!--- convert our encrypted (base64 encoded) string to a byte array ---> <cfset encryptedBytes = BinaryDecode( cfEncrypted , "base64") /> <!--- the decrypted bytes will be in UTF8 ---> <cfset javaDecrypted = CharsetEncode( decryptor.doFinal( encryptedBytes ), "utf8") /> <cfoutput> <p><strong>Java Decrypted</strong> : #javaDecrypted# </p> </cfoutput> <!--- DECRYPT *JAVA* RESULT WITH COLDFUSION ---> <!--- decrypt (base64 encoded) encrypted string ---> <cfset cfDecrypted = Decrypt(javaEncrypted, myKey, algorithm, "base64", myIV) /> <cfoutput> <p><strong>ColdFusion Decrypted</strong> : #cfDecrypted# </p> </cfoutput>
I recently took a look at the ZXing Project which is described as "an open-source, multi-format 1D/2D barcode image processing library implemented in Java.". While its primary focus is mobile phones, I decided to look at the javase component of the project. ZXing also comes with a servlet. But I first wanted to see how the lower level components worked.
Setup
Not seeing any pre-compiled jars, I downloaded the latest sources (ZXing-1.4.zip). After loading it into Eclipse, and poked around a bit to figure out how to decode barcode images of different types. Finally, I used the pre-made ant files to create the two jars I needed: core.jar and javase.jar.
Decoding barcode images
While the project has some decent documentation, the developer notes seem to be outdated in a few places. But examining some of the javase classes turned up a convenience class called MultiFormatReader. From what I could tell it provides a simple way to decode barcodes when you may not know the type ahead of time. Obviously if you know the barcode type ahead of time, it is more efficient to use the specific reader for that type. But since I wanted to test all supported types the MultiFormatReader was perfect for my needs.
Now before I could test ZXing, I needed some barcode images. So I used CFBarbecue.cfc to generate a sample of all of the barcodes supported by the Barbecue library. Now ZXing does not support all of them. But there is a good intersection between the two. Once I had my images, I created an instance of MultiFormatReader, using the ever-handy JavaLoader.cfc. I also passed the reader some hints to increase accuracy.
Note: This code is for demonstration purposes only. Normally, the javaLoader should be stored in the server scope to avoid memory leaks.
<cfset paths = [] /> <cfset arrayAppend(paths, expandPath('zxing-1.4/core/core.jar')) /> <cfset arrayAppend(paths, expandPath('zxing-1.4/javase/javase.jar')) /> <cfset loader = createObject("component", "javaloader.JavaLoader").init( paths ) /> <!--- Create hints for the reader. TRY_HARDER increases accuracy ---> <cfset DecodeHintType = loader.create("com.google.zxing.DecodeHintType") /> <cfset hints = loader.create("java.util.Hashtable").init() /> <cfset hints.put(DecodeHintType.TRY_HARDER, javacast("boolean", true)) /> <!--- Since we will be reading multiple images, create one reader and reuse it ---> <cfset Reader = loader.create("com.google.zxing.MultiFormatReader").init() /> <cfset Reader.setHints(hints) />
<!--- read all barcode images in my subdirectory ---> <cfdirectory action="list" directory="#ExpandPath('images')#" filter="*.gif" name="barcodeImages" /> <!--- Try and decode each barcode with z-xing. Note: Not all codes are supported by z-xing!!! ---> <cfoutput query="barcodeImages"> <cfset imagePath = Directory &"/"& name /> <cftry> <!--- display the original image ---> <strong>Barcode Image</strong> #name# <br /> <cfimage action="writeToBrowser" source="#imagePath#"><br /> <!--- extract the BufferedImage of the current barcode ---> <cfset img = ImageGetBufferedImage(ImageRead(imagePath)) /> <!--- prepare the image for decoding ---> <cfset source = loader.create("com.google.zxing.client.j2se.BufferedImageLuminanceSource").init(img) /> <cfset binarizer = loader.create("com.google.zxing.common.GlobalHistogramBinarizer").init(source) /> <cfset bitmap = loader.create("com.google.zxing.BinaryBitmap").init(binarizer) /> <!--- This method reuses the existing settings for decoding multiple images ---> <cfset decodedResult = Reader.decodeWithState(bitmap) /> <!--- display the results ---> <strong>Decoded Text</strong> #decodedResult.getText()# <br /> <strong>Decoded Format</strong> #decodedResult.getBarcodeFormat()# <br /> <cfcatch> Not supported or unable to decode: #cfcatch.type# #cfcatch.message# </cfcatch> </cftry> <br /><br /><hr /> </cfoutput>
<cfset imagePath = "c:\barCode_loremIpsum_idAutomation.gif" /> <cfset DecodeHintType = loader.create("com.google.zxing.DecodeHintType") /> <cfset hints = loader.create("java.util.Hashtable").init() /> <cfset hints.put(DecodeHintType.PURE_BARCODE, javacast("boolean", true)) /> <!--- prepare the image for decoding ---> <cfset img = ImageGetBufferedImage(ImageNew(imagePath)) /> <cfset source = loader.create("com.google.zxing.client.j2se.BufferedImageLuminanceSource").init(img) /> <cfset binarizer = loader.create("com.google.zxing.common.GlobalHistogramBinarizer").init(source) /> <cfset bitmap = loader.create("com.google.zxing.BinaryBitmap").init(binarizer) /> <!--- attempt to decode the image ---> <cfset Reader = loader.create("com.google.zxing.datamatrix.DataMatrixReader") /> <cfset decodedResult = Reader.decode( bitmap, hints ) /> <cfoutput> <!--- display the original image ---> <strong>Barcode Image</strong> #imagePath# <br /> <cfimage action="writeToBrowser" source="#imagePath#"><br /> <!--- display the results ---> <strong>Decoded Result</strong><br /> #decodedResult.getText()# <hr /> </cfoutput>
C:\test\zxing> java -cp c:\test\zxing\core.jar;c:\test\zxing\javase.jar com.google.zxing.client.j2se.GUIRunner
C:\test\zxing> java -cp c:\test\zxing\core.jar;c:\test\zxing\javase.jar com.google.zxing.client.j2se.CommandLineRunner c:/test/zxing/barcodeimages --try_harder
I have long been a fan of Firefox. But somewhere in between versions 2 and 3.5 it started behaving like a black-hole of resources, sucking up more and more memory, growing increasing sluggish. Not to mention taking forever to start up. So fickle user that I am, I have been trying out other browsers while I give Firefox one last chance before giving it the boot.
Of course one of the browsers I am (re-)trying is Chrome. I did use it briefly when it first came out. But quickly gravitated back to my beloved Firefox, with all of its wonderful extensions and flexible configuration. But I will admit Chrome is extremely fast. No news there. What was a nice surprise was its mini-Task-Manager (Shft-Esc) and the "Stats for Geeks" breakdown.
Meanwhile, I did some file shuffling and installed a completely fresh copy of FireFox 3.5.5. Just to see if I could get a sense of whether the true culprit was FireFox itself, a specific setting or one of my plugins. I tried to start with as little functionality and minimal settings as possible. Not all of them are relevant. But in the interest of full disclosure, here is a run down of the changes I made to the default settings:
Plugins:
- *Disabled* all of the plugins detected by default (EXCEPT Shockwave Flash 10.0.32.18)
I always wondered why my plugin list displayed certain entries I never added explicitly. It turns out it is due to Firefox's automatic scan for common plugins: http://kb.mozillazine.org/Plugin_scanning#Related_preferences
Extensions:
- *Added* Echofon (aka TwitterFox)
Privacy:
- *Changed* Remember browsing history to (at least 30 days)
- *Disabled* Remember search and form history
- *Enabled* Clear history when Firefox closes: everything
Cache:
- *Changed* Cache settings to use up to 30MB
Reason: Sometime after upgrading to FF3+, I noticed the autosuggest feature in the location bar had changed. It got progressively slower over time. So I am experimenting with a lower history threshold to see how it performs (before taking the extreme step of disabling it entirely).
Content:
- *Disabled* Java (No point enabling it since I disabled the plugin)
Startup:
- *Changed* Show a blank page
Update:
- *Changed* When updates to Firefox are found: (Ask me what I want to do)
So far Firefox is doing a lot better. Definitely faster, though maybe not quite as fast as Chrome. At one point I had 15 tabs open in Firefox (only one with Flash Content). Firefox peaked at around 160-180M memory usage in the windows Task Manager and still remained very responsive.
Now to start adding back a plugin at a time, and wait for ugly things to happen. Though I have to say I am enjoying reading some of the more creative ways people express their displeasure with the latest versions of Firefox.
While experimenting with the Barbecue library last week, I put together a cfc for my testing. (You knew that was coming. It is the old developer story: read, test, build something, re-test). Anyway, I decided to post it for anyone else out there experimenting with Barbecue. The cfc can generate any of the 25+ barcodes Barbecue handles, with a full range of settings. (Rather than repeat the same code 25+ times, the cfc uses reflection)
Keep in mind the Barbecue library also includes servlet, which is extremely easy to use. Since I was using the built-in webserver at the time, the greatest challenge for me was figuring out how to add a servlet. Not surprisingly I did not find much in the way of documentation. But once I finally got it working, the servlet was a snap to use. Just a simple call to a url:
http://127.0.0.1:8500/barbecue/?data=12345&type=PDF417
Download CFBarbecue.cfc (See the download widget on the right menu)
Hardly a week goes by without seeing some forum post that either uses or recommends my pet peeve: the erroneous SELECT MAX(id) query. Mind you this is across the board, not just in CF. For some bizarre reason, this faulty technique never seems to die. And it REALLY should. There are so many correct alternatives, I cannot understand why anyone would still use it for new code. If you are even considering it, might I inject a note of sanity and recommend some excellent reading material on Adrian J. Moreno's blog:
I was doing some debugging and came across the nifty java.sql.DatbaseMetaData class. It is an interface implemented by the various database drivers and it provides you with a ton of information about both your database and driver capabilities. While you can glean some of the information from <cfdbinfo>, DatbaseMetaData tells you so much more.
To use it, you need to open a database connection first. If you have full server access, the simplest method is using the internal ServiceFactory. But you could just as easily open your own jdbc connection. Then call one of the 100+ methods to discover things you may have always wondered about. For example:
<cfscript>As there are so many functions, I used a bit of reflection to quickly dump all of the simple properties into a structure. (See the full code at the end of the entry). It was interesting to see how the metadata differed across drivers. Both in what information was exposed and the results of the core methods. Notice the sql keywords differ when using the JTDS driver and the built-in CF8 driver.
myDSN = "MSSQL_2005";
// open a connection to the datasource
factory = createObject("java", "coldfusion.server.ServiceFactory");
datasource = factory.getDataSourceService().getDataSource( myDSN );
connection = datasource.getConnection();
// extract the database metadata
metadata = connection.getMetaData();
results = {};
// Display version information
results["getJDBCMajorVersion()"] = metadata.getJDBCMajorVersion();
results["getJDBCMinorVersion()"] = metadata.getJDBCMinorVersion();
results["getDriverMinorVersion()"] = metadata.getDriverMinorVersion();
results["getDriverName()"] = metadata.getDriverName();
results["getDriverVersion()"] = metadata.getDriverVersion();
results["getDatabaseMajorVersion()"] = metadata.getDatabaseMajorVersion();
results["getDatabaseMinorVersion()"] = metadata.getDatabaseMinorVersion();
results["getDatabaseProductName()"] = metadata.getDatabaseProductName();
results["getDatabaseProductVersion()"] = metadata.getDatabaseProductVersion();
// cleanup
connection.close();
</cfscript>
<cfdump var="#results#" label="[#myDSN#] java.sql.DatbaseMetaData" />
...Read More
<cfscript>
myDSN = "MSSQL_2005";
// open a connection to the datasource
factory = createObject("java", "coldfusion.server.ServiceFactory");
datasource = factory.getDataSourceService().getDataSource( myDSN );
connection = datasource.getConnection();
// extract the database metadata
connMetadata = connection.getMetaData();
// get all methods in the metadata class
allMethods = connMetadata.getClass().getDeclaredMethods();
Modifier = createObject("java", "java.lang.reflect.Modifier");
results = {};
// Execute all simple methods and store the results
for(x=1; x <= arrayLen(allMethods); x++)
{
method = allMethods[x];
// Get the parameters for the current method
parameters = method.getParameterTypes();
// If this is a simple method, requiring no extra information ..
if (Modifier.isPublic(method.getModifiers()))
{
if (not arrayLen(parameters) AND method.getName() neq "getConnection")
{
try
{
// run the current method
data = method.invoke( connMetadata, parameters );
// convert resultsets to a query object first
if ( isInstanceOf(data, "java.sql.ResultSet")) {
data = createObject("java", "coldfusion.sql.QueryTable").init(data);
}
results[method.getName()] = data;
}
catch(java.lang.Exception e)
{
results[method.getName()] = e.message;
}
}
}
}
connection.close() ;
</cfscript>
<cfdump var="#results#" label="connection.getMetaData() " />
While reviewing some MS SQL 2005 metadata, I stumbled across something I never noticed before. It still supports the old ODBC "{fn}" functions. With the exception of CurDate and CurTime, there is probably not much need for them. (Nor do I find the syntax particularly appealing ...). But still, it was surprising to find they do still work. So there is my MS SQL oddity for the day.
<cfquery name="q" datasource="MSSQL_2005">
SELECT
{fn CurDate()} AS [CurrentDate],
{fn CurTime()} AS [CurrentTime],
{fn DayName( getDate() )} AS [DayName],
{fn DayOfMonth( getDate() )} AS [DayOfMonth],
{fn DayOfWeek (getDate() )} AS [DayOfWeek],
{fn DayOfYear(getDate() )} AS [DayOfYear],
{fn Extract(Day From getDate())} AS [ExtractDay],
{fn Hour(getDate() )} AS [Hour],
{fn Minute(getDate() )} AS [Minute],
{fn Month(getDate() )} AS [Month],
{fn MonthName(getDate() )} AS [MonthName],
{fn Now()} AS [Now],
{fn Quarter(getDate() )} AS [Quarter],
{fn Second(getDate() )} AS [Second],
{fn Week(getDate() )} AS [Week],
{fn Year(getDate() )} AS [Year],
{fn TimestampAdd(SQL_TSI_DAY, 14, getDate())} AS [TimestampAdd],
{fn TimestampDiff(SQL_TSI_DAY, '2009-10-31', getDate())} AS [TimestampDiff]
</cfquery>
<cfdump var="#q#" label="Old MS SQL Functions fn{}" />
So I was having fun, trying to break things, and came across a lame excuse only a computer could get away with:
com.mysql.jdbc.exceptions.MySQLSyntaxErrorException: memory exhausted.
Just to get this out of the way: if you are looking for a CF wrapper for the Barbecue library, this is not the entry for you. If you are interested in the basics of using Barbecue, feel free to read on.
Why write yet another entry on Barbecue? Well, I was recently reading about barcodes, for my own edification, and did not find many introductory resources (you know ... ones that ask and answer those silly novice questions?). So decided to write up my findings and first impressions, in the hopes of providing someone else with a starting point in their research. Even if they are not using CF.
Background on Barcodes
Obviously using the library requires you know at least a little bit about the different bar code types. For example, some encode numeric digits only, others have different limits as to how many characters they can encode, etcetera. Two of the more helpful references I came across were: Different Types of Barcodes and a high level Barcode Comparison Chart.
Installing the Barbecue Library
Using the Barbecue library is very simple. Just download the library from sourceforge.net and extract the main jar from the .zip file. The current jar version is: barbecue-1.5-beta1.jar. Then place the main jar somewhere in your CF classpath, such as: C:\ColdFusion8\wwwroot\WEB-INF\lib. Finally restart the ColdFusion server so it will detect the jar.
Another option is to use Mark Mandel's JavaLoader.cfc, which is the option I chose. Just load the Barbecue jar in your array of paths, and create a new instance of the JavaLoader. (Obviously, this is just for demonstration. Usually you store the javaLoader instance in the server scope to avoid memory leaks).
Note: The jdom.jar must also be added to the JavaLoader paths if you intend to use Barbecue's SVG functionality.
<cfset paths = [] /> <cfset arrayAppend(paths, expandPath('barbecue-1.5-beta1/barbecue-1.5-beta1.jar')) /> <cfset arrayAppend(paths, expandPath('barbecue-1.5-beta1/lib/jdom.jar')) /> <cfset loader = createObject("component", "javaloader.JavaLoader").init( paths ) />
<cfset factory = loader.create("net.sourceforge.barbecue.BarcodeFactory") /> <cfset barcode = factory.createBookland( "0123456789" ) />
<cfset ImageHandler = loader.create("net.sourceforge.barbecue.BarcodeImageHandler") /> <cfset buffImage = ImageHandler.getImage( barcode ) /> <cfset barcodeImage = ImageNew(buffImage) /> <cfimage action="writeToBrowser" source="#barcodeImage#" />(2) Writing Images to an OutputStream
<cfset ImageHandler = loader.create("net.sourceforge.barbecue.BarcodeImageHandler") /> <cfset outStream = loader.create("java.io.ByteArrayOutputStream").init() /> <cfset ImageHandler.writePNG( barcode, outStream ) /> <!--- 1) Display the image using cfimage ... ---> <cfset barcodeImage = ImageNew(outStream.toByteArray()) /> <cfimage action="writeToBrowser" source="#barcodeImage#" /> <!--- 2) OR .. using cfcontent ... ---> <cfcontent type="image/png" variable="#outStream.toByteArray()#" />
<cfset ImageHandler = loader.create("net.sourceforge.barbecue.BarcodeImageHandler") /> <cfset IOFile = loader.create("java.io.File") /> <!--- save the barcode in three (3) different formats ---> <cfset imageFile = IOFile.init( ExpandPath("myBarcode.gif") ) /> <cfset ImageHandler.saveGIF( barcode, imageFile ) /> <img src="myBarcode.gif" alt="GIF Format" />Now earlier I mentioned a catch. In my tests, the generated images all had a black border along the bottom of the barcode. I am guessing it has something to do with the canvas color of the image object. One way I was able to remove it, was by creating a blank image first. So I could control the canvas color. Then use some lower level methods to draw the barcode onto my image. That brings me to the other two (2) options for creating images.
<!--- Option 1: RBG image, white canvas ---> <cfset img = ImageNew("", barcode.getWidth(), barcode.getHeight(), "rgb", "ffffff") /> <cfset graphics = ImageGetBufferedImage(img).getGraphics() /> <cfset barcode.draw( graphics, 0, 0) /> <cfimage action="writeToBrowser" source="#img#"> <br /><br /> <!--- Option 2: Transparent image ---> <cfset img = ImageNew("", barcode.getWidth(), barcode.getHeight(), "argb") /> <cfset graphics = ImageGetBufferedImage(img).getGraphics() /> <cfset barcode.draw( graphics, 0, 0) /> <div style="background-color: blue;"> <cfimage action="writeToBrowser" source="#img#"> <br /> </div>
<!--- create a new image and extract the graphics ---> <cfset img = ImageNew("", barcode.getWidth(), barcode.getHeight(), "rgb", "ffffff") /> <cfset graphics = ImageGetBufferedImage(img).getGraphics() /> <!--- create the desired font and colors ---> <cfset Font = createObject("java", "java.awt.Font") /> <cfset Color = createObject("java", "java.awt.Color") /> <cfset barcodeFont = Font.init("Arial", Font.PLAIN, 12 ) /> <cfset backgroundColor = Color.BLUE /> <cfset foregroundColor = Color.decode("##008040") /> <!--- grab a Color object that will be used to set the barcode fore/background ---> <cfset GraphicsOutput = loader.create("net.sourceforge.barbecue.output.GraphicsOutput")> <cfset gOut = GraphicsOutput.init( graphics, barcodeFont, foregroundColor, backgroundColor ) /> <cfset barcode.output( gOut ) /> <cfset ImageWrite( img, ExpandPath("ColdFusion_Barbecue_GraphicsOutput.png")) /> <!--- display the barcode image ---> <cfimage action="writeToBrowser" source="#img#"> <br />
<cfset BarcodeFactory = loader.create("net.sourceforge.barbecue.BarcodeFactory") /> <cfset barcode = BarcodeFactory.createStd2of5( "0123456789" ) /> <cfset barcode.setBarWidth( 6 ) /> ...
<cfset barcode.setDrawingQuietSection( false ) />
<cfset barcode.setDrawingText( false ) />
<cfset Font = createObject("java", "java.awt.Font") /> <cfset barcodeFont = Font.init("Arial", Font.ITALIC, 12) /> <cfset barcode.setFont( barcodeFont ) />
<!--- change foreground ---> <cfset Color = createObject("java", "java.awt.Color") /> <cfset barcode.setForeground( Color.RED ) /> <!--- change background (hex color) ---> <cfset Color = createObject("java", "java.awt.Color") /> <cfset barcode.setBackground( Color.decode("##0080c0") ) />
<cfset barcode.setResolution( 300 ) />SVG
<cfset BarcodeFactory = loader.create("net.sourceforge.barbecue.BarcodeFactory") /> <cfset barcode = BarcodeFactory.createBookland( "0123456789" ) /> <cfset SVGFormatter = loader.create("net.sourceforge.barbecue.formatter.SVGFormatter") /> <cfset svgString = SVGFormatter.formatAsSVG( barcode )>Related Entries:
I was checking my "trials" email folder the other day and these messages gave me a chuckle. That and the fact that I received a "Your ColdFusion 9 trial starts now" message, before my download had even finished! Can you spot what is wrong here?
Exceptions can be deceptive and loathsome creatures at times. Case in point, I was happily using createObject() to load an object from a java jar in my classpath. I then added a try/catch. So I could detect when the jar probably was not added to the CF classpath properly, and display a more meaningful error message.
<cfscript>
try
{
handler = createObject("java", "net.sourceforge.barbecue.BarcodeImageHandler");
}
catch(java.lang.ClassNotFoundException e)
{
throwError(message="Verify the Barbecue jar is in your CF classpath", type="SupportingClassNotFound");
}
</cfscript>
coldfusion.runtime.java.JavaObjectClassNotFoundException.
<cfscript>What is the world coming to when you cannot even trust error messages Oh, wait. Scratch that. Error messages lie all the time. I am sure they do not mean to lie, they just hold back critical information when you most need it. The secretive little trolls ;)
try
{
handler = createObject("java", "net.sourceforge.barbecue.BarcodeImageHandler");
}
catch(coldfusion.runtime.java.JavaObjectClassNotFoundException e)
{
throwError(message="Verify the Barbecue jar is in your CF classpath", type="SupportingClassNotFound");
}
</cfscript>
For some reason I always forget about the one good use for StructFind(). (Thank goodness for old blog entries). Though it seems even that use is gone in ColdFusion 9. Can anyone think of another one?
...Read More
<!--- Works in CF9, not CF8 --->
<cfoutput>
#getSomeStructure()["dog"]#
</cfoutput>
<cffunction name="getSomeStructure" returntype="struct">
<cfset var myStruct = {} />
<cfset myStruct.cat = "felix" />
<cfset myStruct.dog = "clifford" />
<cfreturn myStruct />
</cffunction>
Yesterday , I wrote about discovering a true gem in latest version of Mark Mandel's JavaLoader: the CFCDynamicProxy. This tiny, but powerful, class acts as a wrapper around a ColdFusion cfc. Essentially allowing it to mimic a native java object and communicate directly with other java objects in ways it never could before! We all know just about everything in ColdFusion boils down to a java object internally. But until now there were certain things you just could not do from a cfc without taking the r-e-a-l-l-y long way around. Well, the CFCDynamicProxy changes all that.
A perfect example is page events in iText. In order to add custom headers, footers, etcetera with iText you need a custom java class that implements the PdfPageEvent interface. Well as easy and natural as that is in a java environment, it is a bit awkward to have to create and load a new java class in CF every time you wish to create a different set of headers or footers.
Eventually, I came up with a more dynamic method of calling a cffunction to add headers/footers from a java, but it still required that custom java class. But using the CFCDynamicProxy, I was able to simplify and rewrite the code entirely in CF, and let me say... man what a difference! It is now totally dynamic and written in native CF, no extra java classes needed. In comparison to the elegance of using the new Proxy, my previous attempt looks like a decrepit, wart-nosed-hag. To be fair, a lot of what the previous entry was doing manually is still going on in the background. But the CFCDynamicProxy makes it much, much simpler.
The first step in my code beautification effort was creating a CFC that implemented all of the methods in the iText PdfPageEvent interface. Just as you would if you were implementing a CF interface. Using the PdfPageEvent API, I created a cfc with a function for each of the methods in that interface, taking care to properly align the arguments and data types so they matched their java counterparts.
Mapping the java to ColdFusion data types is pretty simple. Any arguments that are instances of a java class (like PdfWriter, Document, Rectangle) are all mapped to type="any". The rest are usually primitive types like "string" and "float" and the conversions are intuitive, for the most part.
Next I filled in a few functions needed for generating basic page footers. The key function was onPageEnd, which is where all the action takes place. OnPageEnd will be called by iText just before new pages are written to my new pdf document. So inside that function, I grab the writer object and use it to set the desired properties and finally write the footer text onto the pdf.
<cffunction name="onEndPage" access="public" returntype="void" output="true"
hint="Called when a page is finished, just before being written to the document.">
<cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
<cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
<cfset var Local = {} />
<cfscript>
if (len(variables.instance.footerText))
{
Local.cb = arguments.writer.getDirectContent();
Local.cb.saveState();
Local.cb.beginText();
Local.cb.setColorFill( variables.instance.textColor );
Local.cb.setFontAndSize( variables.instance.font, javacast("float", variables.instance.fontSize) );
Local.cb.setTextMatrix( arguments.document.left(), arguments.document.bottom() - 10);
Local.text = variables.instance.footerText &" page ["& arguments.writer.getPageNumber() &"]";
Local.cb.showText( Local.text );
Local.cb.endText();
Local.cb.restoreState();
}
</cfscript>
</cffunction>
<cfscript>
//add the javaloader dynamic proxy library (and the iText jar) to the javaloader
libpaths = [];
arrayAppend(libpaths, expandPath("/javaLoader/support/cfcdynamicproxy/lib/cfcdynamicproxy.jar"));
arrayAppend(libpaths, expandPath("/dev/itext/iText-2.1.7.jar") );
//we HAVE to load the ColdFusion class path to use the dynamic proxy, as it uses ColdFusion's classes
loader = createObject("component", "javaLoader.JavaLoader").init(loadPaths=libpaths, loadColdFusionClassPath=true);
</cfscript>
<cfscript>
//....
//intialize the page event handler component
eventHandler = createObject("component", "PdfPageEventHandler").init( font=textFont, fontSize=10, textColor=textColor);
//add a custom footer
eventHandler.setFooterText( "www.clueless.corp * 85 anywhere blvd * lost city" );
//we can pass in an array of strings which name all the interfaces we want out dynamic proxy to implement
interfaces = ["com.lowagie.text.pdf.PdfPageEvent"];
//get a reference to the dynamic proxy class
CFCDynamicProxy = loader.create("com.compoundtheory.coldfusion.cfc.CFCDynamicProxy");
// create a proxy that we will pass to the iText writer
eventHandlerProxy = CFCDynamicProxy.createInstance(eventHandler, interfaces);
</cfscript>
// ...
<cfscript>
fullPathToOutputFile = ExpandPath("./ABetterPdfPageEventHandler.pdf");
document = loader.create("com.lowagie.text.Document").init();
outStream = createObject("java", "java.io.FileOutputStream").init(fullPathToOutputFile);
writer = loader.create("com.lowagie.text.pdf.PdfWriter").getInstance(document, outStream);
// ** register the PROXY as the page event handler **
writer.setPageEvent( eventHandlerProxy );
document.open();
Paragraph = loader.create("com.lowagie.text.Paragraph");
document.add( Paragraph.init("Paragraph Text....") );
document.newPage();
document.add( Paragraph.init("Paragraph Text....") );
document.newPage();
document.close();
outStream.close();
WriteOutput("Done!");
</cfscript>
So after many weeks, I finally got a chance to work with Mark Mandel's JavaLoader 1.0 Beta today, and boy did it put a smile on my face. When I started reading the documentation on the Dynamic Proxy, I nearly fell out of my chair.
...And there on the other side, read the following words ...
In JavaLoader 1.0, I've written a Dynamic Proxy that you are able to wrap around a CFC, and thereby make Java Objects think they are interacting with a native Java object, but are in fact, talking to your CFC.
A recent question on the adobe forums mentioned the ImageSharpen() function was adding a black border to the generated image. As google did not turn up much on the topic, I concluded most people are using alternate methods to sharpen images. A quick peek at some of the more popular image components confirmed it. These components all use java's ConvolveOp class, internally, for sharpening.
In Part 1 I mentioned the internal CF class coldfusion.thread.Task that seems to be used for running processes with cfthread. Tasks for a given thread can be retrieved from the FusionContext class using a method called getUserThreadTask(). So between the two, you should have enough information for makings of an experimental tracker.
Now I was particularly interested in the coldfusion.thread.Task's cancel() method and whether it could be used to kill a cfthread programatically. So I created a very simple cfthread. The thread itself does very little besides outputting a few messages. But I added a few sleep statements to simulate a thread that would take twenty (20) seconds to execute.
<cfset threadName = "CFZombieThread_Test" >
<!--- Start the new thread ---->
<cfthread action="run" name="#threadName#" myName="#threadName#">
<cfset var zombieThreadName = getPageContext().getFusionContext().getCurrentUserThreadName() />
<cfloop from="1" to="20" index="counter">
<cfoutput>
Mindless groan from zombie thread #zombieThreadName# at #now()# ...<br/>
</cfoutput>
<!---
Force the thread to sleep to simulate a long running task
--->
<cfset sleep(1 * 1000) />
</cfloop>
</cfthread>
<cfset context = getPageContext().getFusionContext() />
<cfset task = context.getUserThreadTask(UCASE(threadName)) />
<cfdump var="#cfthread[threadName]#" label="#threadName# BEFORE cancel() request " />
<!--- Sleep for a few seconds to give the thread a chance to produce some output ...--->
<cfset createObject("java", "java.lang.Thread").currentThread().sleep(2000) />
<!--- Now, try and kill the thread --->
<cfset task.cancel() />
<!--- Finally, give the thread a chance to catch up before we check its final status ...--->
<cfset createObject("java", "java.lang.Thread").currentThread().sleep(500) />
<cfdump var="#cfthread[threadName]#" label="#threadName# AFTER cancel() request " />
<!--- Start the new thread ---->
<cfthread action="run" name="#newThreadName#" >
<cfset var myThreadUUID = "" />
<cfset var myThreadContext = getPageContext().getFusionContext() />
<cfset var myThreadName = myThreadContext.getCurrentUserThreadName() />
<!---
First, the thread will add itself to the tracker object
--->
<cfset myThreadUUID = application.threadTracker.addThread(
threadName = myThreadName,
threadTask = myThreadContext.getUserThreadTask(myThreadName)
) />
<!---
DO THREAD STUFF HERE .....
--->
<!---
Finally, the finished thread will remove itself from
the application tracker object
--->
<cfset application.threadTracker.removeThread(
threadUUID = myThreadUUID
) />
</cfthread>
© Blogger templates The Professional Template by Ourblogtemplates.com 2008
Header image adapted from atomicjeep