Skip to content

Return empty text bounds for zero-length ranges - #22344

Merged
Gillibald merged 4 commits into
AvaloniaUI:mainfrom
adirh3:lumi/textlayout-empty-ranges
Oct 5, 2026
Merged

Gillibald merged 4 commits into
AvaloniaUI:mainfrom
adirh3:lumi/textlayout-empty-ranges

Conversation

@adirh3

@adirh3 adirh3 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

What does the pull request do?

Make TextLine.GetTextBounds return an empty bounds collection for zero-length requests, protecting every consumer of the API.

What is the current behavior?

HitTestTextRange(1, 0) on "abc", and HitTestTextRange(3, 0) on "a\r\nb\r\n", throw ArgumentOutOfRangeException with ParamName="textLength" instead of returning empty geometry.

Reported exception

An anonymized report from an application using Avalonia 12.1.2 on Windows x64 and .NET 11.0.0-rc.1.26425.128:

System.ArgumentOutOfRangeException: textLength ('0') must be a non-zero value. (Parameter 'textLength')
Actual value was 0.
   at Avalonia.Media.TextFormatting.TextLineImpl.GetTextBounds(Int32 firstTextSourceIndex, Int32 textLength)
   at Avalonia.Media.TextFormatting.TextLayout.HitTestTextRange(Int32 start, Int32 length)
   at Avalonia.Controls.SelectableTextBlock.RenderTextLayout(DrawingContext context, Point origin)
   at Avalonia.Controls.TextBlock.RenderCore(DrawingContext context)
   at Avalonia.Rendering.Composition.CompositingRenderer.UpdateCore()
   at Avalonia.Rendering.Composition.Compositor.CommitCore()
   at Avalonia.Media.MediaContext.CommitCompositorsWithThrottling()
   at Avalonia.Media.MediaContext.RenderCore()
   at Avalonia.Media.MediaContext.Render()
   at Avalonia.Threading.DispatcherOperation.InvokeCore()

This report motivated the investigation; the tests establish the direct empty-range API defect, not the reporter's original rendering sequence.

How was the solution implemented?

Return an empty bounds collection in TextLineImpl.GetTextBounds for a zero-length request, as requested in review, and document that result on TextLine. Remove the TextLayout-specific guards. Negative-length handling is unchanged.

Tests

Direct TextLine tests cover eight zero-length cases at caret and CRLF boundaries, plus three nonempty geometry/run-bound checks. The existing TextLayout empty-range, completed-line, and negative-length regressions remain. Each test-only commit precedes its implementation commit.

The new direct TextLine regressions produce eight ArgumentOutOfRangeException failures before the lower-API fix, even with the earlier layout-only guards. After the fix, the committed TextLine and TextLayout suites pass 168 tests, with one existing profiling-only skip.

The confirmed reproducer is the direct zero-length public API call. Three ordinary CRLF SelectableTextBlock selection-rendering probes also passed with the original throwing implementation. The original reporter's text/selection/input sequence remains unknown; this PR does not claim to reproduce that exact UI trigger.

Breaking changes

No signature changes. Zero-length bounds queries now return an empty collection instead of throwing.

AI-assisted contribution. The submitting author will handle review feedback.

adirh3 and others added 2 commits September 29, 2026 12:34
Exercise empty ranges at interior and CRLF boundaries using real text layouts, and retain checks for completed-line geometry and existing negative-range behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Return empty geometry for zero-length requests and stop traversing lines when the remaining range is empty. Keep negative-range behavior and TextLine validation unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MrJul MrJul added bug area-textprocessing backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch labels Sep 29, 2026
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

NuGet packages

The packages for this pull request have been published (workflow run):

@Gillibald Gillibald left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should change GetTextBounds to return an empty rect for an empty range instead of throwing. That also fixes the issue but for all consumers of that API. How is this issue triggered in a real world application?

adirh3 and others added 2 commits October 1, 2026 13:50
Expect no text bounds for zero-length ranges at caret and CRLF line boundaries. Retain checks for normal nonempty geometry. Before the fix, the TextLine suite has 8 failures and 81 passes, with all failures throwing for textLength = 0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Handle zero-length ranges in TextLineImpl.GetTextBounds so all consumers share the empty bounds contract. Remove the TextLayout-only guards and document the result. Negative-length behavior is unchanged.

Verified 168 passing TextLine/TextLayout tests, with one existing profiling test skipped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@adirh3 adirh3 changed the title Handle empty ranges in TextLayout hit testing Return empty text bounds for zero-length ranges Oct 1, 2026
@adirh3

adirh3 commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Moved the fix to TextLineImpl.GetTextBounds, returning an empty bounds collection for zero-length requests, and removed the TextLayout guards. The TextLine/TextLayout suites pass 168 tests with one existing skip. The real-app report came through SelectableTextBlock rendering, but the reporter's exact text and selection sequence is unknown. Ordinary CRLF rendering probes didn't reproduce it; the confirmed reproducer is the zero-length public API call.

@Gillibald Gillibald left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Gillibald
Gillibald added this pull request to the merge queue Oct 5, 2026
Merged via the queue into AvaloniaUI:main with commit 11ea9b1 Oct 5, 2026
11 checks passed
grokys pushed a commit to Evan260/Avalonia that referenced this pull request Oct 7, 2026
* Add regression tests for empty text layout ranges

Exercise empty ranges at interior and CRLF boundaries using real text layouts, and retain checks for completed-line geometry and existing negative-range behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Handle empty ranges in TextLayout hit testing

Return empty geometry for zero-length requests and stop traversing lines when the remaining range is empty. Keep negative-range behavior and TextLine validation unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add direct TextLine empty range regressions

Expect no text bounds for zero-length ranges at caret and CRLF line boundaries. Retain checks for normal nonempty geometry. Before the fix, the TextLine suite has 8 failures and 81 passes, with all failures throwing for textLength = 0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Return no text bounds for empty ranges

Handle zero-length ranges in TextLineImpl.GetTextBounds so all consumers share the empty bounds contract. Remove the TextLayout-only guards and document the result. Negative-length behavior is unchanged.

Verified 168 passing TextLine/TextLayout tests, with one existing profiling test skipped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: adirh <27368554+adirh3@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MrJul pushed a commit that referenced this pull request Oct 9, 2026
* Add regression tests for empty text layout ranges

Exercise empty ranges at interior and CRLF boundaries using real text layouts, and retain checks for completed-line geometry and existing negative-range behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Handle empty ranges in TextLayout hit testing

Return empty geometry for zero-length requests and stop traversing lines when the remaining range is empty. Keep negative-range behavior and TextLine validation unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add direct TextLine empty range regressions

Expect no text bounds for zero-length ranges at caret and CRLF line boundaries. Retain checks for normal nonempty geometry. Before the fix, the TextLine suite has 8 failures and 81 passes, with all failures throwing for textLength = 0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Return no text bounds for empty ranges

Handle zero-length ranges in TextLineImpl.GetTextBounds so all consumers share the empty bounds contract. Remove the TextLayout-only guards and document the result. Negative-length behavior is unchanged.

Verified 168 passing TextLine/TextLayout tests, with one existing profiling test skipped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: adirh <27368554+adirh3@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MrJul MrJul added backported-12.1.x and removed backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch labels Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants