Friday, September 17, 2010

Custom TTF Fonts in JasperReports

You hit this wall too, aren't ya? At first I was truly pissed off by the overall design of Jasper Reports (entire suite in general). At second, I am even more pissed off how fonts are handled in PDFs.

First what you do, is you're trying to install some TTF fonts into your JVM, somewhere in $JRE/lib/fonts, map them and hope things will fly. Then you probably specify that your new font in Jasper Report XML file, something like:
<font fontName="Great Font" />
...and you finding it not working: in your PDF you see Helvetica. If you expect some Asian support (Japanese, e.g.), then you will find blank space there. Familiar, eh? Of course, you go around the XML Schema and finding that Jasper Reports actually has some more arguments to the "font" aggregate, so you adding it:
<font fontName="Great Font" ispdfembedded="true"/>
...and you finding it not working: in your PDF you see Helvetica. Then you, puzzled, finding out that there is also some more attributes (although they are quite odd anyway), so you use them too:
<font fontName="Great Font" ispdfembedded="true"
pdffontname="/madly/hardcoded/path/on/the/system/to/your/greatfont.ttf"/>
...and you still finding it not working: in your PDF you see Helvetica. Then you, angry, yelling "WTF!!!", googling around and you finding a lots of people, suffering from the same problem.

The key to solve this puzzle is to deal with completely stupid implementation of Jasper Reports engine: you have to package these fonts into JAR file and embed into your application. They (developers) think that since it is Java, so "run everywhere", therefore you take your .WAR or .JAR or .EAR file and put on any possible container and it will just work, so no need to setup the environment. In reality, however, when you deploy 1000 applications like this, you also have 1000 duplications of the same stuff. It results to a horrible mess and very very VERY angry sysadmins that literally hates all the guts of Java. Moral: keep it simple, stupid.

So anyways, how to use own TTFs or at least find Japanese visible? JaperReport's answer is: put this stuff into JAR and toss in into the classpath. Here is how to do it:
  1. In the default, root package, create a property file with a magic name: jasperreports_extension.properties
    Developers could not name it more elegant, like fonts.properties or, perfectly elegant, having none of it at all.
  2. Create a package for your fonts. Let's say it is foo.bar.fonts
  3. In this strange properties file we've made above, you have to put two things at least. One is very magical static property that normally can be hardcoded once and forever somewhere deep in the guts of JasperReports and never ever remembered anymore:
    net.sf.jasperreports.extension.registry.factory.simple.font.families
    =net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory
    Can you read that wide?
  4. Another property is to reference the reference to the reference of the actual files, embedded into the JAR:
    net.sf.jasperreports.extension.simple.font.families.somebullshit
    =foo/bar/fonts/fonts.xml
    That's how to let finally Jasper find the fonts.xml file. Please not one very nasty and ugly thing: there is no leading slash prior to "foo". Noticed? If you put it (as it should be), then forget it working.
  5. Now, in the foo.bar.fonts package create "fonts.xml". You can change its name in step #4.
  6. Map your fonts there as it is in examples of JasperReports (demo/fonts).
  7. Compile all this assembly into a JAR file and toss in somewhere in your app $CLASSPATH. Now your PDF should pick up the font finally.
  8. Phew!
If you still find this idiotic (like I do), you'd better try Dynamic Jasper http://http://dynamicjasper.sourceforge.net/ and it suppose to help you.

You might ask: how it should be better implemented? OK, you ever heard about X11 and how fonts are mapped there? In the same fashion. Why not use just fontmap.properties file in the JAR and just simply drop fonts there and have zero XML? Yes, it is magic name, but it is the only magic name. And then stick to something like one line per font, e.g.:
family=weight,encoding,embedded,file.ttf
Said that, it might look this way:
greatfamily=n,UTF-8,t,greatfont-nnormal.ttf
greatfamily=i,UTF-8,t,greatfont-italic.ttf
greatfamily=b,UTF-8,t,greatfont-bold.ttf
greatfamily=bi,UTF-8,t,greatfont-bold-italic.ttf
Then, you run something like $JASPER_REPORTS/bin/fontpack ...and it would create such a JAR right away for you. How that sounds? That sounds just greatly simplified yet idiotism. What it actually should be: you specify font name "Great Font" (not path to actual TTF!) in the XML and it picks up automatically from the JVM right away. If font is not installed, then it has to fall-back to the default (Helvetica, in this case) by warning the user to the log file (instead of crashing, by the way).

Still, I suffer from its performance, if this is the word. Generating PDFs are still hell very slow for my requirements. My next step will be ditching JasperReports and go OpenOffice.org as template editor and renderer iText based engine. Well, if company will pay for that. :-)

Wednesday, September 01, 2010

Nexenta 3.0 Zones: Fixing Broken Bones

As a rule of thumbs, when things installs very smoothly, I always suspect something wrong, because there is an old Ukrainian saying: "Devil lives in a silent lake". This time same thing happened to newly installed Nexenta Core 3.0 Release. Installation went just as sweet and smooth.

But when it comes to zones... :-(

Long story short: Zones are just totally broken in Nexenta Core 3.0 Release, end of story. SSH won't work, you won't login, no passwords asked, nothing. And I am quite surprised it was never fixed (probably nobody took a chance to test it). Now is the way how I fixed, grepping over bugs database:

First, before you install a zone, you have to fix broken /usr/bin/createzone Perl script. This should be done in two steps:
  1. Replace elatte-unstable with hardy-unstable on a line #41.

  2. Throw away @source_files with all its guts and replace with the following:
    my @source_files = qw(
    /lib/svc/method/nexenta-sysidtool-system
    /var/svc/manifest/system/nexenta-sysidtool.xml
    /lib/svc/method/nexenta-sysidtool-net
    );
Second, fix broken /lib/svc/method/svc-syseventd script. This is done in quite dirty way, but at least works fine:
  1. In the global zone edit the script above. Find this line (below the CDDL header):
    . /lib/svc/share/smf_include.sh
    ...and add this:
    [ `zonename` = global ] || sleep 3600 & exit 0
    ...so in result you will get this:
    . /lib/svc/share/smf_include.sh
    [ `zonename` = global ] || sleep 3600 & exit 0

This will allow required services finally start.

Now you can install your zone (zoneadm -z yourzonename install).

Third, fix shadow and passwd. Boot your zone and zlogin to it in a single mode (-S), then issue pwconv command and exit.

Finally, after you anyhow login to that thing, reconfigure broken system service once again, like this:

dpkg-reconfigure sunwcsd
svccfg import /var/svc/manifest/system/sysevent.xml

And yes, this is Unix. You should reboot your zone in order it getting working right. :-)