Repository navigation
feat: createLayout's justify sets lines to fill the width at their word separators after the typesetter breaks them, so a kept typesetter is justified again at another width without shaping anything - #115
Merged
Conversation
…word separators after the typesetter breaks them, so a kept typesetter is justified again at another width without shaping anything `justify` is bits: 1 sets every line that goes on to another to fill `maxWidth`, as CSS's `text-align: justify` does; 2 sets the paragraph's last line and each a forced break ends; 3 sets every line. What a line leaves of the width is shared equally among its spaces and no-break spaces before the white space it ends on, which hangs and takes none, as CSS Text 3 has `text-justify: auto` for the scripts that space their words and as ntk sets it on X11. A line with no separator, or none of the width to spare, keeps the place `align` gives it, and a line an ellipsis ends is not justified. It is not CTLineCreateJustifiedLine. Once a line's spaces have taken some share of their own advance, CoreText spaces the letters as well, 0.2 px a letter on a line of Helvetica with two spaces in it, where a browser widens the separators and nothing else; and it gives the space a line ends on no advance at all. So the line keeps the typesetter's glyphs, and keeps its separators' left edges and widths with the room each takes. Its runs, its carets, its hit tests, `drawLayout`, `drawLayoutGradient` and `layoutCoverage` put a glyph that many shares right of where the typesetter set it as there are separators left of it. A justified line is drawn a run at a time, through the path a line of a scaled face's bitmap glyphs already took. react-x11-components' `<Html>` justifies by giving every space a span of its own and laying the paragraph out again with each spaced by its line's share, through the kerning attribute: a new attributed string, and so a new typesetter, at every width a resize passes. On the Zen Garden's justified designs that was most of every frame. With `justify` the paragraph's kept typesetter is broken and spaced again, and nothing is shaped. test/text-justify.js holds the lines to the breaks they have unjustified and to the width, each space to its share and nothing else to any, a no-break space to a separator and a line without one to its alignment, the bits to their lines, a right-to-left paragraph to its right edge, carets and hit tests to the spacing, a kept typesetter and a font at 2x to the same, and the drawing: the glyphs before a justified line's first space are the unjustified line's pixel for pixel, and its last word ends at the width.
sidorares
added a commit
to sidorares/react-x11-components
that referenced
this pull request
Oct 4, 2026
…text engine where it says it can, where it took three layouts and CoreText shaped it again at every width, so Zen Garden 001 lays a frame of a resize out in 2.7 ms where it took 14 (#695) A justified paragraph was laid out as written, then with every space a run of its own spaced by a hair, then again with each space spaced by its line's share through letter spacing. The last was a new text at every width, which CoreText shaped again: on the Zen Garden's justified designs, 14 to 17 ms of every frame of a resize at 2x on macOS. ntk and react-x11's macOS engine set the lines themselves now, after they break them, and say so with `fonts.justifies` (sidorares/ntk#531, windowkit/appkit#115 and the react-x11 change over it). Where the engine does, a paragraph that nothing reads unjustified is laid out once with `justify`, from the text as written and the shaping the engine keeps; and `justifiedLayout`, under kept spaces, a clamp, a cut, `text-wrap: pretty`, the chunks of a long text and the lines past a float, hands the engine the runs again with the option rather than spacing them. A paragraph with spacers between its inline boxes, and the lines beside a float, which share their room across their pieces, are spaced here as before, and so is every paragraph on an engine that does not say it can. The layout cache and the recorder answer `justifies` as the engine does, and `justify` is part of what a layout is found by. Each design's window dragged from 1180 px to a quarter of that, 6 px every 16 ms, at 2x on macOS, over react-x11 and @windowkit/appkit with the change; the median frame, and the layout in it: | design | frame before | frame after | layout before | layout after | | ------ | ------------ | ----------- | ------------- | ------------ | | 001 | 19.6 ms | 8.2 ms | 14.1 ms | 2.7 ms | | 002 | 19.4 ms | 6.7 ms | 14.7 ms | 2.2 ms | | 004 | 19.3 ms | 6.5 ms | 14.8 ms | 2.2 ms | | 022 | 23.3 ms | 8.0 ms | 16.7 ms | 1.9 ms | | 016 | 28.1 ms | 15.0 ms | 20.7 ms | 8.0 ms | | 020 | 25.7 ms | 18.6 ms | 18.8 ms | 11.9 ms | | 005 | 24.2 ms | 20.3 ms | 16.4 ms | 12.5 ms | What 005, 016 and 020 have left is their text beside floats, spaced a line at a time. On X11, over ntk with `justify`, all 75 designs whose sheets justify pass the Zen Garden bench against Chrome as they did, within a hundredth of a percent of the blocks; on macOS 001, 002, 016 and 022 draw as they did at 1180 px and at 700 but for glyphs a fraction of a pixel apart. A test lays a document out over an engine that says it justifies and counts what it is asked: each justified paragraph once, justified, its spaces as written, `justify-all` and `text-align-last` as their options, and the lines past a float the engine's.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
justifyis bits: 1 sets every line that goes on to another to fillmaxWidth, as CSS'stext-align: justifydoes; 2 sets the paragraph'slast line and each a forced break ends; 3 sets every line. What a line
leaves of the width is shared equally among its spaces and no-break spaces
before the white space it ends on, which hangs and takes none, as CSS Text
3 has
text-justify: autofor the scripts that space their words and asntk sets it on X11. A line with no separator, or none of the width to
spare, keeps the place
aligngives it, and a line an ellipsis ends is notjustified.
It is not CTLineCreateJustifiedLine. Once a line's spaces have taken some
share of their own advance, CoreText spaces the letters as well, 0.2 px a
letter on a line of Helvetica with two spaces in it, where a browser widens
the separators and nothing else; and it gives the space a line ends on no
advance at all. So the line keeps the typesetter's glyphs, and keeps its
separators' left edges and widths with the room each takes. Its runs, its
carets, its hit tests,
drawLayout,drawLayoutGradientandlayoutCoverageput a glyph that many shares right of where the typesetterset it as there are separators left of it. A justified line is drawn a run
at a time, through the path a line of a scaled face's bitmap glyphs already
took.
react-x11-components'
<Html>justifies by giving every space a span of itsown and laying the paragraph out again with each spaced by its line's
share, through the kerning attribute: a new attributed string, and so a new
typesetter, at every width a resize passes. On the Zen Garden's justified
designs that was most of every frame. With
justifythe paragraph's kepttypesetter is broken and spaced again, and nothing is shaped.
test/text-justify.js holds the lines to the breaks they have unjustified
and to the width, each space to its share and nothing else to any, a
no-break space to a separator and a line without one to its alignment, the
bits to their lines, a right-to-left paragraph to its right edge, carets and
hit tests to the spacing, a kept typesetter and a font at 2x to the same,
and the drawing: the glyphs before a justified line's first space are the
unjustified line's pixel for pixel, and its last word ends at the width.