The previous paragraph layout algorithm had a couple of flaws:
- It always produced line break opportunities between runs although on
the textual level there might have been none.
- It didn't handle trailing spacing correctly in some cases.
- It wouldn't have been easily adaptable to Knuth-Plass style optimal
line breaking because it was fundamentally structured first-fit
run-by-run.
The new paragraph layout algorithm fixes these flaws. It proceeds
roughly in the following stages:
1. Collect all text in the paragraph.
2. Compute BiDi embedding levels.
3. Shape all runs, layout all children and store the resulting items in
a reusable (possibly even cacheable) `ParLayout`.
3. Iterate over all line breaks in the concatenated text.
4. Construct lightweight `LineLayout` objects for full lines instead of
runs. These mostly borrow from the `ParLayout` and only reshape the
first and last run if necessary. The design allows to use Harfbuzz's
UNSAFE_TO_BREAK mechanism to make reshaping more efficient. The size
of a `LineLayout` can be measured without building the line's frame.
5. Build only the selected line's frames and stack them.
This creates a smaller state machine helper type for softness coalescing, which does not own the resulting nodes. While this creates a bit more duplication in stack and par builder, it makes it a lot easier to integrate additional logic into the paragraph builder.
Furthermore:
- Line breaks are now "hard", that is, not coalesced with each other.
- Text nodes with equal style are now merged allowing for example `f{}i` to form a ligature.
This adds overridable functions that markup desugars into. Specifically:
- \ desugars into linebreak
- Two newlines desugar into parbreak
- * desugars into strong
- _ desugars into emph
- = .. desugars into heading
- `..` desugars into raw
- New naming scheme
- TextNode instead of NodeText
- CallExpr instead of ExprCall
- ...
- Less glob imports
- Removes Value::Args variant
- Removes prelude
- Renames Layouted to Fragment
- Moves font into env
- Moves shaping into layout
- Moves frame into separate module
Adds top-edge and bottom-edge parameters to the font function. These define how
the box around a word is computed. The possible values are:
- ascender
- cap-height (default top edge)
- x-height
- baseline (default bottom edge)
- descender
The defaults are chosen so that it's easy to create good-looking designs with
vertical alignment. Since they are much tighter than what most other software
uses by default, the default leading had to be increased to 50% of the font size
and paragraph spacing to 100% of the font size.
The values cap-height and x-height fall back to ascender in case they are zero
because this value may occur in fonts that don't have glyphs with cap- or
x-height (like Twitter Color Emoji). Since cap-height is the default top edge,
doing no fallback would break things badly.
Removes softness in favor of a simple boolean for pages and a more finegread u8
for spacing. This is needed to make paragraph spacing consume line spacing
created by hard line breaks.