Repository navigation
Conversation
Contributor
|
I'll fix CI failures and address comments from users with write access that start with 'Devin'.
|
staging-devin-ai-integration
Bot
force-pushed
the
devin/1791548469-winkin-text-backends
branch
4 times, most recently
from
October 9, 2026 21:22
a4fcea2 to
9ea35f4
Compare
… now The text backend commits that follow move and replace layout/inline.rs and the Parley TextLayout, which #1167 changed. Its blitz-dom part is taken out here so that they apply as written, and is put back for the shared inline layout once they have. The stylo_taffy part stays.
The fork relaxes the gates on properties stylo's servo build declines to compute: sideways writing modes, ruby, text-emphasis, text-combine-upright, line-clamp, ::first-line and the other text properties winkin lays out. They are gated because servo's layout does not implement them, not because they need gecko. It also parses hanging-punctuation, text-spacing-trim, text-combine-upright's digits, text-indent's hanging and each-line, and initial-letter's drop and raise. All eleven crates of the stylo family are patched together, so that Blitz sees one Atom type. The fork computes every font-variant-caps value and white-space-collapse: preserve-spaces, so Parley maps them too.
The `winkin` feature of blitz-dom and blitz-paint lays out and paints every inline formatting context with winkin in place of Parley. Text inputs, selection and hit testing still use Parley, and with the feature off nothing changes. blitz-dom builds each inline formatting context's content from the box tree and its computed styles (text_winkin), keeps the layout on the node's TextLayout and breaks it in place, and sizes and places the block, its atomic inlines, floats and the static positions of its out-of-flow boxes from winkin's lines (layout/inline_winkin.rs). It handles vertical and sideways writing modes, ruby, emphasis marks, text-combine-upright, ::first-line, ::first-letter and initial-letter, line clamping, hanging punctuation, text-autospace and the rest of the text properties the stylo fork computes. Floats placed by Taffy narrow the lines, and floats the text reaches are placed as the lines are broken. @font-face faces are added to winkin's font collection with every descriptor, and font-variant-alternates is resolved through @font-feature-values. Inline elements answer their client rects from winkin's box fragments. blitz-paint paints winkin's runs, inline box backgrounds and borders, decorations (with Blitz's decoration painting, including those a block's ancestors propagate to its lines), emphasis marks, text shadows and text-overflow marks (text_winkin.rs). winkin and fontwich are path dependencies on the ifc workspace for now.
HTML's auto directionality: an element with dir=auto takes the direction of the first character of its text with a strong bidi class, skipping text inside bdi, script, style and textarea and inside descendants with a valid dir of their own. With no strong character the inherited direction stands. Blitz sets it as a presentational hint for `direction`, since no selector can express it. Before, dir=auto kept its isolation but always took the inherited direction.
The rules for these used Gecko's :-moz-has-dir-attr and :-moz-dir-attr-like-auto, which Blitz's selector parser does not know, so they were dropped. bdo is now isolate-override with or without a dir attribute, "bdo[dir]" outweighing the "[dir]" rules as the Gecko selector did, and pre and textarea with dir=auto are plaintext, by the HTML spec's selector.
As CSS Text 4's default style sheet does.
The screenshot example renders the document's own height (up to 4000 CSS pixels) rather than at least the viewport's, and writes the PNG to the path given as its third argument, where there is one.
It lays out and paints document text with winkin rather than Parley, so the suites can be run against either engine.
…atic positions from it An absolutely positioned box in inline content goes to winkin through LayoutBuilder::absolute, with its original display, in place of an empty box that took part in line breaking. The static-position rectangle of each OofCandidate comes from Layout::static_positions: an inline-level box's is zero-wide where its anchor stands and spans its line box from the top, facing the way the anchor's bidi level reads, as in Chrome; a block-level box's spans the content box at the line's top, or below the line where in-flow content comes before it. A box on no line, in an empty block or after a final <br>, gets a static position too. In a vertical-rl block the box's block-start edge is its right. The painter no longer passes over placeholder boxes, since there are none.
Every document built its own fontwich::Collection::system(), which lists the platform's fonts by reading every installed font file, and starts with cold font and fallback caches. That cost about 200ms per document. DocumentConfig::winkin_fonts now takes the collection, as font_ctx takes Parley's. Clones share the layers, the fonts loaded through them and fallback's answers, so an embedder lists the fonts once and hands each document a clone. An iframe's document is handed its parent's. Without one, a document lists the platform's fonts itself, through the new text_winkin::system_fonts. blitz-dom re-exports fontwich for embedders.
The runner already hands every document a clone of one Parley FontContext; it now does the same with winkin's font collection. The css suites run in about 50s with the winkin feature, down from about 400s, with the same results.
… pages A tab's DocumentLoader lists winkin's fonts once, next to its Parley FontContext, and hands a clone to each page, error page and view-source document it builds. make_doc_config becomes a DocumentLoader method, since everything but the base URL and abort signal came from the loader.
…ackend compute_inline_layout_inner now resolves the container's sizes into a Frame and hands it to compute_inline_layout_parley or compute_inline_layout_winkin. The Parley half is no longer unreachable code behind a blanket allow, and winkin no longer runs Parley's inline box measuring loop before its own.
winkin builds its own content and never breaks the Parley layout into lines, so building and shaping it was unused work. The ids of the atomic inlines and floats winkin measures now come from the inline root's layout children.
… not Parley's clusters
compute_inline_layout_winkin is a LayoutPassState method, as the Parley path is, and builds winkin's content through the document it borrows.
innerText walks the inline layout's content in logical order through TextLayout::logical_content, and Selection maps a text node's offset through TextLayout::source_offset where the backend maps source text, so both work under winkin, which builds no Parley layout. Under Parley they read the same clusters and follow the same collapsing as before.
…iting-mode winkin turns an inline formatting context's lines onto the page itself, so with the writing-mode feature such a box runs in physical axes (Node::layout_frame_wm): Taffy's frames meet it only at its edges, where a vertical parent's inline size reaches it transposed, and its atomic inlines, floats and the boxes around it are read in physical axes. With the frames, a vertical block finds definite room along its lines far more often. A block in a block container whose lines run the same way stretches into it; any other box fits its content into it, and answers an intrinsic height query with its lines' intrinsic length. An orthogonal flow fits into its parent's definite height, or else into the initial containing block, which also caps a parent's max-height.
A link to Node::style, which does not exist, now names Node::primary_styles; offset_parent's docs no longer link to a private method; a bare URL becomes an automatic link, and two links drop a redundant target.
blitz-dom gains a text module that holds what Blitz asks of a text backend: the fonts documents lay text out with (TextFonts), a document's text context (DocumentText), an inline formatting context's layout and readback (InlineText, InlineLayoutEngine), form-control editing (EditableText, EditEngine, with an Edit enum of editing operations) and outside list markers (MarkerEngine). Aliases name the backend's types: FontContext, TextLayout, TextEditor, MarkerLayout and FaceDescriptors. text::parley implements them with the code that was spread through the crate: building the Parley layout, breaking and placing its lines, PlainEditor, the font metrics provider, @font-face registration and the Stylo conversions. The rest of Blitz now goes through the boundary: the document keeps one TextContext; deferred construction and inline layout call the backend; the key, IME and pointer handling of text inputs speak Edit; hit testing, selected text, innerText, Selection offsets and inline fragment rects read InlineText. blitz-paint's text painting splits into the decorations any backend's lines share and a Parley module behind paint_inline_layout, paint_text_input and paint_marker, selected by blitz-dom's cfg_text_backend! macro. Behaviour is unchanged. This is the fork's copy of the commit: it also carries the fork's winkin hooks, so the winkin feature builds here. The fork's winkin feature lays text out with winkin inside Parley's TextLayout, and those hooks move behind the boundary with the Parley path: the Parley TextLayout carries the winkin layout, skips the Parley build under winkin, and answers the InlineText readback (text length, selected text, logical content, source offsets, hit testing, selection and fragment rects) from winkin, with the helpers that did this in node/text.rs moved to text_winkin. InlineLayoutEngine says whether the backend sets vertical lines itself (SETS_WRITING_MODES), which an inline root's layout writing mode reads.
InlineText::logical_content yields one item for each stretch of text one node holds, as a byte range of text(), rather than one item per cluster with a copy of its text, and innerText pushes each stretch at once. The walk reads the cluster's style only where its style index changes, and innerText of a long paragraph of spans takes less time than upstream's loop over the clusters. InlineText::selected_text returns the pieces of the laid-out text it is made of, which get_selected_text appends without copying them first. selection_rects becomes for_each_selection_rect, which hands each rectangle to a callback, and blitz-paint's selection highlight goes through it. hit_test takes the point and whether it must be exact, and no longer the content box size and scale, which hit testing computed on every call and Parley ignored; the winkin path records the content box it places lines in, which its hit testing and selection rectangles map through. first_baseline gives the first line's baseline in CSS pixels, which places outside list markers. EditableText::metrics gives the laid-out text's size and scale at once, in place of size and scale, which each looked up the layout, and text borrows the text unless an input method is composing. The setters of EditEngine only record what changes, and the text is laid out once, by the next refresh or edit: a single-line input at construction, a textarea in the layout pass once its width is known, and a value set from outside at once. Construction and scale changes no longer lay a textarea out twice, and setting the same width again lays nothing out. selectLine: and selectParagraph: are one Edit::Select of two motions. FaceDescriptors holds parlance's FontWeight and FontStyle, the font value types fontique shares, as the rule's lowest and highest values, read from the @font-face rule in one place; Parley registers the face by the lowest, with no conversion. blitz-dom takes parlance from Parley's revision, which the lockfile already holds. TextFonts::add_fonts takes Bytes, so fonts that need no decoding are not copied. cfg_text_backend! takes an arm for any backend and expands the one in use, so callers may list backends this build does not know. TextLayout keeps only its Parley layout public; the never-read content widths cache goes, with invalidate_content_widths. FontContext's documentation says it is the backend's own type. The thin forwarding methods are #[inline].
…t backend The walk that turns an inline formatting context's DOM into a text backend's content moves out of the Parley backend into layout::inline::build, and drives a new InlineBuilder trait: spans (plain, display: contents, and ruby containers and annotations where the backend sets ruby), text, atomic inlines, floats, absolutely positioned boxes, forced line breaks, <wbr> opportunities and an inside list marker. Blitz decides what each node is: hidden inputs, display: none, the pseudo-elements, <br>, the elements that are atomic inlines, and text-transform, which it applies for a backend that does not (TRANSFORMS_TEXT), forcing word boundaries at atomic inlines and line breaks as before. Parley's TreeBuilder implements the trait. An element with no computed style is now skipped rather than laid out as an unstyled inline; behaviour is otherwise unchanged.
PlainEditor marks the composing (preedit) range of an <input> or <textarea> with an underline in its layout's styles, but the text painter drew only CSS decorations, so composing text was not underlined. The painter now draws it, solid, with the auto thickness and in the text's colour, once per line across the composing runs.
With the winkin feature, text::winkin is the text backend in place of text::parley, implementing the same traits: its TextLayout holds the inline formatting context winkin builds, breaks and reads back; its TextContext holds the fonts and the context winkin lays out in, with @font-face faces in a document layer; FontContext wraps the fontwich collection documents are handed. The Parley types no longer carry winkin inside them, and the document builds no Parley state under winkin. winkin's content is built by Blitz's walk of the DOM, through its own InlineBuilder: spans with their ::first-line restyling, ruby containers and annotations, the block's ::first-letter, emphasis marks, inside list markers, atomic inlines and floats at the sizes Taffy measured, the anchors of absolutely positioned boxes, and <br> with its clear. It sets text-transform and white space itself, from the styles, so the walk hands it each text node's text as the DOM holds it. The content is built when it is laid out, once its boxes are measured, and again where they measure differently. Its lines are laid out by Blitz's inline driver, which takes what was winkin's own copy of it: the driver measures the boxes, sizes the block, keeps the lines clear of floats through the same room, places the boxes and static positions, and reads winkin through the layout surface, which grows what winkin needs: the room over the first line that ruby annotations and emphasis marks may take, lent by the block before (READS_ROOM_ABOVE, room_below), the content box's end that line-clamp keeps the lines within, rewinding the floats a trial break placed, lines that run down the page (line_flow, place_on_page, with the driver sizing such a block in physical terms), static positions that face the way their bidi level does, floats without a node of their own (the initial letter), and position: relative on the inline boxes around an atomic inline (inline_shift). Where winkin's copy differed from the driver, the driver's rule holds: a display: contents element with position: absolute is contents; the static positions of absolutely positioned boxes move with align-content; only position: relative offsets an atomic inline by its insets; the floats an inline formatting context places hand on the absolutely positioned boxes they hold; and the content's scrollable overflow reaches as far as its longest line. What winkin lacked before is filled in on its side of the boundary: Stylo's font metrics (ex, ch, cap, ic) come from the fontwich collection the text is laid out with, outside list markers are laid out and painted by winkin, and <input> and <textarea> get a basic editor: the value laid out as plain text, with a caret and a selection moved by winkin's selection motions. DocumentConfig::winkin_fonts goes: winkin's fonts are DocumentConfig's font_ctx, as Parley's are. blitz-paint paints winkin through the same paint_inline_layout, paint_text_input and paint_marker functions. FaceDescriptors gains the other descriptors fontwich declares a face by: the width range, unicode-range, the feature and variation settings, size-adjust and the metric overrides, which Parley leaves aside. winkin converts them, with the weight and style, to fontwich's own.
…efault blitz-dom's winkin feature now sits beside its parley feature as a text backend of its own: build.rs reads the two into one cfg, text_parley or text_winkin. With both features Parley is used, and with neither blitz-dom stops with its compile error. system-fonts maps to fontwich's system fonts as well as Parley's, and complex-scripts to winkin's ICU dictionaries as well as Parley's complex scripts. blitz-paint no longer depends on winkin itself: it takes winkin, as it takes Parley, from blitz-dom, through cfg_text_backend! and blitz-dom's re-exports, so the two never disagree. Every crate that forwards parley forwards winkin too, with parley in its defaults: blitz, blitz-shell, blitz-html, blitz-paint, blitz-vibey-script, blitz-test-harness, dioxus-native, dioxus-native-dom, the browser, readme, the WPT runner, the tests and the examples. The CI job checks the default build, winkin alone, both, and that neither fails to compile.
A textarea's text was set as preserved white space, wrapping, whatever its style said. Under break-spaces its spaces now wrap as break-spaces has them, and it wraps as its text-wrap-mode does; only the collapsing modes, which would move offsets away from the value's, are still preserved.
Text an input method is composing is laid out in a winkin <input> or <textarea> in place of the selection, in a box of its own keyed by the control's node with COMPOSE_KEY set, which the renderer underlines. The selection follows the input method's cursor inside it, and the caret is hidden where the input method gives none. Any other edit, a cancelled composition and the input method being disabled return the text and the selection to what they were before composing began; a commit then replaces that selection. raw_text holds the composing text and text does not, as with Parley; the selected text is none while composing. The editor keeps the history the ifc editor example keeps: each change to the text records the text and selection before it, up to a hundred, and a new change drops what could be redone. Edit gains Undo and Redo, which the key handling of text inputs sends for Ctrl+Z (Cmd+Z on macOS), Ctrl+Shift+Z, Ctrl+Y off macOS, and the Undo and Redo keys, firing an input event where the text changed. Parley's PlainEditor keeps no history, so they do nothing under Parley. A value set from outside the editor drops the history and any composition. Deleting at the text's ends changes nothing and records nothing, and an undone deletion returns the caret where it was.
Typing, line breaks, deletion by character, grapheme and word, word motion, selection, select-all, copy, cut and paste through the shell's clipboard, undo and redo, and composing text committed, cancelled and composed over a selection, driven through key, IME and clipboard events. Where the backends differ, each is held to its own: winkin's editor undoes and Parley's does not, backspace after a combining mark takes the grapheme under winkin and the mark under Parley, and only winkin returns to a selection a cancelled composition replaced. Under winkin alone, the composing text is checked to be laid out as one underlined box.
The inline driver measures the atomic inlines of an intrinsic inline-size pass along the line only, with no block size or baseline, so winkin found them changed and built its content again for every such pass, and again for the next pass that sets the lines. Content built with boxes as long now serves an inline-size pass, all its intrinsic inline sizes depend on. Where it is built again there, each box keeps the block size and baseline it was last built with, which the next pass that sets the lines checks, and builds again with where they differ.
The containing block's inline size is the basis winkin's styles resolve percentage margins and padding of inline boxes against, and the build was keyed on it. Taffy's intrinsic passes run with an indefinite width (basis 0) and its layout pass with a definite one, so every inline formatting context was built again for the layout pass even where no percentage read the basis. The build now records whether any percentage was resolved against the basis, and keys on it only then. On a page of 510 nested inline-blocks holding block children this cuts winkin builds per resolve from 4133 to 1654.
An intrinsic inline-size pass measures atomic inlines along the line only, so content first built there has no block sizes or baselines for them, and the pass that sets the lines found them changed and built the content again. Where only atomic inlines' block sizes and baselines differ, the content now takes them through winkin's Layout::set_atomic_sizes, in one call for every box whose size changed, which measures their extents again and keeps the analysis, fonts and shaping. Anything else, and anything winkin refuses, still builds again; a refusal changes nothing. With the basis change before it, every inline formatting context on a page of 510 nested inline-blocks is now built once per resolve.
Stylo renames the by-value longhand getters to `slow_clone_*`, takes a mutable `ParserContext` in `parse_into` and `SupportsCondition::eval`, renames `OpaqueElement::from_non_null_ptr` to `from_ptr`, drops the `layout.grid.enabled` pref and the legacy masonry track list, adds `display: grid-lanes`, computes shape() axis positions to length-percentages, gives `mask-origin` the `background-origin` type, names `-moz-none` text decorations `None` and stores font settings in a `ThinVec`.
Stylo now quotes a font-family name only where it would not parse back as identifiers, as the CSSWG resolved and the font-family parsing WPTs expect.
Takes winkin and fontwich at cc23131, which adds text security.
Stylo's initial font-family is serif, which winkin resolved through the serif setting. Chrome sets text with no font-family in the standard font, and for Japanese, Chinese and Korean the two differ: on Windows lang=ja takes Noto Sans JP as standard and Noto Serif JP as serif. Noto Serif JP kerns katakana pairs such as テス, so emphasis tests whose ruby reference splits the text drifted by 0.64px. The initial value is told apart by its shared list: a serif a style sheet names is parsed into a list of its own. With no family, winkin goes straight to the language's standard font.
Both backends lay it out as word-break: normal with overflow-wrap: anywhere.
…nline layout main computes, in layout/inline.rs, the out-of-flow positions an inline span is a containing block for, and stores them on the Parley TextLayout. The text backend commits replace both, so the same is done in layout/inline/lines.rs as the inline boxes are placed, and each backend's TextLayout holds the flags, which Node::layout_style reads as on main.
staging-devin-ai-integration
Bot
force-pushed
the
devin/1791548469-winkin-text-backends
branch
from
October 10, 2026 14:33
7250db5 to
b26a49a
Compare
This branch has not been deployed
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.
Summary
This is @dfrg's
text-backendsbranch atfeb319e5, rebased linearly ontomain(7832c177), with four commits that are not his: one before his commits, three after. It is opened here to get CI and WPT results on the complete winkin integration; it is not ready to merge as is (see "Open before merging").What the branch does
blitz-dom/src/text/mod.rs: per-backend types (TextLayout,TextEditor,MarkerLayout,FontContext) and traits (InlineText,InlineBuilder,InlineLayoutEngine,LineExclusions,EditableText,EditEngine,MarkerEngine,DocumentText). The Parley path moves totext/parley/, and painting toblitz-paint/src/text/parley.rs.blitz-dom/src/layout/inline/{build,lines,floats}.rsand are written once for every backend.text/winkin/andblitz-paint/src/text/winkin.rsimplement the same traits with winkin and fontwich, including hit testing, selection,innerTextand text inputs.parley(default) andwinkinare cargo features, forwarded throughblitz,blitz-paint,blitz-html,blitz-vibey-script,dioxus-native, the browser and the WPT runner.blitz-dom/build.rsturns them into onetext_parley/text_winkincfg; with both enabled Parley is used, with neither it is a compile error.ci.ymlchecks Parley, winkin alone, both, and neither.Commits that are not from the branch
blitz-dompart is taken out. Let an inline root claim abspos boxes on behalf of its inline spans #1167 changedlayout/inline.rsand the ParleyTextLayout, which the branch moves and replaces. Taking those three files back to their state before Let an inline root claim abspos boxes on behalf of its inline spans #1167 lets the branch's commits apply as written. Thestylo_taffypart of Let an inline root claim abspos boxes on behalf of its inline spans #1167 stays.inline_span_cb_flagslives inlayout/inline/lines.rsand is called as the inline boxes are placed (horizontal and vertical paths); each backend'sTextLayouthas aspan_cb_flagsfield, whichNode::layout_stylereads as onmain.wpt.ymlbuilds and runs the WPT runner with--no-default-features --features cpu,winkin, in place of the Parley run, so the WPT comment on this PR compares winkin againstmain's Parley results. The job timeout goes from 15 to 25 minutes. This is temporary and needs rethinking before merge: as written, a push tomainwould publish winkin results to the dashboard.just browink, which runs the browser with the winkin backend.In the rebased commits, the only conflicts were in
Cargo.tomlandCargo.lock:parley/parlancecome from crates.io (0.12 / 0.1) as in #1159, and the lockfile ismain's with the winkin, fontwich and Stylo fork entries resolved on top.Open before merging
winkinandfontwichare git dependencies pinned to a rev, and all Stylo crates are[patch]ed todfrg/stylobranchstyle-more(unpinned).dir=autodirection from the first strong character, UA bidi rules forbdo/pre/textarea,text-autospacein the UA sheet, and the screenshot example changes.build.rscfgs vs plain featurecfgs vs runtime selection).text-overflow. winkin handlesellipsisitself; Implementtext-overflow(continues #893) #1015 implements it for Parley and would need rebasing onto the boundary.Locally only
cargo checkofblitz-domandblitz-paintwas run on the final tree, with each backend; tests and WPT run in CI.Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/93190c0235454579ac1505b20ed27dc8
Open in Devin Desktop: https://dioxus.staging.devinenterprise.com/desktop/session/93190c0235454579ac1505b20ed27dc8?variant=devin-insiders
Requested by: @nicoburns
WPT results
Subtests: 3323 newly passing, 348 newly failing (net +2975). Crashes: -1.
Subtest changes by area (139 areas)
Full diff (2035 changed tests)
Generated by the WPT workflow.