views:

696

answers:

3

I need to batch create images with text. Requirements:

  1. arbitrary size of bitmap
  2. PNG format
  3. transparent background
  4. black text anti-aliased against transparency
  5. adjustable character spacing
  6. adjustable text position (x and y coordinates where text begins)
  7. TrueType and/or Type1 support
  8. Unix command line tool or Python library

So far I've evaluated the following:

The problem with PIL is that e.g. the default spacing for Verdana is way too sparse. I need the text to be a bit tighter, but there's no way to adjust it in PIL.

In ImageMagick I haven't found an easy way to specify where in the image the text begins (I'm using -size WIDTHxHEIGHT and caption:'TEXT'). Adding a transparent border will move the text away from the corner it's achored to, but

  • image size needs to be adjusted accordingly since border adds to the extents
  • it's not possible to adjust horizontal and vertical offset independently

Have I missed some obvious alternatives or failed to find necessary features from the above mentioned?

+2  A: 

(5) indeed looks tricky, short of inserting dummy narrow-spaces into the string (which will break kerning) or using something much higher-level like the SVG or HTML/CSS renderer.

However, if you don't mind getting your hands dirty, it looks quite easy to hack PIL's freetype renderer into adding horizontal space. See _imagingft.c; after the following code in both font_getsize and font_render:

if (kerning && last_index && index) {
    FT_Vector delta;
    FT_Get_Kerning(self->face, last_index, index, ft_kerning_default,
                   &delta);
    x += delta.x >> 6;
}

Add:

if (last_index && index) {
    x += tracking;
}

Try it with a plain integer for tracking (probably quite large judging by that '>>6') first; compile and see if it works. The next step would be to get the tracking value into the C function from Python, for which you would have to change the ParseTuple call in font_render to:

long tracking;
if (!PyArg_ParseTuple(args, "Ol|il:render", &string, &id, &mask, &tracking))
    return NULL;

And in font_getsize:

long tracking;
if (!PyArg_ParseTuple(args, "O|l:getsize", &string, &tracking))
    return NULL;

Then look at what Python interface you want. This is a trivial but quite tedious case of adding the extra 'tracking' argument through each level of the interface, for example:

def truetype(filename, size, index=0, encoding="", tracking= 0): # added optional tracking
    "Load a truetype font file."
    try:
        return FreeTypeFont(filename, size, index, encoding, tracking) # added tracking
    ...

class FreeTypeFont:
    "FreeType font wrapper (requires _imagingft service)"

    def __init__(self, file, size, index=0, encoding="", tracking= 0): # added tracking
        import _imagingft
        self.font = _imagingft.getfont(file, size, index, encoding)
        self.tracking= tracking # add this line

    ...

    def getmask2(self, text, mode="", fill=Image.core.fill):
        size, offset = self.font.getsize(text, self.tracking) # use tracking
        im = fill("L", size, 0)
        self.font.render(text, im.id, mode=="1", self.tracking) # use tracking
        return im, offset

I haven't tested any of this! If it works, might be worth submitting as a patch.

bobince
Neat! To improve upon your idea, it would be useful to be able to change spacing not only in absolute units but also relative to the symbol's normal kerning.
akaihola
Agreed! I haven't even looked at what exactly the units are here, but Doing It Properly at the external API level, units relative to the font size would probably be the best thing.
bobince
A: 

Here's the SVG + ImageMagick solution:

Programmatically create SVG documents based on this template, replacing "TEXT HERE" with the desired text content:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE svg PUBLIC
      "-//W3C//DTD SVG 1.0//EN"
      "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"&gt;
<svg version="1.0" width="152px" height="50px">
  <text style="font-size: 22px; font-weight:bold; font-family: Verdana-Bold;
               letter-spacing: -1.3%;">
    <tspan x="10" y="39">TEXT HERE</tspan>
  </text>
</svg>

Convert the documents to background-transparent PNGs with ImageMagick's convert:

$ convert -background none input.svg output.png
akaihola
+1  A: 

From a quick glance, Pango has support for letter spacing. Pango has Python bindings and is integrated with Cairo.

Torsten Marek
Actually I think I was looking at the documentation for Pango-related features in PyCairo, but evidently I missed letter spacing. Thanks!
akaihola