@@ -1182,42 +1182,40 @@ VerticalOrientationType.Rotate or
1182
1182
lineBreaks . Add ( lineBreakEnumerator . Current ) ;
1183
1183
}
1184
1184
1185
- int usedOffset = 0 ;
1185
+ int processed = 0 ;
1186
1186
while ( textLine . Count > 0 )
1187
1187
{
1188
1188
LineBreak ? bestBreak = null ;
1189
1189
foreach ( LineBreak lineBreak in lineBreaks )
1190
1190
{
1191
- // Adjust the break index relative to the current position in the original line
1192
- int measureAt = lineBreak . PositionMeasure - usedOffset ;
1193
-
1194
- // Skip breaks that are already behind the trimmed portion
1195
- if ( measureAt < 0 )
1191
+ // Skip breaks that are already behind the processed portion
1192
+ if ( lineBreak . PositionWrap <= processed )
1196
1193
{
1197
1194
continue ;
1198
1195
}
1199
1196
1200
1197
// Measure the text up to the adjusted break point
1201
- float measure = textLine . MeasureAt ( measureAt ) ;
1202
- if ( measure > wrappingLength )
1198
+ float advance = textLine . MeasureAt ( lineBreak . PositionMeasure - processed ) ;
1199
+ if ( advance >= wrappingLength )
1203
1200
{
1204
- // Stop and use the best break so far
1205
1201
bestBreak ??= lineBreak ;
1206
1202
break ;
1207
1203
}
1208
1204
1209
- // Update the best break
1210
- bestBreak = lineBreak ;
1211
-
1212
1205
// If it's a mandatory break, stop immediately
1213
1206
if ( lineBreak . Required )
1214
1207
{
1208
+ bestBreak = lineBreak ;
1215
1209
break ;
1216
1210
}
1211
+
1212
+ // Update the best break
1213
+ bestBreak = lineBreak ;
1217
1214
}
1218
1215
1219
1216
if ( bestBreak != null )
1220
1217
{
1218
+ LineBreak breakAt = bestBreak . Value ;
1221
1219
if ( breakAll )
1222
1220
{
1223
1221
// Break-all works differently to the other modes.
@@ -1226,30 +1224,34 @@ VerticalOrientationType.Rotate or
1226
1224
TextLine ? remaining ;
1227
1225
if ( bestBreak . Value . Required )
1228
1226
{
1229
- if ( textLine . TrySplitAt ( bestBreak . Value , keepAll , out remaining ) )
1227
+ if ( textLine . TrySplitAt ( breakAt , keepAll , out remaining ) )
1230
1228
{
1231
- usedOffset += textLine . Count ;
1229
+ processed = breakAt . PositionWrap ;
1232
1230
textLines . Add ( textLine . Finalize ( options ) ) ;
1233
1231
textLine = remaining ;
1234
1232
}
1235
1233
}
1236
1234
else if ( textLine . TrySplitAt ( wrappingLength , out remaining ) )
1237
1235
{
1238
- usedOffset += textLine . Count ;
1236
+ processed += textLine . Count ;
1239
1237
textLines . Add ( textLine . Finalize ( options ) ) ;
1240
1238
textLine = remaining ;
1241
1239
}
1242
1240
else
1243
1241
{
1244
- usedOffset += textLine . Count ;
1242
+ processed += textLine . Count ;
1245
1243
}
1246
1244
}
1247
1245
else
1248
1246
{
1249
1247
// Split the current line at the adjusted break index
1250
- if ( textLine . TrySplitAt ( bestBreak . Value , keepAll , out TextLine ? remaining ) )
1248
+ if ( textLine . TrySplitAt ( breakAt , keepAll , out TextLine ? remaining ) )
1251
1249
{
1252
- usedOffset += textLine . Count ;
1250
+ // If 'keepAll' is true then the break could be later than expected.
1251
+ processed = keepAll
1252
+ ? processed + Math . Max ( textLine . Count , breakAt . PositionWrap - processed )
1253
+ : breakAt . PositionWrap ;
1254
+
1253
1255
if ( breakWord )
1254
1256
{
1255
1257
// A break was found, but we need to check if the line is too long
@@ -1258,7 +1260,7 @@ VerticalOrientationType.Rotate or
1258
1260
textLine . TrySplitAt ( wrappingLength , out TextLine ? overflow ) )
1259
1261
{
1260
1262
// Reinsert the overflow at the beginning of the remaining line
1261
- usedOffset -= overflow . Count ;
1263
+ processed -= overflow . Count ;
1262
1264
remaining . InsertAt ( 0 , overflow ) ;
1263
1265
}
1264
1266
}
@@ -1269,13 +1271,14 @@ VerticalOrientationType.Rotate or
1269
1271
}
1270
1272
else
1271
1273
{
1272
- usedOffset += textLine . Count ;
1274
+ processed += textLine . Count ;
1273
1275
}
1274
1276
}
1275
1277
}
1276
1278
else
1277
1279
{
1278
- // If no valid break is found, add the remaining line and exit
1280
+ // We're at the last line break which should be at the end of the
1281
+ // text. We can break here and finalize the line.
1279
1282
if ( breakWord || breakAll )
1280
1283
{
1281
1284
while ( textLine . ScaledLineAdvance > wrappingLength )
@@ -1316,6 +1319,7 @@ public float ScaledMaxAdvance()
1316
1319
internal sealed class TextLine
1317
1320
{
1318
1321
private readonly List < GlyphLayoutData > data ;
1322
+ private readonly Dictionary < int , float > advances = new ( ) ;
1319
1323
1320
1324
public TextLine ( ) => this . data = new ( 16 ) ;
1321
1325
@@ -1383,6 +1387,11 @@ public void InsertAt(int index, TextLine textLine)
1383
1387
1384
1388
public float MeasureAt ( int index )
1385
1389
{
1390
+ if ( this . advances . TryGetValue ( index , out float advance ) )
1391
+ {
1392
+ return advance ;
1393
+ }
1394
+
1386
1395
if ( index >= this . data . Count )
1387
1396
{
1388
1397
index = this . data . Count - 1 ;
@@ -1395,12 +1404,13 @@ public float MeasureAt(int index)
1395
1404
index -- ;
1396
1405
}
1397
1406
1398
- float advance = 0 ;
1407
+ advance = 0 ;
1399
1408
for ( int i = 0 ; i <= index ; i ++ )
1400
1409
{
1401
1410
advance += this . data [ i ] . ScaledAdvance ;
1402
1411
}
1403
1412
1413
+ this . advances [ index ] = advance ;
1404
1414
return advance ;
1405
1415
}
1406
1416
@@ -1440,11 +1450,11 @@ public bool TrySplitAt(float length, [NotNullWhen(true)] out TextLine? result)
1440
1450
public bool TrySplitAt ( LineBreak lineBreak , bool keepAll , [ NotNullWhen ( true ) ] out TextLine ? result )
1441
1451
{
1442
1452
int index = this . data . Count ;
1443
- GlyphLayoutData glyphWrap = default ;
1453
+ GlyphLayoutData glyphData = default ;
1444
1454
while ( index > 0 )
1445
1455
{
1446
- glyphWrap = this . data [ -- index ] ;
1447
- if ( glyphWrap . CodePointIndex == lineBreak . PositionWrap )
1456
+ glyphData = this . data [ -- index ] ;
1457
+ if ( glyphData . CodePointIndex == lineBreak . PositionWrap )
1448
1458
{
1449
1459
break ;
1450
1460
}
@@ -1455,14 +1465,14 @@ public bool TrySplitAt(LineBreak lineBreak, bool keepAll, [NotNullWhen(true)] ou
1455
1465
if ( index > 0
1456
1466
&& ! lineBreak . Required
1457
1467
&& keepAll
1458
- && UnicodeUtility . IsCJKCodePoint ( ( uint ) glyphWrap . CodePoint . Value ) )
1468
+ && UnicodeUtility . IsCJKCodePoint ( ( uint ) glyphData . CodePoint . Value ) )
1459
1469
{
1460
1470
// Loop through previous glyphs to see if there is
1461
1471
// a non CJK codepoint we can break at.
1462
1472
while ( index > 0 )
1463
1473
{
1464
- glyphWrap = this . data [ -- index ] ;
1465
- if ( ! UnicodeUtility . IsCJKCodePoint ( ( uint ) glyphWrap . CodePoint . Value ) )
1474
+ glyphData = this . data [ -- index ] ;
1475
+ if ( ! UnicodeUtility . IsCJKCodePoint ( ( uint ) glyphData . CodePoint . Value ) )
1466
1476
{
1467
1477
index ++ ;
1468
1478
break ;
@@ -1694,6 +1704,7 @@ private static void RecalculateLineMetrics(TextLine textLine)
1694
1704
textLine . ScaledMaxAscender = ascender ;
1695
1705
textLine . ScaledMaxDescender = descender ;
1696
1706
textLine . ScaledMaxLineHeight = lineHeight ;
1707
+ textLine . advances . Clear ( ) ;
1697
1708
}
1698
1709
1699
1710
/// <summary>
0 commit comments