How text works in Inkscape
This is a document for developers wishing to understand the mechanics of how Inkscape deals with text from the file to the screen.
The internal structure of Inkscape closely follows the structure of the SVG spec, so let us begin with the two example SVG files we will be looking at.
1. Plain text
<text x="100" y="100"> <tspan x="100" y="100" style="font-size:12" sodipodi:role="line">one <tspan style="fill:red">two</tspan> three</tspan> <tspan x="100" y="120" sodipodi:role="line">four</tspan> </text>
This will look like:
one two three four
when rendered. Note that the x/y attributes on tspans are calculated by Inkscape based on sodipodi:linespacing attribute, the font size, and the x/y on the parent text. Other SVG renderers will obey these x/y and render the second line in the correct position, even though they ignore sodipodi:role="line" which tells Inkscape that this tspan is not inline but represents a separate line. Thus, though SVG does not have separate text lines per se, Inkscape emulates them via tspans.
2. Flowed text
<flowRoot> <flowRegion> <use xlink:href="#path1301"/> </flowRegion> <flowDiv> <flowPara> this is some text </flowPara> </flowDiv> </flowRoot>
This will look like that text flowed inside whatever shape has id="path1301".
There is an almost 1:1 mapping from XML nodes into SPObjects, and a 1:1 mapping, occasionally drifting into a many-to-one, from SPObjects into the files containing the implementations of said objects.
For example 1 we have SPText and SPTSpan (in sp-text.cpp and sp-tspan.cpp).
For example 2 we have SPFlowtext (flowRoot), SPFlowRegion, SPFlowdiv and several other minor SPFlow* types that all basically store different kinds of text (and are all in sp-flowdiv.cpp).
Because all of these inherit from SPItem (see the hierarchy diagram here) they all implement the 'show' method for creating an NRArena object which is the final form passed to libnr, the New Rendering Library. Incidentally, like anything with 'new' in its title, libnr is quite old.
But we're getting ahead of ourselves.
All SPObjects have an associated SPRepr which is the XML node. It is basically a tie between which contains the master copy of the data - the SPObject or the SPRepr. In general they're kept very tightly synchronised whenever a change is made.
This article will not further discuss the storage of XML representations.
Internal storage of the SPObjects
This is, of course, completely up to the implementation, but everything except SPFlowRegion contains some variant on a one_flow_src, named 'contents', in which it stores its text and/or formatting. Notice that each node can only possess one style.
one_flow_src and friends
This is the first foray into libnrtype/, the heart of the text layout engine.
one_flow_src is not much use by itself, it is very nearly just a base class containing a doubly-linked list. The three inheritors represent the types of data stored in said linked list:
control_flow_src: control codes, ie line and region breaks. This contains only one member defining the type of break.
text_flow_src: a piece of text. It contains a wrapper for the text itself and a lot of extra housekeeping information which, as far as I can tell, is only used internally by libnrtype.
div_flow_src: a style specification. This has a pointer to an SPStyle class and a few extra fields such as vertical layout and individual character positioning information that are not available in SPStyle.
So, link these into a list and you can specify any string of formatted text. Individual SPObjects, however, can't represent arbitrarily formatted text so they either use a single div_flow_src followed by multiple instances of the other two or, for SPObjects which can't even do styling, just multiple instances of the other two.
If each SPObject were left to do its own rendering, the innermost nested ones would have a great deal of difficulty with placement because they would need to know the sizes and properties of their parent items in order to append their text in the correct place; this would quickly become a nightmare of inter-dependencies. In order to avoid this the inner node types (tspan, flowpara, etc) have no SPItem::show method and rely entirely on their parents to understand there are children that require rendering.
The SPText and SPFlowText objects contain a flow_src which, despite its similar name, does not inherit from one_flow_src but rather contains a list of them. It is intended to be an opaque class, and uses one_flow_src::DoFill() to append a single list of one_flow_srcs. It is the responsibility of the caller to iterate over all the necessary lists; in SPText's case this is done by the recursive function sp-text.cpp:TextReLink().
The next overall stage in the rendering pipeline is to convert the flow_src from a string of characters with formatting instructions into a series of individually placed glyphs. You should read a good overview of typography if you don't understand glyphs, runs, right-to-left rendering and all that.
In order to produce the flow_res, which stores the resulting glyph information, two inputs are required. The first is a flow_src containing the text to process, the second is an optional flow_dest which contains the destination Shape inside which to wrap the text.
The actual processing is done by instantiating a flow_maker object with the two parameters flow_src and flow_dest. Once instantiated, there are a few flags which can be set to control such things as indenting and full justification, then either Work() or TextWork() is called.
TextWork() is the simpler of the two and must be used if you don't have a flow_dest and hence aren't doing wrapping. Work() should be used for full processing.
The outputs of the two differ in that each line of glyphs produced by TextWork() has its top left corner at (0,0). The glyphs produced by Work() are all correctly positioned with the top left of the containing flow_dest at (0,0). This needs fixing.
It's worth discussing the finer points of the flow_res class because much of the cursor positioning, selection highlighting, and so on is dependent on the glyph structure not the character structure.
A flow_res object contains six useful arrays. In order of increasing number of glyphs in a single item, they are:
- glyphs: An array of flow_res::flow_glyph which contains the individual glyphs required by the font rasteriser, along with their positions relative to their containing letter
- letters: An array of flow_res::flow_styled_letter. A letter is a conceptual collection of glyphs which is best understood as being the minimum object which can be independently rotated for the purpose of putting text on a curve. "á", for example, could be rendered using two glyphs ("a" and "'") but these glyphs should be treated as a letter and never independently positioned (most fonts will contain an a acute glyph - it's just an example, OK?). A letter has an absolute position, a rotation, and lots of other useful information.
- spans: An array of flow_res::flow_styled_span. A span is the largest continuous collection of glyphs that are all the same font, style and directionality.
- groups: An array of flow_res::flow_glyph_group. I think a group is the same as a span. It is present to optimise the conversion to libnr objects, but may be useless. Investigation required.
- chunks: An array of flow_res::flow_styled_chunk. A chunk is a continuous run of spans which meet each other end-to-end. This is almost the same as a line of text on a page, but if a flow_dest has a hole in the middle then there can be a gap in the centre of a conceptual line, in which case there will be one chunk either side of the gap.
All of these arrays contain variables for getting your location inside one or more of the other arrays. A single flow_styled_chunk, for example, contains glyphs from g_st to g_en, letters l_st to l_en and spans s_st to s_en.
- chars: The original UTF-8 string you used in the flow_src, minus formatting.
Once you have a flow_res you can call flow_res::Show() and get an NRArenaGroup out of it. libnr can be a topic for another day (and another author?).