@@ -21,7 +21,9 @@ let layout: LayoutModule['layout']
2121let layoutWithLines : LayoutModule [ 'layoutWithLines' ]
2222let layoutNextLine : LayoutModule [ 'layoutNextLine' ]
2323let layoutNextLineRange : LayoutModule [ 'layoutNextLineRange' ]
24+ let materializeLineRange : LayoutModule [ 'materializeLineRange' ]
2425let measureLineStats : LayoutModule [ 'measureLineStats' ]
26+ let measureNaturalWidth : LayoutModule [ 'measureNaturalWidth' ]
2527let walkLineRanges : LayoutModule [ 'walkLineRanges' ]
2628let clearCache : LayoutModule [ 'clearCache' ]
2729let setLocale : LayoutModule [ 'setLocale' ]
@@ -31,6 +33,7 @@ let stepPreparedLineGeometry: LineBreakModule['stepPreparedLineGeometry']
3133let walkPreparedLines : LineBreakModule [ 'walkPreparedLines' ]
3234let getSegmentBreakableFitAdvances : MeasurementModule [ 'getSegmentBreakableFitAdvances' ]
3335let prepareRichInline : RichInlineModule [ 'prepareRichInline' ]
36+ let layoutNextRichInlineLineRange : RichInlineModule [ 'layoutNextRichInlineLineRange' ]
3437let materializeRichInlineLineRange : RichInlineModule [ 'materializeRichInlineLineRange' ]
3538let measureRichInlineStats : RichInlineModule [ 'measureRichInlineStats' ]
3639let walkRichInlineLineRanges : RichInlineModule [ 'walkRichInlineLineRanges' ]
@@ -280,14 +283,16 @@ beforeAll(async () => {
280283 layoutWithLines,
281284 layoutNextLine,
282285 layoutNextLineRange,
286+ materializeLineRange,
283287 measureLineStats,
288+ measureNaturalWidth,
284289 walkLineRanges,
285290 clearCache,
286291 setLocale,
287292 } = mod )
288293 ; ( { countPreparedLines, measurePreparedLineGeometry, stepPreparedLineGeometry, walkPreparedLines } = lineBreakMod )
289294 ; ( { getSegmentBreakableFitAdvances } = measurementMod )
290- ; ( { prepareRichInline, materializeRichInlineLineRange, measureRichInlineStats, walkRichInlineLineRanges } = richInlineMod )
295+ ; ( { prepareRichInline, layoutNextRichInlineLineRange , materializeRichInlineLineRange, measureRichInlineStats, walkRichInlineLineRanges } = richInlineMod )
291296} )
292297
293298beforeEach ( ( ) => {
@@ -747,6 +752,30 @@ describe('rich-inline invariants', () => {
747752 } )
748753 } )
749754
755+ test ( 'layoutNextRichInlineLineRange streams the same ranges as the walker' , ( ) => {
756+ const prepared = prepareRichInline ( [
757+ { text : 'Ship ' , font : FONT } ,
758+ { text : '@maya' , font : '700 12px Test Sans' , break : 'never' , extraWidth : 18 } ,
759+ { text : "'s rich note wraps cleanly" , font : FONT } ,
760+ ] )
761+ const walked : Array < { end : TestLayoutCursor & { itemIndex : number } , width : number } > = [ ]
762+ walkRichInlineLineRanges ( prepared , 120 , line => {
763+ walked . push ( { end : line . end , width : line . width } )
764+ } )
765+
766+ const streamed : typeof walked = [ ]
767+ let cursor = { itemIndex : 0 , segmentIndex : 0 , graphemeIndex : 0 }
768+ while ( true ) {
769+ const line = layoutNextRichInlineLineRange ( prepared , 120 , cursor )
770+ if ( line === null ) break
771+ streamed . push ( { end : line . end , width : line . width } )
772+ cursor = line . end
773+ }
774+
775+ expect ( streamed ) . toEqual ( walked )
776+ expect ( streamed . length ) . toBeGreaterThan ( 1 )
777+ } )
778+
750779 test ( 'non-materializing range walker matches range materialization' , ( ) => {
751780 const prepared = prepareRichInline ( [
752781 { text : 'Ship ' , font : FONT } ,
@@ -1425,6 +1454,16 @@ describe('layout invariants', () => {
14251454 } ) ) )
14261455 } )
14271456
1457+ test ( 'materializeLineRange reproduces streamed layout lines' , ( ) => {
1458+ const prepared = prepareWithSegments ( 'foo trans\u00ADatlantic said "hello" to 世界 and waved.' , FONT )
1459+ const width = prepared . widths [ 0 ] ! + prepared . widths [ 1 ] ! + prepared . widths [ 2 ] ! + prepared . breakableFitAdvances [ 4 ] ! [ 0 ] ! + prepared . discretionaryHyphenWidth + 0.1
1460+ const expected = layoutWithLines ( prepared , width , LINE_HEIGHT ) . lines [ 0 ] !
1461+ const range = layoutNextLineRange ( prepared , { segmentIndex : 0 , graphemeIndex : 0 } , width )
1462+
1463+ expect ( range ) . not . toBeNull ( )
1464+ expect ( materializeLineRange ( prepared , range ! ) ) . toEqual ( expected )
1465+ } )
1466+
14281467 test ( 'measureLineStats matches walked line count and widest line' , ( ) => {
14291468 const prepared = prepareWithSegments ( 'foo trans\u00ADatlantic said "hello" to 世界 and waved.' , FONT )
14301469 const width = prepared . widths [ 0 ] ! + prepared . widths [ 1 ] ! + prepared . widths [ 2 ] ! + prepared . breakableFitAdvances [ 4 ] ! [ 0 ] ! + prepared . discretionaryHyphenWidth + 0.1
@@ -1442,6 +1481,12 @@ describe('layout invariants', () => {
14421481 } )
14431482 } )
14441483
1484+ test ( 'measureNaturalWidth returns the widest forced line' , ( ) => {
1485+ const prepared = prepareWithSegments ( 'wide line\nfit\nmid' , FONT , { whiteSpace : 'pre-wrap' } )
1486+
1487+ expect ( measureNaturalWidth ( prepared ) ) . toBe ( measureWidth ( 'wide line' , FONT ) )
1488+ } )
1489+
14451490 test ( 'line-break geometry helpers stay aligned with streamed line ranges' , ( ) => {
14461491 const prepared = prepareWithSegments ( 'foo trans\u00ADatlantic said "hello" to 世界 and waved.' , FONT )
14471492 const widths = [ 48 , 72 , 120 ]
0 commit comments