* Keep TextBlock text runs in sync with its inlines
_textRuns is built from Inlines, but OnMeasureInvalidated discarded it on
any measure invalidation while Inlines still held the content. Between
that point and the next measure pass, CreateTextLayout read a null
_textRuns as "no inlines" and shaped Text instead, which is null whenever
the content lives in Inlines. That empty result went into the
TextRunCache, keyed by text source index, so every later layout reused it
and the control rendered nothing until something invalidated the cache.
- Discard _textRuns in InvalidateTextLayout, next to the run cache, so
the runs and the cache are dropped by the same event and cannot
disagree about the content.
- Build the runs on demand in EnsureTextRuns, and pick the text source by
HasComplexContent rather than by _textRuns being set.
- Split the constraint-dependent work out of run building. Runs answer to
the content alone; only an embedded control answers to the available
width, so Inline.MeasureEmbeddedControls measures it and
EmbeddedControlRun reports the child's DesiredSize live. Runs now
survive a constraint change instead of being rebuilt every measure.
SelectableTextBlock never hands the run cache to its layout, but it
shared the same fallback and kept the wrong layout on _textLayout.
Fixes#21902
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Invalidate the layout when LineSpacing changes
LineSpacing had no case in the property change switch and is not one of
the properties registered with AffectsRender, so changing it left the
measured size and the rendered text untouched even though CreateTextLayout
feeds it into the paragraph properties. It changes line placement rather
than shaping, so it belongs with LineHeight and the other properties that
keep the run cache.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Drop the text layout whenever the content is invalidated
InvalidateMeasure only raises OnMeasureInvalidated while the measure is
still valid, so a second content change before the next measure pass left
_textLayout holding the layout the first change had already replaced.
MeasureOverride keeps that layout when the constraint has not moved, so
the block measured and rendered the superseded content.
Clear the layout in InvalidateTextLayout and InvalidateTextLayoutKeepCache
rather than relying on OnMeasureInvalidated to run, which is what
TextPresenter already does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Drop the text layout when an embedded control resizes
A line snapshots its metrics when it is formatted, so a layout built
before a child was measured again keeps reporting the width and height
that child used to have. MeasureOverride reuses the layout whenever the
constraint has not moved, so a control that resizes while the block is
already measure invalid never reaches the measured size.
- MeasureEmbeddedControls reports whether any child came back a different
size, and the layout is dropped only then rather than on every pass.
- Route the remaining layout resets through DisposeTextLayout so every
reset goes through one place.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* test(textblock): cover fractional layout rounding
A wrapped TextBlock with a fractional line height must retain every line when its desired height lands exactly on a fractional-scale pixel boundary.
* fix(text): preserve lines at rounded fractional heights
TextLayout treated a floating-point accumulation error as a MaxHeight overflow. Use the established tolerant comparison so a line that exactly fits its rounded layout bounds is retained.
---------
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* Introduce a universal IGlyphTypeface implementation that does not rely on any platform implementation
* Revert changes
* Fix Android
* Make the test happy
* Fix build
* Update baseline
* Fix naming
* Fix headless
* Move interfaces to dedicated files
Make GlyphTypeface.GlyphCount an integer
* Fix GlyphCount
* Make IGlyphTypeface NotClientImplementable
* Make sure we cache platform typefaces by their desired name, style, weight and stretch
* Update baseline
* Only use IGlyphTypeface
* Fix Android
* Try to clear the buffer before we encode somethimg
* Add needed test font
* Add more unit tests
* Reduce allocations
* Remove Direct2D1 test files
* More tests
* More complete table implementations
* More adjustments
* Use batch APIs
* Handle invalid timestamps
* Update baseline
* Introduce a CharacterToGlyphMap struct for faster access
* Remove AggressiveInlining
* Remove AggressiveInlining
* Make the head table optional for legacy fonts
* Remove Load method. Fix TextBlockTests
* Fix nullables
* Remove redundant folder
* Update Api baseline
* revert diff helper changes
* revert changes
* Use bare minimum font for Headless platform and introduce a test font manager that uses the Inter font for testing.
* Add missing font file for Headless platform
---------
Co-authored-by: Gillibald <stebner@avaloniaui.net>
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* Limit InlineUIContainer to available width
* Unit test for InlineUIContainer maximum width
* Invalidate InlineUIContainer's host
* Better InlineUIContainer visual children management
* OnInlineHostChanged also handled by InlineUIContainer
---------
Co-authored-by: Jan Kučera <miloush@users.noreply.github.com>
Co-authored-by: Benedikt Stebner <Gillibald@users.noreply.github.com>
* Don't round size when UselayoutRounding is false for TextBlock. Fixes#18423
* Added back size rounding
* Removed rounding again for the text size and fixed padding rounding in RenderCore
* Fix issue with MinTextWidth (Fixes#18372)
* Make sure that MeasureOverride for TextPresenter and TextBlock are using same textWidth
* Revert #16601 that is introducing an invalid calculation for the OverhangLeading
Add tests for OverhangLeading and OverhangTrailing
* Revert MinTextWidth
* Fix tests to not rely on fixed values
* Fix remaining issues
* Fix comment in Direct2D1 GlyphRunImpl.cs
* Fix Direct2D1 rendering
* Fix gold images
* Restore TextLineImpl
* Update gold image
* Restore Math.Max on OverhangLeading and Trailing
* Adopt similar behavior to WPF: don't use OverhangLeading/Trailing for measuring and remove clip by default.
But it requires further support with NeedsClipBounds
* Remove MinTextWidth
Keep ClipToBounds=true default for TextBlock
* Revert change
---------
Co-authored-by: Benedikt Stebner <Gillibald@users.noreply.github.com>
* Add failing test for TextBlock
* Fix TextBlock re-measure in infinite container
* Fix outdated test
---------
Co-authored-by: Max Katz <maxkatz6@outlook.com>
* Always measure TextBlock with infinite width
* Make sure the constraint is always fulfilled
* Add some tests
* Adjust tests because we no longer retain the TextLayout in the arrange pass
* Rework how TextBlock skips redundant measure and arrange calls
Add some tests
* Adjust tests
* Try this
* Make sure the TextBlock is arranged after it has been measured with a different availableSize
* Make it more clear that we are resetting and recreating the TextLayout
* Capture textLayout after inlines have been processed
* Make sure to clean up VisualChildren and created text runs when the TextLayout is invalidate
* Remove localized comment
---------
Co-authored-by: Tim <47110241+timunie@users.noreply.github.com>