avm2: Implement the Flash Text Engine (flash.text.engine) - #24153
Open
falpi wants to merge 1 commit into
Open
Conversation
falpi
force-pushed
the
add-font-text-engine-implementation
branch
2 times, most recently
from
July 7, 2026 23:32
9fe104a to
46782d5
Compare
Replace the stubbed flash.text.engine classes with a working implementation of line breaking, metrics, line management, navigation, and justification. Line breaking. TextBlock.createTextLine and recreateTextLine break the block's content into TextLines by reusing the core text layout engine. The content element tree is flattened into text plus format runs -- GroupElement recurses over its children and GraphicElement contributes a U+FDEF placeholder -- the remaining text is laid out at the requested width, and the break point for the new line is taken from the start of the second laid-out line. Each TextLine delegates its own layout and rendering to a backing EditText that holds just that one line. Metrics. TextLine exposes real ascent, descent, textWidth and textHeight extracted from the backing field's layout, with the line origin placed on the baseline as in Flash Player. Line management. Lines are wired into a first/previous/next/last chain, and textLineCreationResult reports success, complete, insufficientWidth or emergency. Assigning TextBlock.content or bidiLevel invalidates the previously broken lines; firstInvalidLine walks the chain to the first non-valid line; lastLine returns the last broken line; and releaseLines detaches an inclusive range of lines, relinks the surrounding chain, and invalidates every line that followed the released range. The TextLine.validity state machine matches Flash Player, including the restricted transitions that raise ArgumentError ruffle-rs#2008. Navigation and hit-testing. TextBlock.findNextAtomBoundary, findPreviousAtomBoundary, findNextWordBoundary and findPreviousWordBoundary locate atom and word boundaries in the content text. TextLine implements getAtomIndexAtPoint, getAtomIndexAtCharIndex and getAtomBounds (including the trailing paragraph terminator), which the Text Layout Framework relies on to place and move the caret. Justification. A paragraph whose block carries a SpaceJustifier requesting justification is rendered by switching the backing field to Justify alignment and spreading the line to the requested width.
falpi
force-pushed
the
add-font-text-engine-implementation
branch
from
July 8, 2026 09:26
46782d5 to
1610468
Compare
Member
|
See: #23879 |
Author
I guess a lot of people are working on this... let's hope that sooner or later we manage to get decent support for FTE in the mainstream. |
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
flash.text.engineshipped as a type-complete but inert package: the classes and constant enums existed, andTextBlock's storage was recently nativized, butTextBlock.createTextLinewas a stub andTextLine's metrics and hit-testing were stub getters (there was no nativetext_line.rsmodule at all). Anything built on FTE — the wholeflashx.textLayout(Text Layout Framework) stack that SparkRichText,Label,TextInputandTextAreacompose their text with — rendered nothing usable.This PR adds the composition engine on top of the native
TextBlockobject. Spark text controls now compose, wrap, measure, justify and edit real text, with a working caret and word selection.What this adds over upstream
createTextLine/recreateTextLineTextLinemetrics —ascent/descent/textWidth/textHeightgetAtomIndexAtPoint/getAtomBounds/getAtomIndexAtCharIndex/getAtomWordBoundaryOnLeftfirstLine/lastLine/firstInvalidLine,nextLine/previousLinelastLinestub, no walkable chainreleaseLines#2004validation, relinkcontent=/bidiLevel=line invalidationSpaceJustifier)findNext/PreviousAtomBoundary,findNext/PreviousWordBoundaryTextLine.validitystate machine#2008)The enum/constant classes,
ElementFormat,FontDescription,FontMetrics, the justifiers,TabStopand the content-element types are untouched — they were already present. The contribution is ~1,000 lines concentrated inTextBlockandTextLine.How it works.
break_text_lineflattens the content-element tree into text + format runs, lays the remaining text out at the requested width with the existing layout engine, and takes the break at the start of the second laid-out line; eachTextLineis backed by anEditTextholding just that one line. A line's origin is its baseline start, so the backing field is re-homed by(-gutter, -(gutter + ascent))and display coordinates + hit-testing match Flash Player. (Cost: laying out the remainder to take one break is O(N²) in the block's text length — fine for typical FTE content, a TODO for very large blocks.)Known limitations / residual gaps
The composition path is implemented; the following remain stubbed or approximated (a member-level audit against the Adobe surface: of ~228 public members, ~165 real / ~31 partial / ~18 stub / ~14 missing — though most "real" members are the pre-existing enums and native storage, not this PR):
GraphicElementis stubbed;hasGraphicElement/getAtomGraphicinert;totalAscent/Descentcollapse to ascent/descent.TextLineMirrorRegionis an empty shell;mirrorRegionsabsent.hasTabshardcoded false,lineOffsetignored).bidiLevelstored + invalidates, but no reordering pass; visual order always equals logical order.lineRotation/textRotationstored, no rotated layout.ElementFormattypography —alpha,baselineShift,typographicCase,digitCase/Width,ligatureLevel,dominant/alignmentBaseline, per-runtextRotation,localeare stored/validated but not applied to the glyph; only font/size/color/weight/kerning/tracking reach the layout.ElementFormat.getFontMetricsfabricates fromfontSize;FontDescription.isFontCompatiblealways false;isDeviceFontCompatiblemissing.SpaceJustifierspacing envelope andEastAsianJustifierstyles are inert.recreateTextLinereuses geometry but doesn't reset the recycled display object's state;releaseLineCreationData/getTextLineAtCharIndexabsent.A left-to-right business application rarely reaches these; rich, multilingual or vertical layouts do.
Testing
Automated.
avm2/textline_validity— the validity state-machine test — is taken off the known-failure list (real transitions +#2008).textblock_createline_fte,textblock_properties,text_engine_fontdescription,text_engine_groupelement,textline_inapplicable_properties.Manual — Spark component showcase. A single-page Spark showcase (the main Spark controls, plus a few
mx-legacy ones, instantiated with sample data in labelled panels; a status label driven by interaction handlers also verifies event routing through the Spark skins). Because all Spark text is composed byflashx.textLayouton top of FTE, every labelled control exercises this PR. All of the following render correctly and match Flash Player (glyph placement, line-box heights and baselines, caret geometry), and interactions behave correctly:s:Label,s:RichText(justified paragraph, word wrap),s:TextInput(text entry, click-to-place caret, keyboard navigation, drag and double-click word selection),s:TextArea(multi-line editing with embedded newlines, selection across wrapped lines, scrolling).s:Button,s:ToggleButton,s:CheckBox,s:RadioButton,s:ButtonBar,s:TabBar;s:DropDownList,s:ComboBox(editable),s:List;s:DataGrid(Spark) column headers and cell renderers;s:NumericStepper; ands:Paneltitles,s:BorderContainerands:TileGrouplabels.s:Spinner,s:HSlider,s:VSlider,s:HScrollBar; thes:Rect/s:Ellipse/s:Groupprimitives; and thes:Scroller/VGroup/HGrouplayout containers.mxcomponents (theTextFieldpath — unaffected by this PR, verified to still render alongside):mx:ProgressBar,mx:ColorPicker,mx:DateField,mx:DataGrid.Interaction handlers (button clicks, toggle/checkbox/radio changes, list and dropdown selection, stepper/slider/date changes) update a live status label, confirming event routing through the Spark skins works — not just static rendering.
visual/definefont4— expected divergenceThis PR changes how TLF content is laid out and rendered:
TextLines are nowindividually positioned by TLF using the real font metrics (previously the
stub values
ascent = 12.0/descent = 3.0), and each line renders with itsorigin on the text baseline, like in Flash Player. As a result the output of
visual/definefont4no longer matches its reference image, which was capturedwith the previous behavior (whole paragraph laid out as a single word-wrapped
EditText): the text is shifted by roughly oneEditTextgutter (2 px), plusa ~1 px difference in line spacing.
The rendering is otherwise identical (same glyphs, same rasterization — the
first line matches the reference pixel-perfectly once the shift is accounted
for). Since the divergence is a consequence of the more accurate line
placement rather than a rendering bug, the test is marked with the existing
known_failure = trueconvention on its image comparison, with the currentoutput pinned as
output.ruffle.pngso any future regression is still caught.output.expected.pngis left untouched as the reference to converge to.Checklist