@@ -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 /// <summary>
@@ -576,7 +607,15 @@ internal ShapedBuffer CloneWritable()
576607
577608 span . CopyTo ( glyphs ) ;
578609
579- return new ShapedBuffer ( Text , new ArraySlice < GlyphInfo > ( glyphs ) , GlyphTypeface , FontRenderingEmSize , BidiLevel ) ;
610+ // The glyph indices ride in their own slice and must stay aligned with the
611+ // copied infos; justification mutates advances only, so a plain copy holds.
612+ var indicesSpan = _glyphIndices . Span ;
613+ var indices = new ushort [ indicesSpan . Length ] ;
614+
615+ indicesSpan . CopyTo ( indices ) ;
616+
617+ return new ShapedBuffer ( Text , new ArraySlice < GlyphInfo > ( glyphs ) , new ArraySlice < ushort > ( indices ) ,
618+ GlyphTypeface , FontRenderingEmSize , BidiLevel ) ;
580619 }
581620
582621 int IReadOnlyCollection < GlyphInfo > . Count => _glyphInfos . Length ;
@@ -596,7 +635,9 @@ public SplitResult<ShapedBuffer> Split(int textLength)
596635 if ( textLength <= 0 )
597636 {
598637 var emptyBuffer = new ShapedBuffer (
599- Text . Slice ( 0 , 0 ) , _glyphInfos . Slice ( _glyphInfos . Start , 0 ) ,
638+ Text . Slice ( 0 , 0 ) ,
639+ _glyphInfos . Slice ( _glyphInfos . Start , 0 ) ,
640+ _glyphIndices . Slice ( _glyphIndices . Start , 0 ) ,
600641 GlyphTypeface , FontRenderingEmSize , BidiLevel ) ;
601642
602643 return new SplitResult < ShapedBuffer > ( emptyBuffer , this ) ;
@@ -662,6 +703,8 @@ private SplitResult<ShapedBuffer> SplitAscending(int textLength)
662703
663704 var firstGlyphs = _glyphInfos . Slice ( sliceStart , splitGlyphIndex ) ;
664705 var secondGlyphs = _glyphInfos . Slice ( sliceStart + splitGlyphIndex , glyphInfosLength - splitGlyphIndex ) ;
706+ var firstGlyphIndices = _glyphIndices . Slice ( sliceStart , splitGlyphIndex ) ;
707+ var secondGlyphIndices = _glyphIndices . Slice ( sliceStart + splitGlyphIndex , glyphInfosLength - splitGlyphIndex ) ;
665708
666709 var firstText = Text . Slice ( 0 , splitCharCount ) ;
667710 var secondText = Text . Slice ( splitCharCount ) ;
@@ -672,9 +715,9 @@ private SplitResult<ShapedBuffer> SplitAscending(int textLength)
672715 var leadingClusterCount = FindClusterOffsetForSplit ( splitCharCount ) ;
673716
674717 var leading = new ShapedBuffer (
675- firstText , firstGlyphs ,
718+ firstText , firstGlyphs , firstGlyphIndices ,
676719 GlyphTypeface , FontRenderingEmSize , BidiLevel ,
677- _glyphRef , _prefixRef , _startsRef ,
720+ _glyphRef , _glyphIndicesRef , _prefixRef , _startsRef ,
678721 _clusterStartIdx , leadingClusterCount , _cacheGeneration ) ;
679722
680723 if ( secondText . Length == 0 )
@@ -683,9 +726,9 @@ private SplitResult<ShapedBuffer> SplitAscending(int textLength)
683726 }
684727
685728 var trailing = new ShapedBuffer (
686- secondText , secondGlyphs ,
729+ secondText , secondGlyphs , secondGlyphIndices ,
687730 GlyphTypeface , FontRenderingEmSize , BidiLevel ,
688- _glyphRef , _prefixRef , _startsRef ,
731+ _glyphRef , _glyphIndicesRef , _prefixRef , _startsRef ,
689732 _clusterStartIdx + leadingClusterCount , _clusterCount - leadingClusterCount , _cacheGeneration ) ;
690733
691734 return new SplitResult < ShapedBuffer > ( leading , trailing ) ;
@@ -735,6 +778,8 @@ private SplitResult<ShapedBuffer> SplitDescending(int textLength)
735778 // Visual trailing = glyphs [splitGlyphIndex, end) → logically text[0..textLength] (our "first")
736779 var secondGlyphs = _glyphInfos . Slice ( sliceStart , splitGlyphIndex ) ;
737780 var firstGlyphs = _glyphInfos . Slice ( sliceStart + splitGlyphIndex , glyphInfosLength - splitGlyphIndex ) ;
781+ var secondGlyphIndices = _glyphIndices . Slice ( sliceStart , splitGlyphIndex ) ;
782+ var firstGlyphIndices = _glyphIndices . Slice ( sliceStart + splitGlyphIndex , glyphInfosLength - splitGlyphIndex ) ;
738783
739784 var firstText = Text . Slice ( 0 , textLength ) ;
740785 var secondText = Text . Slice ( textLength ) ;
@@ -747,9 +792,9 @@ private SplitResult<ShapedBuffer> SplitDescending(int textLength)
747792 var firstClusterCount = FindClusterOffsetForSplit ( textLength ) ;
748793
749794 var first = new ShapedBuffer (
750- firstText , firstGlyphs ,
795+ firstText , firstGlyphs , firstGlyphIndices ,
751796 GlyphTypeface , FontRenderingEmSize , BidiLevel ,
752- _glyphRef , _prefixRef , _startsRef ,
797+ _glyphRef , _glyphIndicesRef , _prefixRef , _startsRef ,
753798 _clusterStartIdx , firstClusterCount , _cacheGeneration ) ;
754799
755800 if ( secondText . Length == 0 || secondGlyphs . Length == 0 )
@@ -758,9 +803,9 @@ private SplitResult<ShapedBuffer> SplitDescending(int textLength)
758803 }
759804
760805 var second = new ShapedBuffer (
761- secondText , secondGlyphs ,
806+ secondText , secondGlyphs , secondGlyphIndices ,
762807 GlyphTypeface , FontRenderingEmSize , BidiLevel ,
763- _glyphRef , _prefixRef , _startsRef ,
808+ _glyphRef , _glyphIndicesRef , _prefixRef , _startsRef ,
764809 _clusterStartIdx + firstClusterCount , _clusterCount - firstClusterCount , _cacheGeneration ) ;
765810
766811 return new SplitResult < ShapedBuffer > ( first , second ) ;
0 commit comments