@@ -55,7 +55,16 @@ public void Dispose()
5555 // Split children and WithBidiLevel aliases clone this ref so the array
5656 // survives until every observer has been disposed.
5757 private IRef < PooledArray < GlyphInfo > > ? _glyphRef ;
58+
59+ // Ref-counted handle to the pooled ushort[] that backs _glyphIndices. The
60+ // parallel glyph-id array exists so consumers (GlyphRunImpl, the new
61+ // TryGetGlyphBounds batch path, SKFont.GetGlyphWidths) can take a
62+ // ReadOnlySpan<ushort> over the run's glyph IDs without walking the
63+ // GlyphInfo struct array. Lifetime mirrors _glyphRef: cloned on Split /
64+ // WithBidiLevel, disposed in Dispose, null on caller-owned storage.
65+ private IRef < PooledArray < ushort > > ? _glyphIndicesRef ;
5866 private ArraySlice < GlyphInfo > _glyphInfos ;
67+ private ArraySlice < ushort > _glyphIndices ;
5968
6069 // Lazily-computed cluster-width cache. MeasureLength and metrics
6170 // queries both fold multi-glyph clusters and accumulate per-cluster
@@ -131,15 +140,18 @@ public ShapedBuffer(ReadOnlyMemory<char> text, int bufferLength, GlyphTypeface g
131140 Text = text ;
132141 _glyphRef = RefCountable . Create ( new PooledArray < GlyphInfo > ( bufferLength ) ) ;
133142 _glyphInfos = new ArraySlice < GlyphInfo > ( _glyphRef . Item . Array , 0 , bufferLength ) ;
143+ _glyphIndicesRef = RefCountable . Create ( new PooledArray < ushort > ( bufferLength ) ) ;
144+ _glyphIndices = new ArraySlice < ushort > ( _glyphIndicesRef . Item . Array , 0 , bufferLength ) ;
134145 GlyphTypeface = glyphTypeface ;
135146 FontRenderingEmSize = fontRenderingEmSize ;
136147 BidiLevel = bidiLevel ;
137148 }
138149
139- internal ShapedBuffer ( ReadOnlyMemory < char > text , ArraySlice < GlyphInfo > glyphInfos , GlyphTypeface glyphTypeface , double fontRenderingEmSize , sbyte bidiLevel )
150+ internal ShapedBuffer ( ReadOnlyMemory < char > text , ArraySlice < GlyphInfo > glyphInfos , ArraySlice < ushort > glyphIndices , GlyphTypeface glyphTypeface , double fontRenderingEmSize , sbyte bidiLevel )
140151 {
141152 Text = text ;
142153 _glyphInfos = glyphInfos ;
154+ _glyphIndices = glyphIndices ;
143155 GlyphTypeface = glyphTypeface ;
144156 FontRenderingEmSize = fontRenderingEmSize ;
145157 BidiLevel = bidiLevel ;
@@ -152,19 +164,23 @@ internal ShapedBuffer(ReadOnlyMemory<char> text, ArraySlice<GlyphInfo> glyphInfo
152164 /// until every sibling has been disposed. When <paramref name="sourcePrefixRef"/>
153165 /// is null the alias starts without a cluster cache and will build its own lazily.
154166 /// </summary>
155- private ShapedBuffer ( ReadOnlyMemory < char > text , ArraySlice < GlyphInfo > glyphInfos ,
167+ private ShapedBuffer ( ReadOnlyMemory < char > text ,
168+ ArraySlice < GlyphInfo > glyphInfos , ArraySlice < ushort > glyphIndices ,
156169 GlyphTypeface glyphTypeface , double fontRenderingEmSize , sbyte bidiLevel ,
157170 IRef < PooledArray < GlyphInfo > > ? sourceGlyphRef ,
171+ IRef < PooledArray < ushort > > ? sourceGlyphIndicesRef ,
158172 IRef < PooledArray < double > > ? sourcePrefixRef ,
159173 IRef < PooledArray < int > > ? sourceStartsRef ,
160174 int clusterStartIdx , int clusterCount , int sourceCacheGeneration )
161175 {
162176 Text = text ;
163177 _glyphInfos = glyphInfos ;
178+ _glyphIndices = glyphIndices ;
164179 GlyphTypeface = glyphTypeface ;
165180 FontRenderingEmSize = fontRenderingEmSize ;
166181 BidiLevel = bidiLevel ;
167182 _glyphRef = sourceGlyphRef ? . Clone ( ) ;
183+ _glyphIndicesRef = sourceGlyphIndicesRef ? . Clone ( ) ;
168184
169185 if ( sourcePrefixRef is not null )
170186 {
@@ -193,6 +209,16 @@ private ShapedBuffer(ReadOnlyMemory<char> text, ArraySlice<GlyphInfo> glyphInfos
193209 /// </summary>
194210 internal ArraySlice < GlyphInfo > GlyphInfos => _glyphInfos ;
195211
212+ /// <summary>
213+ /// Contiguous view of the glyph indices for this buffer, kept in sync with
214+ /// <see cref="GlyphInfos"/> by the indexer setter. Consumers needing a
215+ /// <see cref="ReadOnlySpan{T}"/> of glyph IDs (e.g. for
216+ /// <c>GlyphTypeface.TryGetGlyphBounds</c> or
217+ /// <c>SKFont.GetGlyphWidths</c>) can use this directly without allocating
218+ /// a parallel array.
219+ /// </summary>
220+ public ReadOnlySpan < ushort > GlyphIndices => _glyphIndices . Span ;
221+
196222 /// <summary>
197223 /// The buffer's glyph typeface.
198224 /// </summary>
@@ -230,6 +256,10 @@ public void Dispose()
230256 _glyphRef = null ;
231257 _glyphInfos = ArraySlice < GlyphInfo > . Empty ; // ensure we don't misuse a returned array
232258
259+ _glyphIndicesRef ? . Dispose ( ) ;
260+ _glyphIndicesRef = null ;
261+ _glyphIndices = ArraySlice < ushort > . Empty ;
262+
233263 ReleaseClusterCacheRefs ( ) ;
234264 _clusterPrefix = null ;
235265 _clusterStartChars = null ;
@@ -259,6 +289,7 @@ public GlyphInfo this[int index]
259289 set
260290 {
261291 _glyphInfos [ index ] = value ;
292+ _glyphIndices [ index ] = value . GlyphIndex ;
262293 // Bump the shared glyph generation so any sibling that built a
263294 // cluster cache against the pre-mutation glyphs will detect the
264295 // mismatch on its next EnsureClusterCache call and rebuild.
@@ -558,8 +589,8 @@ internal ShapedBuffer WithBidiLevel(sbyte paragraphEmbeddingLevel)
558589 }
559590
560591 return new ShapedBuffer (
561- Text , _glyphInfos , GlyphTypeface , FontRenderingEmSize , paragraphEmbeddingLevel ,
562- _glyphRef , prefixRef , startsRef , startIdx , count , _cacheGeneration ) ;
592+ Text , _glyphInfos , _glyphIndices , GlyphTypeface , FontRenderingEmSize , paragraphEmbeddingLevel ,
593+ _glyphRef , _glyphIndicesRef , prefixRef , startsRef , startIdx , count , _cacheGeneration ) ;
563594 }
564595
565596 int IReadOnlyCollection < GlyphInfo > . Count => _glyphInfos . Length ;
@@ -579,7 +610,9 @@ public SplitResult<ShapedBuffer> Split(int textLength)
579610 if ( textLength <= 0 )
580611 {
581612 var emptyBuffer = new ShapedBuffer (
582- Text . Slice ( 0 , 0 ) , _glyphInfos . Slice ( _glyphInfos . Start , 0 ) ,
613+ Text . Slice ( 0 , 0 ) ,
614+ _glyphInfos . Slice ( _glyphInfos . Start , 0 ) ,
615+ _glyphIndices . Slice ( _glyphIndices . Start , 0 ) ,
583616 GlyphTypeface , FontRenderingEmSize , BidiLevel ) ;
584617
585618 return new SplitResult < ShapedBuffer > ( emptyBuffer , this ) ;
@@ -645,6 +678,8 @@ private SplitResult<ShapedBuffer> SplitAscending(int textLength)
645678
646679 var firstGlyphs = _glyphInfos . Slice ( sliceStart , splitGlyphIndex ) ;
647680 var secondGlyphs = _glyphInfos . Slice ( sliceStart + splitGlyphIndex , glyphInfosLength - splitGlyphIndex ) ;
681+ var firstGlyphIndices = _glyphIndices . Slice ( sliceStart , splitGlyphIndex ) ;
682+ var secondGlyphIndices = _glyphIndices . Slice ( sliceStart + splitGlyphIndex , glyphInfosLength - splitGlyphIndex ) ;
648683
649684 var firstText = Text . Slice ( 0 , splitCharCount ) ;
650685 var secondText = Text . Slice ( splitCharCount ) ;
@@ -655,9 +690,9 @@ private SplitResult<ShapedBuffer> SplitAscending(int textLength)
655690 var leadingClusterCount = FindClusterOffsetForSplit ( splitCharCount ) ;
656691
657692 var leading = new ShapedBuffer (
658- firstText , firstGlyphs ,
693+ firstText , firstGlyphs , firstGlyphIndices ,
659694 GlyphTypeface , FontRenderingEmSize , BidiLevel ,
660- _glyphRef , _prefixRef , _startsRef ,
695+ _glyphRef , _glyphIndicesRef , _prefixRef , _startsRef ,
661696 _clusterStartIdx , leadingClusterCount , _cacheGeneration ) ;
662697
663698 if ( secondText . Length == 0 )
@@ -666,9 +701,9 @@ private SplitResult<ShapedBuffer> SplitAscending(int textLength)
666701 }
667702
668703 var trailing = new ShapedBuffer (
669- secondText , secondGlyphs ,
704+ secondText , secondGlyphs , secondGlyphIndices ,
670705 GlyphTypeface , FontRenderingEmSize , BidiLevel ,
671- _glyphRef , _prefixRef , _startsRef ,
706+ _glyphRef , _glyphIndicesRef , _prefixRef , _startsRef ,
672707 _clusterStartIdx + leadingClusterCount , _clusterCount - leadingClusterCount , _cacheGeneration ) ;
673708
674709 return new SplitResult < ShapedBuffer > ( leading , trailing ) ;
@@ -718,6 +753,8 @@ private SplitResult<ShapedBuffer> SplitDescending(int textLength)
718753 // Visual trailing = glyphs [splitGlyphIndex, end) → logically text[0..textLength] (our "first")
719754 var secondGlyphs = _glyphInfos . Slice ( sliceStart , splitGlyphIndex ) ;
720755 var firstGlyphs = _glyphInfos . Slice ( sliceStart + splitGlyphIndex , glyphInfosLength - splitGlyphIndex ) ;
756+ var secondGlyphIndices = _glyphIndices . Slice ( sliceStart , splitGlyphIndex ) ;
757+ var firstGlyphIndices = _glyphIndices . Slice ( sliceStart + splitGlyphIndex , glyphInfosLength - splitGlyphIndex ) ;
721758
722759 var firstText = Text . Slice ( 0 , textLength ) ;
723760 var secondText = Text . Slice ( textLength ) ;
@@ -730,9 +767,9 @@ private SplitResult<ShapedBuffer> SplitDescending(int textLength)
730767 var firstClusterCount = FindClusterOffsetForSplit ( textLength ) ;
731768
732769 var first = new ShapedBuffer (
733- firstText , firstGlyphs ,
770+ firstText , firstGlyphs , firstGlyphIndices ,
734771 GlyphTypeface , FontRenderingEmSize , BidiLevel ,
735- _glyphRef , _prefixRef , _startsRef ,
772+ _glyphRef , _glyphIndicesRef , _prefixRef , _startsRef ,
736773 _clusterStartIdx , firstClusterCount , _cacheGeneration ) ;
737774
738775 if ( secondText . Length == 0 || secondGlyphs . Length == 0 )
@@ -741,9 +778,9 @@ private SplitResult<ShapedBuffer> SplitDescending(int textLength)
741778 }
742779
743780 var second = new ShapedBuffer (
744- secondText , secondGlyphs ,
781+ secondText , secondGlyphs , secondGlyphIndices ,
745782 GlyphTypeface , FontRenderingEmSize , BidiLevel ,
746- _glyphRef , _prefixRef , _startsRef ,
783+ _glyphRef , _glyphIndicesRef , _prefixRef , _startsRef ,
747784 _clusterStartIdx + firstClusterCount , _clusterCount - firstClusterCount , _cacheGeneration ) ;
748785
749786 return new SplitResult < ShapedBuffer > ( first , second ) ;
0 commit comments