The past two days I've been working on a small project which
required the generating of a PDF file with a barcode and some
other text data, and a watermark image. Today I decided to do a
clean install on Ubuntu 9.10 AMD64 running in VirtualBox of all
programs I use to convert an XML file, via an intermediate XML
file, to a PDF file, . And a good thing I did, since my customer
had trouble with making Apache FOP, one of the programs I use in
the project, work with the Barcode4J extension. The FOP shell
script, located in /usr/bin/, which launches the Java FOP
program is different in more recent versions of Ubuntu from the
script in Ubuntu 8.10, which I am (still) running. Instead of
setting CLASSPATH
one now has to set
JAVA_CLASSPATH
to the full path of a jar file to
make Apache FOP see the Barcode4J extension. This is explained
in more detail in the "Testing Barcode4J via Apache FOP" section
below.
Since Apache Formatting Objects Processor (FOP) is a Java application I first installed the Sun Java Runtime Environment on a clean install of Ubuntu 9.10 as follows:
sudo apt-get install sun-java6-jre
Maybe you prefer a different Java Runtime Environment, but I am OK with the version Sun provides. Note that if you want to write your own Java programs you might want to install the Java Development Kit instead, i.e.:
sudo apt-get install sun-java6-jdk
After some time, depending on your network speed, a "window" showing the "Operating System Distributor License for Java version 1.1 (DLJ)" should appear. Press tab to navigate to the Ok button and press enter if you want to agree with the license. In the next "window", press the left arrow key to select the Yes button and press Enter, and the installation of the Java runtime environment should continue.
Now the command java -version
should report a message
identical or similar to:
java version "1.6.0_15"
Java(TM) SE Runtime Environment (build 1.6.0_15-b03)
Java HotSpot(TM) 64-Bit Server VM (build 14.1-b02, mixed mode)
The Apache Formatting Objects Processor can be installed via the command line as follows:
sudo apt-get install fop
After a successful installation entering just fop
on the
command line should give a long list of options, and near the very end:
SEVERE: Exception
org.apache.fop.apps.FOPException: No input file specified
followed by a stack trace.
I also got the following warning at the beginning of the output:
[warning] /usr/bin/fop: Unable to locate servlet-api in /usr/share/java
which can be ignored.
A few days ago, when looking online for a way to generate
barcodes via Apache FOP I came upon Barcode4J, a flexible
generator for barcodes, supporting several formats including:
Code 39, Code 128, EAN-128, UPC-A, UPC-E, EAN-13, etc., written
in Java. On top of that, barcodes can be rotated as
well. Barcode4J can be used as an extension to Apache FOP and is
supported via fo:instream-object
.
In order to download Barcode4J use a web browser to navigate to the Browse Barcode4J Files page on SourceForge.net, and click the large green button to start the downloading of barcode4j-2.0-bin-zip.
After the download has finished, move the zip file to a
directory you want it installed in. This can be a newly created
(hidden) directory in your home directory, for example. Or if
you want to make the extension available to other users on the
same computer you might want to use (a sub directory in)
/usr/local/share
. Extract the file:
unzip barcode4j-2.0-bin.zip
This will create a new sub directory barcode4j-2.0
in the same directory.
In order to make Barcode4J available to Apache FOP as an
extension one has to add the correct jar to the class path of
Apache FOP. The jar files are located inside the build sub
directory of barcode4j-2.0. In Ubuntu 9.10, and most likely on
Ubuntu 9.04 as well, you can do this by setting the
JAVA_CLASSPATH
variable. However, on Ubuntu 8.10 -
and most likely older versions as well - you have to use
CLASSPATH
. Something I (and my customer) bumped into
today, since I develop on Ubuntu 8.10 while he tests on Ubuntu 9.04.
Anyway, use the following command and change the variable name
if you're on an older Ubuntu version:
export JAVA_CLASSPATH='/path/to/barcode4j-fop-ext-complete.jar'
replace /path/to
with the absolute path to the location
of the jar file, which as explained above is located in the build sub
directory of the barcode4j-2.0 directory.
Do not accidentally use the full path of
barcode4j-fop-ext-0.20.5-complete.jar
since this
file is for use with a much older version of Apache FOP and will
result in the following error if you run a recent fop
:
Exception in thread "main" java.lang.IncompatibleClassChangeError:
Implementing class
Next, change your working directory to the xsl-fo directory which is located in the examples sub directory of barcode4j-2.0 and enter the following command:
fop fop-extension-demo.fo test.pdf
This will create a file named test.pdf in the xls-fo directory. Open this file and verify it has several generated barcodes in it; you can open this PDF file from the command line as follows:
xdg-open test.pdf
On my system I've made g
an alias for
xdg-open
, see A Windows start alternative for Ubuntu
If you don't see any barcodes, you most likely also got a lot of
"SEVERE: Intrinsic dimensions of instream-foreign-object could
not be determined" messages reported by the fop
command. This is most likely caused by the Barcode4J extension
not being available in the class path. You either used the wrong
variable name (or made a typo in it), or you didn't provide the
correct path to the jar file. You can check this using:
ls $JAVA_CLASSPATH
which should report the absolute path, including the name of the jar file. If, however, you get:
ls: cannot access /some/path: No such file or directory
you can be quite sure that JAVA_CLASSPATH
contains
the wrong value.
If you do get the barcodes in the PDF file you might want to add
the export command to your .bashrc
.
Note that if you use the Document Viewer program to view your pdf files you don't have to close the program and reopen the pdf file if you make changes to your input XML file or stylesheet. The Document Viewer notices that the pdf file has changed, and reloads it. This is very handy when tweaking XML files.
For the aforementioned project I had decided to use Saxon-B as an XSLT processor; it would be used to transform an XML file into another XML file with XSL-FO elements added to it, which could be further processed by Apache FOP. Saxon-B was installed using:
sudo apt-get install libsaxonb-java
After the installation has completed, entering saxonb-xslt
on the command line should report version information, for example
"Saxon 9.0.0.4J from Saxonica", followed by a link to usage information,
followed by a long list of options.
Below follow two files which can be used to create a simple PDF
file with a barcode and, bonus, a "watermark" image; an image
that is used as a background on top of which the text and the
barcode are rendered. The image itself is not available for
download, so you have to add an alternative image named
watermark.png
yourself to the directory you
saved the XML and XSL file to, or remove the four background
related attributes from the fo:region-body
element.
Save the XML code that follows below to a file named
hello-barcode.xml
. This file can be converted to an
XML file containing formatting objects using Saxon-B and the
stylesheet that follows after this code.
<?xml version="1.0"?>
<example>
<heading>Hello, barcode world!</heading>
<barcode>123456789012</barcode>
</example>
Save the XML stylesheet code that follows below to a file named
hello-barcode.xsl
. This file directs Saxon-B on how
to convert the previous XML code into an XML file containing
formatting objects, which Apache FOP can render into PDF or any
of the outer output formats supported, like PostScript, PNG,
TIFF, SVG, etc.
The stylesheet consists roughly of four parts:
message
variable, and assigns this value to the
message attribute of the barcode element.
<?xml version="1.0"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml"/>
<xsl:template match="example">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master
master-name="page">
<fo:region-body
region-name="body"
margin-top="0.5in"
margin-bottom="1in"
margin-left="0.5in"
margin-right="0.5in"
background-image="watermark.png"
background-repeat="no-repeat"
background-position-horizontal="left"
background-position-vertical="top"/>
/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="page">
<fo:flow flow-name="body">
<fo:block font-size="18pt" margin="0.5cm">
<xsl:value-of select="heading"/>
</fo:block>
<fo:block margin-top="2.4cm" margin-left="3cm">
<xsl:apply-templates select="barcode"/>
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template match="barcode">
<xsl:variable name="message" select="."/>
<fo:instream-foreign-object>
<barcode:barcode
xmlns:barcode="http://barcode4j.krysalis.org/ns"
message="{$message}">
<barcode:ean-13>
<barcode:height>15mm</barcode:height>
</barcode:ean-13>
</barcode:barcode>
</fo:instream-foreign-object>
</xsl:template>
</xsl:stylesheet>
The PDF of which the top left part is shown above was generated using the following two commands:
saxonb-xslt -o:hello-barcode.fo hello-barcode.xml hello-barcode.xsl
fop hello-barcode.fo hello-barcode.pdf
Note that in the above XSL example the margins of the
fo:block
element are (ab)used to line out the
barcode on top of the "watermark", a photo of a wasp moth,
tentative id Cosmosoma ethodaea, I took in August last year.
After reading several reviews I purchased the following three books on XSLT, XPath and XSL-FO some time ago. I especially like the book on XSLT by Doug Tidwell a lot, maybe also because I've used it the most so far.