ComplexTextController is the class we use to measure and place complex text.
We have two text paths: our fast text codepath and our complex text codepath (which uses ComplexTextController). "Complex text" is defined loosely as whatever FontCascade::codePath() returns. The complex path works for all text (from a correctness point of view) but the fast text codepath is faster, so we use that one whenever we can. Our fast text codepath works when there is a one-to-one mapping from code point to glyph, and placement can be done by simply lining up all the glyphs vertically on the baseline and squishing them together horizontally. Also, because kerning and ligatures are very common, our fast text codepath supports those, but it doesn't support anything more complicated than that.
Measuring and placing complex text heavily involves platform text libraries, which means that ComplexTextController is not platform independent. It has a tight coupling with Core Text. CoreText has a concept of a Font which is actually closer to our concept of a FontCascade - a CTFont includes a "cascade list" which describes which fonts to use when the primary font doesn't support a particular character. It also has a notion of a "line" and a "run," where a line is composed of a sequence of runs and a run contains glyphs at particular positions.
ComplexTextController Input and Ingestion
The input to ComplexTextController is a FontCascade and a TextRun (not a CTRun). The TextRun contains a StringView for the characters we are interested in. Because WebKit's font fallback needs to happen differently than Core Text's font fallback, we first split up the string into chunks (in collectComplexTextRuns()) which we know should be rendered with a particular Font. This is done in string order. Then (in collectComplexTextRunsForCharacters()) we create a CTLine from the characters. Because bidi processing has already happened previously, and because we are fairly sure that the font chosen can render the characters we pass to Core Text, it's likely that the CTLine will only have a single CTRun in it.
The CTRuns inside the CTLine are given to us in LTR order, not string order. Similarly, the glyphs in the runs are also in LTR order, not string order. Because there can be multiple CTLines within the ComplexTextController, and the lines are processed in string order, we iterate through the runs backwards if the ComplexTextController is in RTL mode in order to preserve the fact that we are iterating in string order. When we encounter a CTRun, we interrogate CoreText about it, and record everything we learned about it in our own class called ComplexTextRun (see below for more detail). ComplexTextRun doesn't actually retain any of the CoreText objects - it only retains the information we learned about each run. Then, after we've encountered all the CTRuns, we reverse the order of the entire ComplexTextRun vector if we are in RTL mode which means that, from this point forward, our ComplexTextRuns (and the glyphs inside it) are always in LTR order.
The next step is to run through the ComplexTextRuns and enforce things like justification and letter spacing (in adjustGlyphsAndAdvances()). This is also the place where we flatten each of the ComplexTextRuns into just a few Vectors. We iterate through runs and glyphs in LTR order (which the ComplexTextRuns are already in).
Here is the information that we can gather from each CTRun:
Here are the pieces:
Point p is the point that the client specifies to put the text at. Points r, s, and w are the origins of the glyphs themselves (as in, the points you give to CTFontDrawGlyphs to make the glyphs appear). Vector pr is the “initial advance.” Vectors qt, tx, and xy are “base advances.” Vectors qr, ts, and xw are “glyph origins.”
We want to convert this information into two sequences of information: layout advances and paint advances. Paint advances are the advances you pass to CTFontDrawGlyphs, and therefore, has an “initial paint advance” which points to the first glyph’s paint location. On the other hand, layout advances always start at the origin.
In the example above, the initial paint advance is vector pr, and the subsequent paint advances are vectors rs, sw, and wy. The layout advances are vectors pt, tx, and xy (“pt” is not a typo). This has the nice property that the sum of the paint advances is equal to the sum of the layout advances.
adjustGlyphsAndAdvances() is the place which performs this conversion. There can be multiple ComplexTextRuns represented by the ComplexTextController, and if there are two, they are joined together at points p and y (the leftmost ComplexTextRun’s y point is coincident with the rightmost ComplexTextRun’s p point). When this happens, character C’s paint advance has to be between the leftmost ComplexTextRun’s w point and the rightmost ComplexTextRuns’ r point. The layout advances are unchanged in this situation.
After creating a ComplexTextController, clients can do a couple things with it. First, they can ask for the totalWidth() which is just the vector from p to y above. Second, they can create a sort of cursor into the text, and tell the cursor to advance in string order. After you advance the cursor, you can ask for the runWidthSoFar() which represents the sum of the layout advances you have traversed. You can also pass a GlyphBuffer to advance() which records paint advances.
There are some interesting properties of this, though. First, advance() happens in string order, not glyph order. Remember that glyphs are held in glyph order inside ComplexTextController, which means when you advance in the string, it can be nontrivial to find which run you are currently inside of. Also, the reported paint advances are appended in string order, so if you have an RTL string, you have to reverse() the GlyphBuffer before using it to paint.
Secondly, we only report the initial paint advance when you actually traverse across the leftmost character, which means if your string is RTL you have to consume the entire string in order to get it. This is a problem if you want to know the paint advance (relative to the origin of the entire TextRun) of an arbitrary glyph in the middle of an RTL TextRun. However, you can exploit the fact that the sum of the layout advances is equal to the sum of the paint advances. If you use the totalWidth() to find the distance from p to y in the above image, you can subtract all of the paint advances from y to s, which leaves you with the advance from p to s.