* Add failing tests for runs that shape to no glyphs
A shaper hides the default ignorables it substitutes for line breaks
behind the font's space glyph, and deletes them when the font has no
such glyph. A run holding nothing but a line break then shapes to an
empty glyph buffer while still owning its characters.
- TextFormatter drops that run today, so the line covers no text and
the layout loop never advances past it - measuring a wrapping
TextBlock whose text starts with a newline never returns.
- GlyphRun indexes its glyph list without checking it is non-empty, so
hit-testing such a run throws IndexOutOfRangeException.
Covers all three layers: GlyphRun hit-testing, the formatted line, and
the TextLayout repro from the issue. The layout test bounds maxLines so
a regression fails instead of hanging the test run.
The Skia tests embed the headless platform's BareMinimum.ttf, which has
four glyphs and no space, rather than a copy of it.
* Keep text runs that shape to no glyphs
ShapeTogether split the shaped buffer by glyph count, so a run whose
glyphs were all deleted by the shaper was skipped and its characters
rolled into previousLength for a following run to absorb. When no such
run follows, the characters are lost: the line reports a length of 0
and TextLayout formats the same line forever.
Splitting by text length preserves the case the accumulation exists for
- a run whose glyphs merged into a neighbouring cluster splits off no
text either - and stops discarding characters that shaped to nothing.
That makes zero-glyph glyph runs reachable for the first time, so:
- GetDistanceFromCharacterHit, FindGlyphIndex and FindNearestCharacterHit
handle an empty glyph list. There is no cluster to snap to and the run
sits at a single position, so it degenerates to one zero-width cluster.
- InkBounds answers empty directly instead of building a platform glyph
run. TextLineImpl reads it for every shaped run in a line, and the Skia
implementation creates an SKFont and measures glyph widths in its
constructor - wasted on a run that marks nothing.
Fixes#22004
* Adjust GlyphRun.InkBounds xml comment
* Add failing tests for whitespace absorbed into a fallback run
A fallback run is extended for as long as the fallback font has glyphs
instead of ending where the primary font regains coverage. Practically
every font maps U+0020, so the space that follows fallback text is
pulled into the fallback run and shaped with its space glyph.
- itemization: a Hebrew letter followed by " b" must produce a 1-char
fallback run, not a 2-char one that swallows the space
- measurement: the space after an emoji must have the same advance as
the same space elsewhere in the line
* Merge abutting text bounds within the usual float tolerance
The two edges being compared are reached by summing glyph advances along
different paths, so abutting bounds can land an ULP apart and a single
directional span gets reported as two rectangles. Compare them the way
the rest of layout compares coordinates.
* End a fallback run where the default typeface regains coverage
Whitespace was exempt from the return-to-primary check so that a space
wouldn't split a fallback run. Practically every font maps U+0020, so
the exemption let a fallback run reach past the text the default
typeface couldn't render and shape the following space with the
fallback's own advance - a full em in most emoji fonts, which is the
long space reported after an emoji.
- the check now applies to spacing whitespace (Zs), so a fallback run
ends at the first cluster the default typeface can render; control
and format codepoints stay exempt - many fonts map the
default-ignorable bidi controls, and a default typeface whose cmap
merely has such a mark must not pull it out of the fallback run: the
mark renders nothing either way, and the split cuts the run for no
reason
- a default typeface that cannot shape the run's script is still not a
return target for text, but it does reclaim the spacing whitespace
between the words, which carries no shaping
- a run of pure whitespace no longer becomes the anti-thrashing bias for
the next run, so the words on either side of a space keep resolving to
the same fallback font
Test expectations that encoded the old run structure move with it: a
line of emoji separated by spaces is no longer a single run, glyph
clusters are relative to each run's own text (the affected helpers now
add the run's start), and the spaces of a right-to-left line measure
with the primary font, which widens those lines.
* Add failing test for a right-to-left split inside a cluster
Splitting a right-to-left shaped buffer keeps a straddling cluster's
glyphs together in the leading half, but cuts the text at the requested
offset instead of following them. The halves then disagree about which
characters their glyphs cover: the leading half claims one character
while holding the glyphs for two, and the trailing half claims a
character whose glyphs it doesn't have. The left-to-right path already
snaps the text boundary to the cluster.
* Snap a right-to-left split to the cluster the glyphs stay with
A cluster straddling the split point keeps all its glyphs in the leading
half, so the text has to be cut where those glyphs end, not at the
requested offset. Cutting at the offset produced two halves whose glyph
and character coverage disagreed, and their shared cluster cache then
described neither: measuring the trailing half returned counts that cut
a cluster, and splitting it again could hand back a leading half holding
every glyph and no trailing half at all - a null run that crashed the
bidi reorderer.
The ascending path already snapped this way; this makes the descending
path match.
* Add failing test for a word gap at a run boundary
Justification collects break opportunities run by run. The line breaker
always reports a break at the end of the text it is given, and that
artificial run-final break is filtered out - so a real word gap that
happens to fall on a run boundary produces no opportunity at all and is
never widened. A line whose word gap borders a fallback run is left
partly or entirely unjustified.
* Search break opportunities in the line text, not per run
A run boundary is not a text boundary - a font fallback or a style
change splits a run mid-sentence - but the line breaker reports a break
at the end of whatever text it is given. Searching run by run invented a
break at every run boundary, and dropping those artefacts also dropped
every real word gap that fell on one.
- collect the opportunities from each stretch of consecutive text runs,
copied into a pooled buffer, and exclude only the stretch-final break
- runs that carry no text (embedded objects, end of line) end a stretch,
as do runs whose length doesn't match their text, since neither can be
mapped back to text source positions by offset
* Add Emoji, Emoji_Presentation and Default_Ignorable Unicode data
Font fallback has to tell content codepoints apart from ones a font is not
expected to draw, and emoji presentation cannot be decided from the general
category alone. None of that data was available.
- Pack Emoji, Emoji_Presentation and Default_Ignorable_Code_Point as single
bits into the spare bits of the GraphemeBreak trie word (it only used 7 of
32); the emoji properties come from the emoji-data.txt the generator already
reads for Extended_Pictographic.
- Expose them as Codepoint.IsEmoji, HasEmojiPresentation and IsDefaultIgnorable.
- Regenerate GraphemeBreak.trie.cs; no other generated file changes.
* Add failing test for default ignorables in cluster coverage
A cluster of base + U+FE0F + combining mark is handed to the primary font even
though it has no glyph for the mark, because every candidate font is required
to cover the variation selector as well and none does. The mark renders as
.notdef.
The fallback font is Noto Sans rather than DejaVu Sans: DejaVu maps U+FE0F and
would hide the defect.
* Ignore default ignorable codepoints when matching cluster coverage
Variation selectors are nonspacing marks by general category, so the cluster
coverage check demanded a glyph for them from every candidate font. Text fonts
do not map U+FE0F, which made any base + U+FE0F + mark cluster look unmatchable:
the fallback search was keyed on the variation selector, found nothing, and the
cluster fell back to the primary font with the mark rendered as .notdef. This is
what made emoji keycap sequences render as a digit followed by a tofu box.
- Skip default ignorables (along with the break/control/format codepoints
already handled) when testing cluster coverage and when picking the codepoint
the fallback search is keyed on.
- A cluster holding only a default ignorable no longer ends a shapeable run;
it renders nothing whichever font it lands on.
* Unify glyph-demand predicate in cluster coverage
Default_Ignorable_Code_Point already covers every invisible format
codepoint, so the blanket Format exclusion in NeedsGlyph only skipped
the deliberately visible ones (prepended concatenation marks like
U+0600, interlinear annotation chars) - those should demand a glyph,
and the base-only pass already did. Dropping Format makes NeedsGlyph
identical to the run-ending condition in TryGetShapeableLength, which
now calls it instead of restating it.
* Fix TextBlock ellipsis not showing with TextWrapping and MaxLines
When TextWrapping and MaxLines are set, the collapsing width was using the
layout's maximum width instead of the current line's width. This caused the
ellipsis to not appear when the last line was shorter than other lines.
Also added HasCollapsed check to prevent double-collapsing when text has
already been collapsed due to overflow.
* Remove HasCollapsed check
* Add unit test for MaxLines trailing ellipsis on short wrapped lines
* Remove redundant comment
* fix: simplify TextLayout test
* Fix inter-word justification distribution and safety
TextAlignment.Justify was incorrect or a no-op in several common cases.
This corrects glyph targeting, buffer safety, run partitioning, the fill
target, and which lines get justified.
- Target the glyph ending each break boundary; step zero-width breaks
(CJK/Korean ideograph boundaries, hyphens) back one so the widened gap
sits on the break and the last visible glyph is never stretched.
- Copy-on-write the shaped buffer before adjusting advances and swap in a
fresh run, so justification never mutates glyph storage shared with a
TextRunCache entry, a Split sibling or a WithBidiLevel alias. Adds
ShapedBuffer.CloneWritable and TextLineImpl.ReplaceTextRun, which also
repoints the bidi-reordered runs used by draw and hit-test.
- Consume break opportunities per run instead of draining the whole queue
against the first run.
- Fill the visible Width to the paragraph width rather than the width
including trailing whitespace, so wrapped lines with trailing whitespace
justify instead of being left un-justified.
- Target MaxWidth for wrapped text rather than the widest produced line.
- Skip the last line, newline-terminated lines and hard-break lines.
Adds justification test coverage: CJK, mixed Latin+CJK, Arabic buffer
integrity, Latin wrapping, trailing whitespace and multi-run lines.
* Add failing test for trailing-whitespace overcount across runs
- TextLineImpl.CreateLineMetrics walks a line's runs backward to
compute trailing whitespace, stopping only when a run has none of
its own - not when a run is only partly whitespace.
- On a multi-run line where an interior run also ends in a space
(common at a script boundary, since each script run tends to end at
the following space), that space gets wrongly counted as trailing
too, even though visible content follows it later in the line.
- This shrinks Width, which InterWordJustification uses to decide how
much space a line still needs to reach the target width.
* Fix trailing-whitespace overcount across runs
- Only continue accumulating trailing whitespace from an earlier run
when the current run is entirely whitespace; otherwise the current
run's own visible content is the line's true end, and an earlier
run's trailing space is interior, not trailing.
- Width now correctly includes interior, non-trailing whitespace, so
wrapped multi-run lines (e.g. mixed-script text) no longer get
stretched past the target width when justified.
* Ignore CLAUDE.md
* Add GlyphTypeface.CanShapeScript(Script), backed by a lazy read of the GSUB/GPOS ScriptList via the new ScriptListTable parser.
Add FontFallbackScriptHints.TryGetComplexShapingTags, which maps 17 complex scripts to the OpenType script tag(s) a font must declare to shape them.
Thread a Script shapingScript constraint through FontManager.TryMatchCharacter and FontCollectionBase.TryMatchCharacter as internal overloads, leaving the public signatures untouched.
Rework TextCharacters.CreateShapeableRun into a capability preference: a complex script tries shaping-capable fonts first (tier 0), then cmap-only coverage (tier 1); simple scripts keep the single historical tier.
Replace the NUL placeholder U+200B (ZERO WIDTH SPACE) with U+2060 (WORD JOINER) so the substitution introduces no line-break opportunity.
Suppress previous-run font reuse across a culture change for locale-sensitive scripts, so a zh run's font no longer pins following ja text.
Add 3 subset test fonts and 3 new test files (7 new tests) and fix 2 stale doc-comment crefs.
* Fix comments
* Fix font fallback reusing the wrong cached face from the script bucket
FontCollectionBase.TryMatchCharacter caches a resolved fallback family per
(script, culture) bucket. Two bugs let that bucket hand back a face that does
not match the request:
- A positive bucket entry suppressed the Tier D platform lookup for any later
codepoint, even one the bucket family cannot cover. A CJK run left the
Simplified-only U+534E unmatched (tofu) because the bucket resolved to a
Japanese font that lacks it, while another installed font could place it.
Tier D now treats only a negative (null) entry as "platform already
attempted"; a positive entry no longer blocks the platform, since that
family may not cover this codepoint.
- A cached covering match of a neighbouring FontCollectionKey was reused for a
different requested key. After a Bold run cached a face, a following Normal
run reused it and rendered bold (and the symmetric style and stretch cases).
Tier A, B and C now keep the exact key: when the cached covering match is a
neighbouring key, the exact-key face of the same family is resolved from the
platform via TryMatchCharacterFromPlatform, falling back to the neighbour
only when the platform has no exact face. Collections without a platform are
unchanged.
Add regression tests for both: a bucket whose font lacks a later same-script
codepoint, and weight and style leaks resolved through Tier B (script cache)
and Tier C (the sweep, exercised via a non-locale-sensitive script with no
culture).
* Fix BiDi correctness across TextCollapsingProperties implementations
Audit and fix logical-vs-visual ordering bugs in every text-collapsing
implementation and the helpers they share, so trimming always operates in
logical order (the consumer re-runs the BiDi reorderer in FinalizeLine, so
pre-applying visual order would be reordered a second time).
- LogicalTextRunEnumerator returned the first run on every MoveNext when
_indexedTextRuns was null.
- TextLeadingPrefixCharacterEllipsis: walked visual-order runs while splitting
at logical offsets, guarded the wrong field in the ctor, hardcoded
LeftToRight for the symbol, appended the suffix in reverse-logical order, and
mis-tracked the width budget across multi-run lines.
- TextPathSegmentEllipsis: crashed via Split(0) in the fallback path, and
measured RTL segments as ~0 width (it assumed ascending cluster ids).
- ShapedTextRun.TryMeasureCharacters/Backwards returned visual-order counts for
RTL runs.
ShapedBuffer gains logical-order, cluster-cache-backed width helpers
(GetCharRangeWidth, FindLeading/FindTrailingCharCountWithinWidth) that are
safe in the one-char-per-cluster simple mode (null _clusterStartChars). Adds a
BiDi characterization test suite and documents the logical-order Collapse
contract.
* Align text width comparisons to epsilon-tolerant MathUtilities
Several width-fit comparisons in the measure/collapse path used raw operators
with no epsilon, while the cluster-cache hot path (MeasureCharactersThatFit)
and the ShapedTextRun branch of MeasureLength already went through
MathUtilities. Replace the raw float comparisons with their epsilon-aware
equivalents (<= -> LessThanOrClose, < -> LessThan, > -> GreaterThan) in
ShapedBuffer.FindLeading/FindTrailingCharCountWithinWidth,
TextPathSegmentEllipsis, and TextLeadingPrefixCharacterEllipsis.
Also unify the DrawableTextRun branch of TextFormatterImpl.MeasureLength with
the ShapedTextRun branch (>= -> GreaterThan) so the "whole run fits" boundary
is identical for both run types: a run fits when width <= remaining (tolerant)
and overflows only when strictly greater.
Integer char-offset/index comparisons and the availableWidth <= 0 positivity
guards are intentionally left as raw comparisons.
* Cleanup some comments
* Fix Split(0) crash in TextLeadingPrefixCharacterEllipsis suffix collection
When a logical-tail run fits entirely within the remaining suffix budget,
TryMeasureCharactersBackwards returns suffixCount == run.Length, so
Split(run.Length - suffixCount) evaluated to Split(0), which throws
ArgumentOutOfRangeException. This was reachable on multi-run lines whose
post-split tail contains a small later run that fully fits.
Guard the split on splitAt > 0 and use the whole run as-is when it fully fits
(dropping it only when suffixCount == 0) — the same boundary pattern already
applied to TextPathSegmentEllipsis. Add a multi-run regression test.
* Remove duplicate ShapedBuffer.MeasureCharactersThatFit
MeasureCharactersThatFit and FindLeadingCharCountWithinWidth ran the
identical cluster-prefix binary search; the only functional difference
was the unused `out widthConsumed`. Drop the former and repoint its
callers (TextFormatterImpl.MeasureLength and the benchmark) at
FindLeadingCharCountWithinWidth, which also unifies the availableWidth<=0
guard. The one test that asserted the consumed width now recovers it via
GetCharRangeWidth(0, fit).
Also tidy GetCharRangeWidth's simple-mode bounds to use Math.Clamp.
* perf(text): O(1) width queries on ShapedBuffer, cached LB-class lookup
- ShapedBuffer: lazy, pooled cluster-prefix cache shared across Split;
adds TotalGlyphAdvance / MeasureCharactersThatFit.
- GlyphRun/TextFormatterImpl/Skia GlyphRunImpl: read the cache, drop
duplicate scans; SplitTextRuns now returns firstLength via out.
- LineBreakEnumerator: cache Next/PreviousClass per advance; remove
unused LineBreakPairTable.
- Tests + benchmarks for the new paths and a dotnet-trace harness.
~1.32x faster / -5% alloc on the emoji-wrap micro-benchmark; no
observable behaviour or public API change.
* perf(TextFormatting): inline LineBreakEnumerator rule dispatch
Replace the BreakUnitDelegate[] s_rules array dispatch in
LineBreakEnumerator.ExecuteRules with a sequence of direct static
calls and a `goto Done` early-exit. This removes 42 indirect calls
per codepoint and lets the JIT reason across rule boundaries, which
in turn makes [MethodImpl(AggressiveInlining)] meaningful — JIT
cannot inline through delegate.Invoke, so the attribute was a no-op
in the previous shape.
Selectively apply AggressiveInlining to the small single-condition
rules (LB03, LB04, LB06, LB07, LB08a, LB11–LB15d, LB18, LB20,
LB21b, LB22, LB29, LB31) and let the JIT decide on the larger ones
to avoid bloating the merged ExecuteRules.
The static BreakUnitDelegate[] s_rules array is removed.
Benchmarks (BDN default job, --inProcess, N=13–22, rel. StdDev <1.5%):
UnicodeBreakEnumeratorBenchmark.LineBreakEnumerator_Sequence
Ascii 154.6 µs -> 24.84 µs (6.22x)
Bmp 190.2 µs -> 32.81 µs (5.80x)
Supplementary 227.6 µs -> 37.56 µs (6.06x)
TextLayoutProfile.BuildEmojisWrapped
Before (branch, pre-inline) 804.5 µs / 570.15 KB
After (branch, post-inline) 547.0 µs / 570.15 KB (-32.0%)
vs upstream/master (1061 µs) -> ~1.94x total speedup
No allocation change; CPU-only dispatch reshape. All
LineBreakEnumerator unit tests pass (5/5).
* Refactor ShapedBuffer to share pool storage via IRef + generation counter
Wrap the ArrayPool-rented glyph and cluster-cache arrays in a small
PooledArray<T> disposable and expose them through IRef<T>. Split children
and WithBidiLevel aliases now Clone() the refs instead of borrowing raw
pool arrays, so the backing storage survives until every sibling has
been disposed - eliminating the UAF risk that existed when a parent was
disposed before its children.
Add a per-glyph-holder generation counter (Volatile.Read / Interlocked
.Increment). The indexer setter bumps the counter on every write, and
EnsureClusterCache compares its recorded generation against the holder's
current value, rebuilding on mismatch. This lets us drop the previous
"no mutation after Split/WithBidiLevel" contract: mutations performed
through any sibling now propagate to the others' caches transparently.
Dispose is made idempotent via a _disposed guard so overlapping cache
eviction and TextLine teardown only release the IRefs once.
Adds ShapedBufferSharedStorageTests covering sibling lifetime,
Dispose idempotency, and generation-driven cache invalidation across
Split children and WithBidiLevel aliases.
* test(ShapedBuffer): cover cluster-cache sharing across Split/WithBidiLevel aliases
Add a regression test hook (`ClusterPrefix`) exposing the
backing cluster-prefix array reference, plus two tests that mutate a
parent buffer before aliasing it and assert the alias reuses the
parent's pooled prefix array instead of rebuilding. Guards against
forgetting to propagate `_cacheGeneration` to alias buffers, which
would silently defeat the cached-split fast path.
* Correctly use MathUtilities.LessThanOrClose
---------
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>
* Enable nullability in RenderTests
* Enable nullability in LeakTests
* Enable nullability in Skia.UnitTests
* Enable nullability in Generators.Tests
* Enable nullability in DesignerSupport.Tests
* Enable nullability in Build.Tasks.UnitTests
* Improve FontCollection user story
* Make adjustments after review
* Refactor IsFontFile
* Make FontFamilyLoader internal
Make tests happy again
* Update baseline
* Adjust modifier
---------
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* Fixed the issue where a newline character required two backspaces to be deleted in Windows.
* add unit test
* Handling the differences between HeadlessTextShaperStub and TextShaperImpl
* Make unit tests available
* Treat "\r\n" as a single unit.
* The backspace test against CRLF
* Backspace should treat CRLF as a unit.
* Do not treat spacing combining marks as whitespace
* Do not treat combining marks as whitespace
Try to find a matching typeface on a grapheme boundary
* Add embedded font for testing
* Remove using
* Fix naming and remove redundant code
* Apply default until we reach the next base character
* - Adjusts the overhang calculation so it matches what WPF produces
- Fixes baseline alignment for inline controls
* Adjust OverhangTrailing
Add unit test
* Only run overhang unit test on Windows
* xml documentatation for paragraph properties
* TextTestApp
* text test files renamed parameter
* glyph ink bounds
* shaped buffer make selected row visible
* text test app force light theme
* text test app distinguish start and end distance marks
---------
Co-authored-by: Jan Kučera <miloush@users.noreply.github.com>
Co-authored-by: Benedikt Stebner <Gillibald@users.noreply.github.com>
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* Reverse text runs for RTL flow direction
* Optimize text run traversal.
* Remove unused LINQ directive from TextLineImpl.cs
* Add RTL newline handling test in TextLineTests
* Add RTL newline handling tests for text formatting.
* Make sure we only apply the cluster offset if we are inside the current cluster
* Better naming
* Guard coveredLength against invalid values
---------
Co-authored-by: Max Katz <maxkatz6@outlook.com>
* Adjust TextLineImpl.GetTextRunBounds so it properly handles substitutions
Adjust TextLineImpl.GetTextRunBounds so it properly reports out of text range bounds
Adjust TextLineImpl.GetTextRunBounds so it properly reports text source run indices
* Remove redundant comments
* Add requested changes
* 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>
* Trim text-runs in their logical order
* Revert breaking changes
* Added TODO12 comment
* Added test unit
* Use LogicalTextRunEnumerator
---------
Co-authored-by: Benedikt Stebner <Gillibald@users.noreply.github.com>
* Always Return 0 bidi level for TextEndOfLine run
* Fix test unit
* Expand the unit test
---------
Co-authored-by: Benedikt Stebner <Gillibald@users.noreply.github.com>
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* [Text] fix infinite loop hang on selection of bidi text
* Deal with zero width whitespaces during run bounds calculation
* Do the same thing for RTL
---------
Co-authored-by: Benedikt Stebner <Gillibald@users.noreply.github.com>
* Repro for Line_Formatting_For_Oversized_Embedded_Runs_Does_Not_Freeze
* Repro for Line_With_IncrementalTab_Should_Return_Correct_Backspace_Position
* Fix GetBackspaceCaretCharacterHit
Fix GetPreviousCaretCharacterHit
Fix WrapWithOverflow for not text runs
* Fix custom font manager
* Move DejaVuSans to a different location to prevent using it as a fallback
---------
Co-authored-by: Nikita Tsukanov <keks9n@gmail.com>