Summer of Code 2012 - Line placements and font sets
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 path).
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 difficult:
- 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 details)
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