Ask Ben: Printing Shipping Labels With ColdFusion
I can't seem the find the question in my Inbox at the moment, but a few days ago, someone asked me about using ColdFusion to print shipping labels. Now, I know that Microsoft gets a bad reputation, but I happen to think that a lot Microsoft applications are quite useful and make my work a whole lot easier. This is one of those occasions. Using ColdFusion in conjunction with Microsoft Word's Web Archive File format (MHT files) makes creating and printing shipping label templates a piece of tasty cake (and we all know that cake is good).
When it comes to printing shipping lables, a lot of the time, you need to create a printable format that matches up with some sort of sticky label paper that you have in your printer. Most likely, there is a Microsoft Word template that accomodates what ever kind of paper you are dealing with. The first thing you want to do is find this Word template, download it, and open it up in Word. It will probably look something like this:
Now, it's time to leverage the web archive file (MHT) that Microsoft offers. Go to the File menu and save the current Word document as a single web page:
When doing this, it might tell you that it has to convert certain Microsoft Word features into web-compatible features. Most of the time this should be fine, so just accept whatever they tell you needs to get done.
Now, you should have an MHT file that can be opened in any text editor (such as Macromedia Homesite). In reality, this shipping label MHT file is just an amped up HTML file and we can use ColdFusion to generate it. Once you open the MHT file, you will see that the hardest part is just trying to clean up the Microsoft Word HTML enough to be able to work with it. Once you tab out the HTML into a readable format, you can delete the majority of data and replace it with some sort of CFLoop to output your shipping labels.
Since I don't have a query full of address information, my demo MTH / HTML just uses an indexed CFLoop and outputs the same address 30 times. I am hoping that when you replace this with a ColdFusion query loop, you will see that the transition is quite easy.
Here is my generate_labels.cfm ColdFusion template. This does not serve up the document, it only creates the data:
MIME-Version: 1.0
Content-Location: file:///C:/9E42A1F3/shipping_labels.htm
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset="us-ascii"
<html
xmlns:o=3D"urn:schemas-microsoft-com:office:office"
xmlns:w=3D"urn:schemas-microsoft-com:office:word"
xmlns:dt=3D"uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
xmlns=3D"http://www.w3.org/TR/REC-html40">
<head>
<meta http-equiv=3DContent-Type content=3D"text/html; charset=3Dus-ascii">
<meta name=3DProgId content=3DWord.Document>
<meta name=3DGenerator content=3D"Microsoft Word 11">
<meta name=3DOriginator content=3D"Microsoft Word 11">
<link rel=3DFile-List href=3D"shipping_labels_files/filelist.xml">
<style>
@font-face
{font-family:Tahoma;
panose-1:2 11 6 4 3 5 4 4 2 4;
mso-font-charset:0;
mso-generic-font-family:swiss;
mso-font-pitch:variable;
mso-font-signature:1627421319 -2147483648 8 0 66047 0;}
@font-face
{font-family:"Monotype Corsiva";
panose-1:3 1 1 1 1 2 1 1 1 1;
mso-font-charset:0;
mso-generic-font-family:script;
mso-font-pitch:variable;
mso-font-signature:647 0 0 0 159 0;}
@font-face
{font-family:Garamond;
panose-1:2 2 4 4 3 3 1 1 8 3;
mso-font-charset:0;
mso-generic-font-family:roman;
mso-font-pitch:variable;
mso-font-signature:647 0 0 0 159 0;}
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-parent:"";
margin:0in;
margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.0pt;
font-family:"Times New Roman";
mso-fareast-font-family:"Times New Roman";}
p.AveryWizard, li.AveryWizard, div.AveryWizard
{mso-style-name:"Avery Wizard";
margin:0in;
margin-bottom:.0001pt;
line-height:133%;
mso-pagination:widow-orphan;
font-size:16.0pt;
mso-bidi-font-size:10.0pt;
font-family:"Times New Roman";
mso-fareast-font-family:"Times New Roman";}
@page Section1
{size:8.5in 11.0in;
margin:.75in 0in 0in 29.25pt;
mso-header-margin:.5in;
mso-footer-margin:.5in;
mso-paper-source:0;}
div.Section1
{page:Section1;}
table.MsoNormalTable
{mso-style-name:"Table Normal";
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-parent:"";
mso-padding-alt:0in 5.4pt 0in 5.4pt;
mso-para-margin:0in;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.0pt;
font-family:"Times New Roman";
mso-ansi-language:#0400;
mso-fareast-language:#0400;
mso-bidi-language:#0400;}
</style>
</head>
<body lang=3DEN-US style=3D'tab-interval:.5in'>
<div class=3DSection1>
<table
class=3DMsoNormalTable
border=3D0
cellspacing=3D0
cellpadding=3D0
style=3D'margin-left:.4pt;border-collapse:collapse;mso-padding-alt:0in 0in 0in 0in'>
<tr style=3D'page-break-inside:avoid;height:93.0pt'>
<cfoutput>
<!---
Instead of looping over a query as we probably
would want to do, we are going to loop over an
index array and just repeat the same address
over and over again; we are just demonstrating
label printing with ColdFusion and MS Word -
we don't need real addresses.
--->
<cfloop
index="intI"
from="1"
to="30"
step="1">
<td
width=3D336
valign=3Dtop
style=3D'width:3.5in;padding:0in 0in 0in 0in; height:93.0pt'>
<p
class=3DAveryWizard
align=3Dcenter
style=3D'text-align:center;line-height:normal'>
<span style=3D'font-size:30.0pt;mso-bidi-font-size:10.0pt;font-family:"Monotype Corsiva"'>
COMPANY NAME
</span>
<span style=3D'font-size:18.0pt;mso-bidi-font-size:10.0pt;font-family:"Monotype Corsiva";layout-grid-mode:line'><o:p></o:p></span>
</p>
<p
class=3DAveryWizard
align=3Dcenter
style=3D'text-align:center;line-height:normal'>
<span style=3D'font-size:20.0pt;mso-bidi-font-size:10.0pt;font-family:Garamond;layout-grid-mode:line'>
STREET 1 ADDRESS
<o:p></o:p>
</span>
</p>
<p
class=3DAveryWizard
align=3Dcenter
style=3D'text-align:center;line-height:20.0pt'>
<span style=3D'font-size:20.0pt;mso-bidi-font-size:10.0pt;font-family:Garamond;layout-grid-mode:line'>
CITY HERE, STATE
<span style=3D'mso-spacerun:yes'> </span>
ZIP CODE
</span>
<span style=3D'font-size:18.0pt;mso-bidi-font-size:10.0pt;font-family:Tahoma;mso-bidi-font-family:"Times New Roman";layout-grid-mode:line'><o:p></o:p></span>
</p>
<p
class=3DAveryWizard
align=3Dcenter
style=3D'text-align:center;line-height:20.0pt'>
<span style=3D'font-size:18.0pt;mso-bidi-font-size:10.0pt;font-family:Tahoma;mso-bidi-font-family:"Times New Roman";layout-grid-mode:line'><o:p> </o:p></span>
</p>
</td>
<!---
The Left/Right columns are sepparated by
a spacer column. Check to see if we need
to put that in.
--->
<cfif ((intI MOD 2) EQ 1)>
<td
width=3D66
style=3D'width:49.55pt;padding:0in 0in 0in 0in;height:93.0pt'>
<p
class=3DAveryWizard
align=3Dcenter
style=3D'text-align:center;line-height:normal'>
<span style=3D'font-size:12.0pt;mso-bidi-font-size:10.0pt;layout-grid-mode:line'><o:p> </o:p></span>
</p>
</td>
</cfif>
<!---
Check to see if we need to end this row
and put in the next row (when checking the
index modulus, be sure to check to see
if another row is even required).
--->
<cfif (
(NOT (intI MOD 2)) AND
(intI LT 30)
)>
</tr>
<!--- In between each row is a spacer row. --->
<tr style=3D'page-break-inside:avoid;height:.25in'>
<td width=3D336 style=3D'width:3.5in;padding:0in 0in 0in 0in;height:.25in'>
<p class=3DAveryWizard align=3Dcenter style=3D'text-align:center;line-height:normal'>
<span style=3D'font-size:12.0pt;mso-bidi-font-size:10.0pt;layout-grid-mode:line'><o:p> </o:p></span>
</p>
</td>
<td width=3D66 style=3D'width:49.55pt;padding:0in 0in 0in 0in;height:.25in'>
<p class=3DAveryWizard align=3Dcenter style=3D'text-align:center;line-height:normal'>
<span style=3D'font-size:12.0pt;mso-bidi-font-size:10.0pt;layout-grid-mode:line'><o:p> </o:p></span>
</p>
</td>
<td width=3D336 style=3D'width:3.5in;padding:0in 0in 0in 0in;height:.25in'>
<p class=3DAveryWizard align=3Dcenter style=3D'text-align:center;line-height:normal'>
<span style=3D'font-size:12.0pt;mso-bidi-font-size:10.0pt;layout-grid-mode:line'><o:p> </o:p></span>
</p>
</td>
</tr>
<tr style=3D'page-break-inside:avoid;height:93.0pt'>
</cfif>
</cfloop>
<!--- End address loop. --->
</cfoutput>
</tr>
</table>
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'>
<span style=3D'font-size:12.0pt;mso-bidi-font-size:10.0pt;layout-grid-mode:line'><o:p> </o:p></span>
</p>
</div>
</body>
</html>
Now that we have the data generating ColdFusion template for our shipping labels, all we need is a simple ColdFusion file that grabs that content and serves it up as a Microsoft Word document:
<!--- Create the label data. --->
<cfsavecontent variable="strMHTData">
<!---
Include the MHT data. We are doing this as
a CFSaveContent so that we can have better
trim and streaming control.
--->
<cfinclude template="./generate_labels.cfm" />
</cfsavecontent>
<!---
Trim the MHT content. Microsoft seems to be
very picky about leading whitespace.
--->
<cfset strMHTData = Trim( strMHTData ) />
<!--- Set the header information. --->
<cfheader
name="content-disposition"
value="attachment; filename=shipping-labels.mht"
/>
<!--- Set the content to be a Microsoft Word file. --->
<cfcontent
type="application/msword"
variable="#ToBinary( ToBase64( strMHTData ) )#"
/>
Running the above code, we get prompted to open up a Microsoft Word document that looks like this:
Print that and you should be good to go. Now, I hope I don't get flamed for this technique, but if you embrace the feature of Microsoft Word, rather than shunning all the stuff that makes them suck, you will find that they do have a lot of things that just make your life easier.
Want to use code from this post? Check out the license.
Reader Comments
Bizarre, I was expecting a Create-a-PDF/CFPRINT solution. o_O
I wish! I neither have CF8, nor a printer, let along a printer hooked up to a CF8 box :D
1.) Download CF8 and enable developer mode.
2.) Go buy a printer.
3.) Write up tutorial and post it.
4.) ????
5.) Profit!!1!!
very interesting
I actually downloaded CF8, I just have been lazy about installing it on my PC at home :(
Interesting technique, but I'd definitely recommend using cfreport for this. Custom or standard labels work great with your choice of output type and no reliance on Word.
T
@Terry,
Sadly, I have never used CFReport. How does it handle things like having to match up with custom paper layouts? Or is it basically hit and miss configuration (but then once you got it, you got it)?
I have no problem with using Word as part of a solution...but I always thought this was kind of cool...
http://www.cfreport.org/index.cfm?mode=entry&entry=38BBAD81-3048-2D03-0A777CDC2FB60D74
If you want to try it (I haven't) I would suggest you read the comments.
This is a good tutorial on using the CF Report Builder to create Avery labels:
http://www.adobe.com/devnet/coldfusion/articles/averylabels.html
The tutorial includes pre-created files for a number of common label numbers, and all you have to do is
1) install the CF Report Builder (free, quick install)
2) copy a label file your web directory
3) modify the file to include your query and fields
Very simple.
I haven't yet tried the label builder mentioned by Mark above because there were sample files for the labels I needed, but I'm interested to see if it works for less common labels that don't already have a file built.
I'll second the suggestion others have made, labels are pretty easy to do using the report builder, there are a bunch of pre-built ones, and if they don't fit your needs it's easy to create some with the specifications you want.
Having said that, your solution is still pretty cool and probably accomodates people who for some reason are still running an old version of CF. Good thinking there!
Great post as always.
We have a project where I generating hundreds of Barcode Labels. We using iText with Barbcue. Since we are using a thermal label printer it's a little easier on the spacing. Plus I like doing everything as PDF's :).
Barcodes:
http://www.digitalrewind.com/blog/index.cfm/2007/4/5/Generating-Barcodes-with-Coldfusion
IText
http://blog.internetdatabases.com/?p=7
@Ben
Sorry for the delay, but as Thomas stated, it's fairly straight forward to create custom labels. Just get the measurements set up correctly and it works great.
I think that the report builder is a pretty powerful tool that is overlooked by many CF developers. Even though I don't use it a lot I find the tool priceless for labels and so on. The only problem is that the CF7 app was pretty lame. It was about the clunkiest, buggiest app I've seen in a long time. Once you figured out the idiosyncrasies, the final printed output is worth the effort though.
I have not used the CF8 report builder yet, but supposedly they put work into improving it.
If you are just outputting formatted html, how about using cfdocument to generate a PDF? This is available on >= CF7 and a lot easier.
Oh I see, you are using an MS Word label template, never mind.
@Josh
you did make a very good point. i just finished an overhaul of an in-house label-generating page using cfdocument to generate a pdf. i found the detailed specs for my label of interest on http://www.onlinelabels.com/ and used these to create the css necessary to layout the labels quite precisely.
armed with the detailed specs in inches and the knowledge that the pdfs are 100dpi, it was dead simple to create the pdf.
sherrardb - excellent! Can you post an example css (with html)?
why certainly. i hope all of this renders correctly.
my notes:
Avery 5366 Label Dimensions & Info
downloaded from http://www.onlinelabels.com/
which was found through a google search http://www.google.com/search?hl=en&q=avery+5366+template&btnG=Search
Label Length: 3.4375"
Label Height: 0.6562"
Sheet Top Margin: 0.5"
Sheet Bottom Margin: 0.5"
Sheet Left Margin: 0.5"
Sheet Right Margin: 0.5"
Label Horizontal Spacing (gutter): 0.547"
Label Vertical Spacing (gutter): 0"
Similar in Layout To**: Avery ® 5066 **, 5366 **, 8366 **
Intended Use: File Labels, Folder Labels
the pdf resolution seems to be 100dpi, so for adjustments based on these measurements, just multiply the measurement in inches time 100.
the code: i had to deidentify some of this so forgive me if i've introduced any syntax errors
<cfset barcode_height = "63px">
<cfset barcode_width = "174px">
<cfset label_width = "142px">
<cfset gutter_width = "50px">
<cfset spacer_height = "0px">
<cfset name = "John Doe">
<cfset id = "WHATEVER">
<cfdocument pagetype="letter" format="pdf" mimetype="application/pdf" margintop=".5" marginright="0" marginbottom="0" marginleft=".5" overwrite="yes">
<cfoutput>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Barcode Labels</title>
<style type="text/css" media="all">
<!--
td.label {
height: #barcode_height#;
width: #label_width#;
}
div.label {
font-family: Arial, Helvetica, sans-serif;
font-size: 14pt;
color: Black;
padding-top: 15px;
padding-left: 3px;
}
td.barcode {
font-family: "BC C39 3 to 1 Narrow";
font-size: 34pt;
height: #barcode_height#;
width: #barcode_width#;
padding-left: 9px;
}
td.gutter {
height: #barcode_height#;
width: #gutter_width#;
}
td.spacer {
height: #spacer_height#;
}
body, div, table, tr, td {
background-color: White;
margin: 0;
padding: 0;
border: 0;
}
td {
empty-cells: show;
}
/* for debugging */
/*
td.label { background-color: ##c0ffc0; }
div.label { background-color: ##80ff80; }
td.barcode { background-color: ##c0c0c0; }
td.gutter { background-color: ##c0c0ff; }
td.spacer { background-color: ##ffffc0; }
table.barcode-container { background-color: ##ffc0c0; }
body.barcode-body { background-color: ##a0a0a0; }
*/
-->
</style>
</head>
<body class="barcode-body">
<table cellspacing="0" class="barcode-container">
<!--- this particular sheet is two labels across and fifteen labels down --->
<cfloop from="1" to="15" index="i">
<cfif i gt 1>
<tr><td colspan="5" class="spacer"></td></tr>
</cfif>
<tr>
<td class="barcode">*#id#*</td>
<td class="label" valign="top"><div class="label">#id#<br />#name#</div></td>
<td class="gutter"></td>
<td class="barcode">*#id#*</td>
<td class="label" valign="top"><div class="label">#id#<br />#name#</div></td>
</tr>
</cfloop>
</table>
</body>
</html>
</cfoutput>
</cfdocument>
First, I'd like to say that this is a very good article.
It's true that sometimes is better to use PDF cfreport solution; however, what if you want to pull some data from your database for your users so they can analyze it, write a brief summary about it.
They would like to make their report in word using the data pulled up from the database. So, I used your technique to do just that.
Thanks, great tutorial!
@Nery,
Glad to help, my friend.
I tried this example but apparently coldfusion doesn't have a "variable" attribute for the "content" tag.
@aden,
The Variable attribute is new in ColdFusion 7. You must be in a version before that. There is a way to write to the binary stream in pre-7 versions.
It's more complicated, but you can take a look here if some related examples:
www.bennadel.com/blog/1227-Using-ColdFusion-To-Stream-Files-To-The-Client-Without-Loading-The-Entire-File-Into-Memory.htm
That seems like a difficult way to create labels, but hey, it elegant and it works. They look ml-1000, or avery 5163 shipping labels.
@Vincenzo,
Glad you got it working!
I have a avery report that I am stuck with. I'll be using this to see if I can get unstuck :-)
Wish me luck
A