@@ -481,19 +481,36 @@ extension AttributedString.Runs {
481
481
internal func _slicedRunBoundary(
482
482
after i: AttributedString . Index ,
483
483
attributeNames: [ String ] ,
484
- constraints: [ AttributeRunBoundaries ] ,
484
+ constraints: Set < AttributeRunBoundaries ? > ,
485
485
endOfCurrent: Bool
486
486
) -> AttributedString . Index {
487
487
precondition (
488
488
self . _strBounds. contains ( i. _value) ,
489
489
" AttributedString index is out of bounds " )
490
490
precondition ( !attributeNames. isEmpty)
491
491
let r = _guts. findRun ( at: i. _value)
492
+ let currentRangeIdx = _strBounds. rangeIdx ( containing: i. _value)
493
+ let currentRange = _strBounds. ranges [ currentRangeIdx]
494
+
495
+ guard constraints. count != 1 || constraints. contains ( nil ) else {
496
+ // We have a single constraint and attributes are guaranteed to be consistent between constraint boundaries
497
+ // This means that we will not break until the next constraint boundary, so we don't need to enumerate the actual run contents
498
+ let constraintBreak = _guts. string. _firstConstraintBreak ( in: i. _value ..< currentRange. upperBound, with: constraints)
499
+ if !endOfCurrent && constraintBreak == currentRange. upperBound {
500
+ // No constraint break, return the next subrange start or the end index
501
+ if currentRangeIdx == _strBounds. ranges. count - 1 {
502
+ return . init( currentRange. upperBound, version: _guts. version)
503
+ } else {
504
+ return . init( _strBounds. ranges [ currentRangeIdx + 1 ] . lowerBound, version: _guts. version)
505
+ }
506
+ } else {
507
+ return . init( constraintBreak, version: _guts. version)
508
+ }
509
+ }
510
+
492
511
let endRun = _lastOfMatchingRuns ( with: r. runIndex, comparing: attributeNames)
493
512
let utf8End = endRun. utf8Offset + _guts. runs [ endRun] . length
494
513
let strIndexEnd = _guts. string. utf8. index ( r. start, offsetBy: utf8End - r. start. utf8Offset)
495
- let currentRangeIdx = _strBounds. rangeIdx ( containing: i. _value)
496
- let currentRange = _strBounds. ranges [ currentRangeIdx]
497
514
if strIndexEnd < currentRange. upperBound {
498
515
// The coalesced run ends within the current range, so just look for the next break in the coalesced run
499
516
return . init( _guts. string. _firstConstraintBreak ( in: i. _value ..< strIndexEnd, with: constraints) , version: _guts. version)
@@ -520,7 +537,7 @@ extension AttributedString.Runs {
520
537
internal func _slicedRunBoundary(
521
538
before i: AttributedString . Index ,
522
539
attributeNames: [ String ] ,
523
- constraints: [ AttributeRunBoundaries ] ,
540
+ constraints: Set < AttributeRunBoundaries ? > ,
524
541
endOfPrevious: Bool
525
542
) -> AttributedString . Index {
526
543
precondition (
@@ -545,6 +562,11 @@ extension AttributedString.Runs {
545
562
currentStringIdx = currentRange. upperBound
546
563
if endOfPrevious { return . init( currentStringIdx, version: _guts. version) }
547
564
}
565
+
566
+ guard constraints. count != 1 || constraints. contains ( nil ) else {
567
+ return . init( _guts. string. _lastConstraintBreak ( in: currentRange. lowerBound ..< currentStringIdx, with: constraints) , version: _guts. version)
568
+ }
569
+
548
570
let beforeStringIdx = _guts. string. utf8. index ( before: currentStringIdx)
549
571
let r = _guts. runs. index ( atUTF8Offset: beforeStringIdx. utf8Offset)
550
572
let startRun = _firstOfMatchingRuns ( with: r. index, comparing: attributeNames)
@@ -561,7 +583,7 @@ extension AttributedString.Runs {
561
583
internal func _slicedRunBoundary(
562
584
roundingDown i: AttributedString . Index ,
563
585
attributeNames: [ String ] ,
564
- constraints: [ AttributeRunBoundaries ]
586
+ constraints: Set < AttributeRunBoundaries ? >
565
587
) -> ( index: AttributedString . Index , runIndex: AttributedString . _InternalRuns . Index ) {
566
588
precondition (
567
589
_strBounds. contains ( i. _value) || i. _value == endIndex. _stringIndex,
@@ -571,8 +593,22 @@ extension AttributedString.Runs {
571
593
if r. runIndex. offset == endIndex. _runOffset {
572
594
return ( i, r. runIndex)
573
595
}
574
- let startRun = _firstOfMatchingRuns ( with: r. runIndex, comparing: attributeNames)
575
596
let currentRange = _strBounds. ranges [ _strBounds. rangeIdx ( containing: i. _value) ]
597
+
598
+ guard constraints. count != 1 || constraints. contains ( nil ) else {
599
+ let nextIndex = _guts. string. unicodeScalars. index ( after: i. _value)
600
+ let constraintBreak = _guts. string. _lastConstraintBreak ( in: currentRange. lowerBound ..< nextIndex, with: constraints)
601
+ var runIdx = r. runIndex
602
+ while runIdx. utf8Offset > constraintBreak. utf8Offset {
603
+ _guts. runs. formIndex ( before: & runIdx)
604
+ }
605
+ return (
606
+ . init( constraintBreak, version: _guts. version) ,
607
+ runIdx
608
+ )
609
+ }
610
+
611
+ let startRun = _firstOfMatchingRuns ( with: r. runIndex, comparing: attributeNames)
576
612
let stringStart = Swift . max (
577
613
_guts. string. utf8. index ( r. start, offsetBy: startRun. utf8Offset - r. start. utf8Offset) ,
578
614
currentRange. lowerBound)
@@ -587,9 +623,9 @@ extension AttributedString.Runs {
587
623
extension BigString {
588
624
internal func _firstConstraintBreak(
589
625
in range: Range < Index > ,
590
- with constraints: [ AttributedString . AttributeRunBoundaries ]
626
+ with constraints: Set < AttributedString . AttributeRunBoundaries ? >
591
627
) -> Index {
592
- guard !constraints . isEmpty , ! range. isEmpty else { return range. upperBound }
628
+ guard !range. isEmpty else { return range. upperBound }
593
629
594
630
var r = range
595
631
if
@@ -602,7 +638,7 @@ extension BigString {
602
638
if constraints. _containsScalarConstraint {
603
639
// Note: we need to slice runs on matching scalars even if they don't carry
604
640
// the attributes we're looking for.
605
- let scalars : [ UnicodeScalar ] = constraints. compactMap { $0. _constrainedScalar }
641
+ let scalars : [ UnicodeScalar ] = constraints. compactMap { $0? . _constrainedScalar }
606
642
if let firstBreak = self . unicodeScalars [ r] . _findFirstScalarBoundary ( for: scalars) {
607
643
r = r. lowerBound ..< firstBreak
608
644
}
@@ -613,9 +649,9 @@ extension BigString {
613
649
614
650
internal func _lastConstraintBreak(
615
651
in range: Range < Index > ,
616
- with constraints: [ AttributedString . AttributeRunBoundaries ]
652
+ with constraints: Set < AttributedString . AttributeRunBoundaries ? >
617
653
) -> Index {
618
- guard !constraints . isEmpty , ! range. isEmpty else { return range. lowerBound }
654
+ guard !range. isEmpty else { return range. lowerBound }
619
655
620
656
var r = range
621
657
if
@@ -628,7 +664,7 @@ extension BigString {
628
664
if constraints. _containsScalarConstraint {
629
665
// Note: we need to slice runs on matching scalars even if they don't carry
630
666
// the attributes we're looking for.
631
- let scalars : [ UnicodeScalar ] = constraints. compactMap { $0. _constrainedScalar }
667
+ let scalars : [ UnicodeScalar ] = constraints. compactMap { $0? . _constrainedScalar }
632
668
if let lastBreak = self . unicodeScalars [ r] . _findLastScalarBoundary ( for: scalars) {
633
669
r = lastBreak ..< r. upperBound
634
670
}
0 commit comments