Last week I implemented
point placements for the new HarfBuzz text shaping/rendering code. This week
my goal was to implement line placements, but I started with something different:
I implemented fonts sets as this was on my TODO list for a long time. Also it
was not very hard, but the implementation
is different from what the old code did.
Currently Mapnik tries to look up a glyph for each character and if it finds one
in the active font is uses this glyph. Otherwise it tries the next font.
With HarfBuzz one can’t use this method, as there is no 1:1 character to glyph
mapping. One character can produce multiple glyphs and several characters together
can result in a single glyph. So the new code instead processes each text run
as one unit. (For font selection a run usually is text with a certain script,
but there are other parameters, too.)
If no font containing all glyphs for a certain run is found the whole run
is suppressed. It could be discussed if it would be better to suppress rendering
the whole text, but this behavior is similar to the old one.
As a next step I looked at the old placement finder code and I was horrified.
The main function for line placements was about 200 lines long and you have to
spend a very long time to understand what exactly is happening there. One of the
biggest problems is that it does all the path processing in the same function
as finding the actual placement.
Some background on paths in Mapnik
In Mapnik a path is an object that implements two functions:
- void rewind(): Go back to the first point in this path.
- unsigned vertex(double &x, double &y): Returns a path command (move to,
line to, end) and the coordinates of the associated point.
As one can clearly see it is a nightmare to use this for placement finding. For
example you can’t go back a step if you need to. You only can start from the
beginning again. While placing glyphs one needs to go forward a certain amount
but with these functions one can only go forward one point at a time.
One point might be
enough but sometimes it is not. So you have to check each time you move forward
how many points you have to skip. At the same time you have to make sure not to
fall of the end of the path (which might crash the program) and not to process
a “move to” command (as no text should be placed across discontinuities in the
In order not to get insane while writing the placement finder code I decided
to write a
class vertex_cache first. This class caches the vertices and
adds functions to advance or go back an exactly defined length.
After having created this class writing the placement finder code wasn’t hard
anymore. The code for line placements is very similar to the code for point
placements. There are some small differences which make it a little bit more
- Rotation angle is dependent on the width of the character at corners (begin
and end have to match the line)
- Multi-glyph sequences where only the first glyph can be placed normally all
other glyphs expect to be placed on a straight line (see this discussion for
While working on line placements I decided to add the function requested
in bug #614 (Optional disabling
auto-upright and selecting which direction is upright). This change shows the
advantage of the new clean design: Only a single line in placement finder had
to be changed! (Some more lines where needed for loading and saving this
attribute from/to XML.)
The current placement finder code doesn’t handle offsets, yet. I don’t want
to use fake offsets as in Mapnik master as this can lead to ugly renderings:
Instead I will try to render on a line parallel to the path.
- Add collision detection to line placements
- Add support for ShieldSymbolizer