• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Tehreer / SheenBidi / 29167177394

11 Jul 2026 08:31PM UTC coverage: 96.086% (-0.09%) from 96.177%
29167177394

push

github

mta452
[lib] Invalidate paragraph user info on attribute changes

2 of 2 new or added lines in 1 file covered. (100.0%)

42 existing lines in 4 files now uncovered.

4051 of 4216 relevant lines covered (96.09%)

444218.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.79
/Source/API/SBTextIterators.c
1
/*
2
 * Copyright (C) 2025-2026 Muhammad Tayyab Akram
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
#include <API/SBBase.h>
18

19
#if SB_TEXT_API_SUPPORTED
20

21
#include <stddef.h>
22

23
#include <API/SBAssert.h>
24
#include <API/SBAttributeList.h>
25
#include <API/SBAttributeRegistry.h>
26
#include <API/SBLine.h>
27
#include <API/SBParagraph.h>
28
#include <API/SBText.h>
29
#include <Core/List.h>
30
#include <Core/Object.h>
31
#include <Text/AttributeDictionary.h>
32
#include <Text/AttributeManager.h>
33
#include <Text/BidiTypesBuffer.h>
34

35
#include "SBTextIterators.h"
36

37
/* ==========================================================================
38
 * Text Iterator Implementation
39
 * ========================================================================== */
40

41
/**
42
 * Resets a text iterator to process a specific range of text.
43
 *
44
 * @param iterator
45
 *      The text iterator to reset.
46
 * @param index
47
 *      The starting index of the range to process.
48
 * @param length
49
 *      The length of the range to process.
50
 */
51
static void ResetTextIterator(TextIteratorRef iterator, SBUInteger index, SBUInteger length);
52

53
/**
54
 * Initializes a text iterator with the specified text and direction mode.
55
 *
56
 * Prepares the iterator to traverse through the text. The direction mode determines whether the
57
 * iterator will follow visual ordering (for display) or logical ordering (for processing).
58
 *
59
 * @param iterator
60
 *      The text iterator to initialize.
61
 * @param text
62
 *      The text object to iterate over.
63
 * @param visualDirectionMode
64
 *      `SBTrue` for visual direction mode, `SBFalse` for logical direction mode.
65
 */
66
static void InitializeTextIterator(TextIteratorRef iterator, SBTextRef text,
50✔
67
    SBBoolean visualDirectionMode)
68
{
69
    iterator->text = SBTextRetain(text);
50✔
70
    iterator->visualDirectionMode = visualDirectionMode;
50✔
71

72
    ResetTextIterator(iterator, 0, text->buffer.codeUnits.count);
50✔
73
}
50✔
74

75
/**
76
 * Releases the text object held by the iterator and performs any necessary cleanup.
77
 *
78
 * @param iterator
79
 *      The text iterator to finalize.
80
 */
81
static void FinalizeTextIterator(TextIteratorRef iterator)
50✔
82
{
83
    SBTextRef text = iterator->text;
50✔
84

85
    if (text) {
50✔
86
        SBTextRelease(text);
50✔
87
    }
88
}
50✔
89

90
/**
91
 * Resets a text iterator to process a specific range of text.
92
 *
93
 * This function normalizes the input range and sets up the iterator state to begin processing text
94
 * from the specified position. If in visual direction mode, it determines the appropriate paragraph
95
 * and direction based on the text's bidirectional properties.
96
 *
97
 * @param iterator
98
 *      The text iterator to reset.
99
 * @param index
100
 *      The starting index of the range to process.
101
 * @param length
102
 *      The length of the range to process.
103
 */
104
static void ResetTextIterator(TextIteratorRef iterator, SBUInteger index, SBUInteger length)
61✔
105
{
106
    SBTextRef text = iterator->text;
61✔
107
    SBUInteger startIndex;
108
    SBUInteger endIndex;
109
    SBBoolean forwardMode;
110
    SBUInteger paragraphIndex;
111

112
    SBUIntegerNormalizeRange(text->buffer.codeUnits.count, &index, &length);
61✔
113

114
    /* Setup iterator boundary */
115
    startIndex = index;
61✔
116
    endIndex = index + length;
61✔
117
    forwardMode = SBTrue;
61✔
118
    paragraphIndex = SBInvalidIndex;
61✔
119

120
    if (length > 0) {
61✔
121
        /* Find out the index of the first paragraph */
122
        paragraphIndex = TextAnalysisGetCodeUnitParagraphIndex((TextAnalysisRef)&text->analysis, index);
55✔
123

124
        if (iterator->visualDirectionMode) {
55✔
125
            TextParagraphRef textParagraph = ListGetRef(&text->analysis.paragraphs, paragraphIndex);
13✔
126
            SBParagraphRef bidiParagraph = textParagraph->bidiParagraph;
13✔
127

128
            forwardMode = (bidiParagraph->baseLevel & 1) == 0;
13✔
129

130
            if (!forwardMode) {
13✔
131
                SBUInteger paragraphEnd = textParagraph->index + textParagraph->length;
3✔
132

133
                if (paragraphEnd < endIndex) {
3✔
134
                    paragraphIndex = TextAnalysisGetCodeUnitParagraphIndex((TextAnalysisRef)&text->analysis, endIndex - 1);
2✔
135
                }
136
            }
137
        }
138
    }
139

140
    /* Initialize the iterator state */
141
    iterator->forwardMode = forwardMode;
61✔
142
    iterator->startIndex = startIndex;
61✔
143
    iterator->endIndex = endIndex;
61✔
144
    iterator->paragraphIndex = paragraphIndex;
61✔
145

146
    /* Initialize the current element info to NULL */
147
    iterator->currentParagraph = NULL;
61✔
148
    iterator->paragraphStart = SBInvalidIndex;
61✔
149
    iterator->paragraphEnd = SBInvalidIndex;
61✔
150
}
61✔
151

152
static SBBoolean AdvanceTextIterator(TextIteratorRef iterator)
97✔
153
{
154
    /* Get the text reference and calculate the remaining length */
155
    SBTextRef text = iterator->text;
97✔
156
    SBUInteger remainingLength = iterator->endIndex - iterator->startIndex;
97✔
157

158
    if (remainingLength > 0) {
97✔
159
        /* Get the current paragraph and its boundaries */
160
        TextParagraphRef textParagraph = ListGetRef(&text->analysis.paragraphs, iterator->paragraphIndex);
58✔
161
        SBUInteger paragraphStart = textParagraph->index;
58✔
162
        SBUInteger paragraphEnd = paragraphStart + textParagraph->length;
58✔
163

164
        /* Adjust the paragraph boundaries to iterator range */
165
        if (paragraphStart <= iterator->startIndex) {
58✔
166
            paragraphStart = iterator->startIndex;
55✔
167
        }
168
        if (paragraphEnd >= iterator->endIndex) {
58✔
169
            paragraphEnd = iterator->endIndex;
42✔
170
        }
171

172
        /* Initialize the current element info */
173
        iterator->currentParagraph = textParagraph;
58✔
174
        iterator->paragraphStart = paragraphStart;
58✔
175
        iterator->paragraphEnd = paragraphEnd;
58✔
176

177
        /* Update iterator position based on the direction */
178
        if (iterator->forwardMode) {
58✔
179
            /* Move forward to the next paragraph */
180
            iterator->startIndex = paragraphEnd;
52✔
181
            iterator->paragraphIndex += 1;
52✔
182
        } else {
183
            /* Move backward to the previous paragraph */
184
            iterator->endIndex = paragraphStart;
6✔
185
            iterator->paragraphIndex -= 1;
6✔
186
        }
187

188
        return SBTrue;
58✔
189
    }
190

191
    /* No more text to process */
192
    return SBFalse;
39✔
193
}
194

195
/* ==========================================================================
196
 * Paragraph Iterator Implementation
197
 * ========================================================================== */
198

199
/**
200
 * Initializes a paragraph information structure.
201
 *
202
 * Sets default values for a paragraph's properties including its position, length, and base
203
 * embedding level.
204
 *
205
 * @param info
206
 *      Pointer to the paragraph info structure to initialize.
207
 */
208
static void InitializeParagraphInfo(SBParagraphInfo *info)
17✔
209
{
210
    info->index = SBInvalidIndex;
17✔
211
    info->length = 0;
17✔
212
    info->baseLevel = 0;
17✔
213
    info->userInfo = NULL;
17✔
214
}
17✔
215

216
/**
217
 * Cleans up resources associated with a paragraph iterator, including its parent text iterator.
218
 *
219
 * @param object
220
 *      The paragraph iterator to finalize.
221
 */
222
static void FinalizeParagraphIterator(ObjectRef object)
11✔
223
{
224
    SBParagraphIteratorRef iterator = object;
11✔
225
    FinalizeTextIterator(&iterator->parent);
11✔
226
}
11✔
227

228
SB_INTERNAL SBParagraphIteratorRef SBParagraphIteratorCreate(SBTextRef text)
11✔
229
{
230
    const SBUInteger size = sizeof(SBParagraphIterator);
11✔
231
    void *pointer = NULL;
11✔
232
    SBParagraphIteratorRef iterator;
233

234
    /* Text MUST be available. */
235
    SBAssert(text != NULL);
11✔
236

237
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeParagraphIterator);
11✔
238

239
    if (iterator) {
11✔
240
        InitializeTextIterator(&iterator->parent, text, SBFalse);
11✔
241
        InitializeParagraphInfo(&iterator->currentParagraph);
11✔
242
    }
243

244
    return iterator;
11✔
245
}
246

247
SBTextRef SBParagraphIteratorGetText(SBParagraphIteratorRef iterator)
1✔
248
{
249
    return iterator->parent.text;
1✔
250
}
251

252
void SBParagraphIteratorReset(SBParagraphIteratorRef iterator, SBUInteger index, SBUInteger length)
6✔
253
{
254
    ResetTextIterator(&iterator->parent, index, length);
6✔
255
    InitializeParagraphInfo(&iterator->currentParagraph);
6✔
256
}
6✔
257

258
const SBParagraphInfo *SBParagraphIteratorGetCurrent(SBParagraphIteratorRef iterator)
9✔
259
{
260
    return &iterator->currentParagraph;
9✔
261
}
262

263
SBBoolean SBParagraphIteratorMoveNext(SBParagraphIteratorRef iterator)
24✔
264
{
265
    TextIteratorRef parent = &iterator->parent;
24✔
266

267
    if (AdvanceTextIterator(parent)) {
24✔
268
        TextParagraphRef textParagraph = parent->currentParagraph;
18✔
269
        SBParagraphInfo *currentInfo;
270

271
        currentInfo = &iterator->currentParagraph;
18✔
272
        currentInfo->index = parent->paragraphStart;
18✔
273
        currentInfo->length = parent->paragraphEnd - parent->paragraphStart;
18✔
274
        currentInfo->baseLevel = textParagraph->bidiParagraph->baseLevel;
18✔
275
        currentInfo->userInfo = textParagraph->userInfo;
18✔
276

277
        return SBTrue;
18✔
278
    }
279

280
    return SBFalse;
6✔
281
}
282

283
SBParagraphIteratorRef SBParagraphIteratorRetain(SBParagraphIteratorRef iterator)
1✔
284
{
285
    return ObjectRetain(iterator);
1✔
286
}
287

288
void SBParagraphIteratorRelease(SBParagraphIteratorRef iterator)
12✔
289
{
290
    ObjectRelease(iterator);
12✔
291
}
12✔
292

293
/* ==========================================================================
294
 * Logical Run Iterator Implementation
295
 * ========================================================================== */
296

297
/**
298
 * Initializes a logical run structure.
299
 *
300
 * Sets default values for a logical run's properties including its position, length, and
301
 * bidirectional embedding level.
302
 *
303
 * @param run
304
 *      Pointer to the logical run structure to initialize.
305
 */
306
static void InitializeLogicalRun(SBLogicalRun *run)
23✔
307
{
308
    run->index = SBInvalidIndex;
23✔
309
    run->length = 0;
23✔
310
    run->level = 0;
23✔
311
}
23✔
312

313
/**
314
 * Cleans up resources associated with a logical run iterator, including its parent text iterator.
315
 *
316
 * @param object
317
 *      The logical run iterator to finalize.
318
 */
319
static void FinalizeLogicalRunIterator(ObjectRef object)
13✔
320
{
321
    SBLogicalRunIteratorRef iterator = object;
13✔
322
    FinalizeTextIterator(&iterator->parent);
13✔
323
}
13✔
324

325
SB_INTERNAL SBLogicalRunIteratorRef SBLogicalRunIteratorCreate(SBTextRef text)
13✔
326
{
327
    const SBUInteger size = sizeof(SBLogicalRunIterator);
13✔
328
    void *pointer = NULL;
13✔
329
    SBLogicalRunIteratorRef iterator;
330

331
    /* Text MUST be available. */
332
    SBAssert(text != NULL);
13✔
333

334
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeLogicalRunIterator);
13✔
335

336
    if (iterator) {
13✔
337
        InitializeTextIterator(&iterator->parent, text, SBFalse);
13✔
338
        InitializeLogicalRun(&iterator->currentRun);
13✔
339

340
        iterator->levelIndex = SBInvalidIndex;
13✔
341
    }
342

343
    return iterator;
13✔
344
}
345

346
SBTextRef SBLogicalRunIteratorGetText(SBLogicalRunIteratorRef iterator)
×
347
{
UNCOV
348
    return iterator->parent.text;
×
349
}
350

351
void SBLogicalRunIteratorReset(SBLogicalRunIteratorRef iterator, SBUInteger index, SBUInteger length)
×
352
{
UNCOV
353
    ResetTextIterator(&iterator->parent, index, length);
×
354
    InitializeLogicalRun(&iterator->currentRun);
×
355

UNCOV
356
    iterator->levelIndex = SBInvalidIndex;
×
UNCOV
357
}
×
358

359
const SBLogicalRun *SBLogicalRunIteratorGetCurrent(SBLogicalRunIteratorRef iterator)
11✔
360
{
361
    return &iterator->currentRun;
11✔
362
}
363

364
SBBoolean SBLogicalRunIteratorMoveNext(SBLogicalRunIteratorRef iterator)
31✔
365
{
366
    /* Get parent iterator and current paragraph */
367
    TextIteratorRef parent = &iterator->parent;
31✔
368
    TextParagraphRef textParagraph = parent->currentParagraph;
31✔
369

370
    /* Check if we need to load a new paragraph */
371
    if (iterator->levelIndex == SBInvalidIndex) {
31✔
372
        SBLogicalRun *currentRun = &iterator->currentRun;
21✔
373
        SBUInteger runStart = parent->startIndex;
21✔
374

375
        /* Attempt to load the next paragraph */
376
        if (AdvanceTextIterator(parent)) {
21✔
377
            textParagraph = parent->currentParagraph;
11✔
378
            iterator->levelIndex = 0;
11✔
379
            currentRun->index = runStart;
11✔
380
            currentRun->length = 0;
11✔
381
        } else {
382
            /* No more paragraphs available */
383
            textParagraph = NULL;
10✔
384
            InitializeLogicalRun(currentRun);
10✔
385
        }
386
    }
387

388
    if (textParagraph) {
31✔
389
        SBLogicalRun *currentRun = &iterator->currentRun;
21✔
390
        SBUInteger paragraphLength = parent->paragraphEnd - parent->paragraphStart;
21✔
391
        SBUInteger currentLevelStart = iterator->levelIndex;
21✔
392
        SBParagraphRef bidiParagraph;
393
        const SBLevel *embeddingLevels;
394
        SBLevel currentLevel;
395

396
        /* Get bidirectional information for the paragraph */
397
        bidiParagraph = textParagraph->bidiParagraph;
21✔
398
        embeddingLevels = &bidiParagraph->fixedLevels[parent->paragraphStart - textParagraph->index];
21✔
399
        currentLevel = embeddingLevels[iterator->levelIndex];
21✔
400

401
        /* Find the end of the current level run */
402
        while (++iterator->levelIndex < paragraphLength) {
44✔
403
            if (embeddingLevels[iterator->levelIndex] != currentLevel) {
33✔
404
                break;
10✔
405
            }
406
        }
407

408
        /* Update the run information */
409
        currentRun->index += currentRun->length;
21✔
410
        currentRun->length = iterator->levelIndex - currentLevelStart;
21✔
411
        currentRun->level = currentLevel;
21✔
412

413
        /* Check if the end of the paragraph is reached */
414
        if (iterator->levelIndex == paragraphLength) {
21✔
415
            /* Prepare for the next paragraph */
416
            iterator->levelIndex = SBInvalidIndex;
11✔
417
        }
418

419
        return SBTrue;
21✔
420
    }
421

422
    /* No more runs available */
423
    return SBFalse;
10✔
424
}
425

426
SBLogicalRunIteratorRef SBLogicalRunIteratorRetain(SBLogicalRunIteratorRef iterator)
1✔
427
{
428
    return ObjectRetain(iterator);
1✔
429
}
430

431
void SBLogicalRunIteratorRelease(SBLogicalRunIteratorRef iterator)
14✔
432
{
433
    ObjectRelease(iterator);
14✔
434
}
14✔
435

436
/* ==========================================================================
437
 * Script Run Iterator Implementation
438
 * ========================================================================== */
439

440
/**
441
 * Initializes a script run structure.
442
 *
443
 * Sets default values for a script run's properties including its position, length, and writing
444
 * script identifier.
445
 *
446
 * @param run
447
 *      Pointer to the script run structure to initialize.
448
 */
449
static void InitializeScriptRun(SBScriptRun *run)
29✔
450
{
451
    run->index = SBInvalidIndex;
29✔
452
    run->length = 0;
29✔
453
    run->script = SBScriptNil;
29✔
454
}
29✔
455

456
/**
457
 * Cleans up resources associated with a script run iterator, including its parent text iterator.
458
 *
459
 * @param object The script run iterator to finalize.
460
 */
461
static void FinalizeScriptRunIterator(ObjectRef object)
15✔
462
{
463
    SBScriptRunIteratorRef iterator = object;
15✔
464
    FinalizeTextIterator(&iterator->parent);
15✔
465
}
15✔
466

467
SB_INTERNAL SBScriptRunIteratorRef SBScriptRunIteratorCreate(SBTextRef text)
15✔
468
{
469
    const SBUInteger size = sizeof(SBScriptRunIterator);
15✔
470
    void *pointer = NULL;
15✔
471
    SBScriptRunIteratorRef iterator;
472

473
    /* Text MUST be available. */
474
    SBAssert(text != NULL);
15✔
475

476
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeScriptRunIterator);
15✔
477

478
    if (iterator) {
15✔
479
        InitializeTextIterator(&iterator->parent, text, SBFalse);
15✔
480
        InitializeScriptRun(&iterator->currentRun);
15✔
481

482
        iterator->scriptIndex = SBInvalidIndex;
15✔
483
    }
484

485
    return iterator;
15✔
486
}
487

488
SBTextRef SBScriptRunIteratorGetText(SBScriptRunIteratorRef iterator)
×
489
{
UNCOV
490
    return iterator->parent.text;
×
491
}
492

493
void SBScriptRunIteratorReset(SBScriptRunIteratorRef iterator, SBUInteger index, SBUInteger length)
2✔
494
{
495
    ResetTextIterator(&iterator->parent, index, length);
2✔
496
    InitializeScriptRun(&iterator->currentRun);
2✔
497

498
    iterator->scriptIndex = SBInvalidIndex;
2✔
499
}
2✔
500

501
const SBScriptRun *SBScriptRunIteratorGetCurrent(SBScriptRunIteratorRef iterator)
14✔
502
{
503
    return &iterator->currentRun;
14✔
504
}
505

506
SBBoolean SBScriptRunIteratorMoveNext(SBScriptRunIteratorRef iterator)
37✔
507
{
508
    /* Get parent iterator and current paragraph */
509
    TextIteratorRef parent = &iterator->parent;
37✔
510
    TextParagraphRef textParagraph = parent->currentParagraph;
37✔
511

512
    /* Check if there's a need to load a new paragraph */
513
    if (iterator->scriptIndex == SBInvalidIndex) {
37✔
514
        SBScriptRun *currentRun = &iterator->currentRun;
26✔
515
        SBUInteger runStart = parent->startIndex;
26✔
516

517
        /* Attempt to load the next paragraph */
518
        if (AdvanceTextIterator(parent)) {
26✔
519
            textParagraph = parent->currentParagraph;
14✔
520
            iterator->scriptIndex = 0;
14✔
521
            currentRun->index = runStart;
14✔
522
            currentRun->length = 0;
14✔
523
        } else {
524
            /* No more paragraphs available */
525
            textParagraph = NULL;
12✔
526
            InitializeScriptRun(currentRun);
12✔
527
        }
528
    }
529

530
    if (textParagraph) {
37✔
531
        SBScriptRun *currentRun = &iterator->currentRun;
25✔
532
        SBUInteger paragraphLength = parent->paragraphEnd - parent->paragraphStart;
25✔
533
        SBUInteger scriptStart = iterator->scriptIndex;
25✔
534
        const SBScript *scriptArray;
535
        SBScript currentScript;
536

537
        /* Get script information for the paragraph */
538
        scriptArray = textParagraph->scripts.items;
25✔
539
        currentScript = scriptArray[iterator->scriptIndex];
25✔
540

541
        /* Find the end of the current script run */
542
        while (++iterator->scriptIndex < paragraphLength) {
62✔
543
            if (scriptArray[iterator->scriptIndex] != currentScript) {
48✔
544
                break;
11✔
545
            }
546
        }
547

548
        /* Update the run information */
549
        currentRun->index += currentRun->length;
25✔
550
        currentRun->length = iterator->scriptIndex - scriptStart;
25✔
551
        currentRun->script = currentScript;
25✔
552

553
        /* Check if the end of the paragraph is reached */
554
        if (iterator->scriptIndex == paragraphLength) {
25✔
555
            /* Prepare for the next paragraph */
556
            iterator->scriptIndex = SBInvalidIndex;
14✔
557
        }
558

559
        return SBTrue;
25✔
560
    }
561

562
    /* No more runs available */
563
    return SBFalse;
12✔
564
}
565

566
SBScriptRunIteratorRef SBScriptRunIteratorRetain(SBScriptRunIteratorRef iterator)
1✔
567
{
568
    return ObjectRetain(iterator);
1✔
569
}
570

571
void SBScriptRunIteratorRelease(SBScriptRunIteratorRef iterator)
16✔
572
{
573
    ObjectRelease(iterator);
16✔
574
}
16✔
575

576
/* ==========================================================================
577
 * Attribute Run Iterator Implementation
578
 * ========================================================================== */
579

580
/**
581
 * Initializes an attribute run structure.
582
 *
583
 * Sets default values for an attribute run's properties including its position, length, and
584
 * attribute collection information.
585
 *
586
 * @param run
587
 *      Pointer to the attribute run structure to initialize.
588
 */
589
static void InitializeAttributeRun(SBAttributeRun *run)
101✔
590
{
591
    run->index = SBInvalidIndex;
101✔
592
    run->length = 0;
101✔
593
    run->attributes = NULL;
101✔
594
}
101✔
595

596
/**
597
 * Cleans up resources associated with an attribute run iterator, including the text reference and
598
 * attribute item list.
599
 *
600
 * @param object
601
 *      The attribute run iterator to finalize.
602
 */
603
static void FinalizeAttributeRunIterator(ObjectRef object)
39✔
604
{
605
    SBAttributeRunIteratorRef iterator = object;
39✔
606

607
    SBTextRelease(iterator->text);
39✔
608
    AttributeDictionaryFinalize(&iterator->items, NULL);
39✔
609
}
39✔
610

611
/**
612
 * Advances the iterator to find the next run of text that contains attributes matching the
613
 * specified ID filter.
614
 *
615
 * @param iterator
616
 *      The attribute run iterator.
617
 * @return
618
 *      `SBTrue` if a matching run was found, `SBFalse` if the end was reached.
619
 */
620
static SBBoolean LoadOnwardAttributeRunByFilteringID(SBAttributeRunIteratorRef iterator)
3✔
621
{
622
    SBTextRef text = iterator->text;
3✔
623
    AttributeManagerRef manager = (AttributeManagerRef)&text->attributeManager;
3✔
624
    SBAttributeRun *currentRun = &iterator->currentRun;
3✔
625
    SBUInteger index;
3✔
626
    SBBoolean result;
627

628
    index = iterator->currentIndex;
3✔
629
    result = AttributeManagerGetOnwardRunByFilteringID(manager, &index, iterator->endIndex,
3✔
630
        iterator->filterAttributeID, &iterator->items);
3✔
631

632
    /* Populate the current run */
633
    currentRun->index = iterator->currentIndex;
3✔
634
    currentRun->length = index - iterator->currentIndex;
3✔
635
    currentRun->attributes = &iterator->items._list;
3✔
636

637
    iterator->currentIndex = index;
3✔
638

639
    return result;
3✔
640
}
641

642
/**
643
 * Advances the iterator to find the next run of text that contains attributes matching the
644
 * specified scope and group filters.
645
 *
646
 * @param iterator
647
 *      The attribute run iterator.
648
 * @return
649
 *      `SBTrue` if a matching run was found, `SBFalse` if the end was reached.
650
 */
651
static SBBoolean LoadOnwardAttributeRunByFilteringCollection(SBAttributeRunIteratorRef iterator)
58✔
652
{
653
    SBTextRef text = iterator->text;
58✔
654
    AttributeManagerRef manager = (AttributeManagerRef)&text->attributeManager;
58✔
655
    SBAttributeRun *currentRun = &iterator->currentRun;
58✔
656
    SBUInteger index;
58✔
657
    SBBoolean result;
658

659
    index = iterator->currentIndex;
58✔
660
    result = AttributeManagerGetOnwardRunByFilteringCollection(manager, &index, iterator->endIndex,
58✔
661
        iterator->filterScope, iterator->filterGroup, &iterator->items);
58✔
662

663
    /* Populate the current run */
664
    currentRun->index = iterator->currentIndex;
58✔
665
    currentRun->length = index - iterator->currentIndex;
58✔
666
    currentRun->attributes = &iterator->items._list;
58✔
667

668
    iterator->currentIndex = index;
58✔
669

670
    return result;
58✔
671
}
672

673
SB_INTERNAL SBAttributeRunIteratorRef SBAttributeRunIteratorCreate(SBTextRef text)
39✔
674
{
675
    const SBUInteger size = sizeof(SBAttributeRunIterator);
39✔
676
    void *pointer = NULL;
39✔
677
    SBAttributeRunIteratorRef iterator;
678

679
    /* Text MUST be available. */
680
    SBAssert(text != NULL);
39✔
681

682
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeAttributeRunIterator);
39✔
683

684
    if (iterator) {
39✔
685
        iterator->text = SBTextRetain(text);
39✔
686
        iterator->startIndex = 0;
39✔
687
        iterator->endIndex = text->buffer.codeUnits.count;
39✔
688
        iterator->currentIndex = SBInvalidIndex;
39✔
689
        iterator->filterAttributeID = SBAttributeIDNone;
39✔
690
        iterator->filterGroup = SBAttributeGroupNone;
39✔
691
        iterator->filterScope = SBAttributeScopeCharacter;
39✔
692

693
        AttributeDictionaryInitialize(&iterator->items, text->attributeRegistry->valueSize);
39✔
694
        InitializeAttributeRun(&iterator->currentRun);
39✔
695
    }
696

697
    return iterator;
39✔
698
}
699

700
SBTextRef SBAttributeRunIteratorGetText(SBAttributeRunIteratorRef iterator)
×
701
{
UNCOV
702
    return iterator->text;
×
703
}
704

705
void SBAttributeRunIteratorSetupAttributeID(SBAttributeRunIteratorRef iterator, SBAttributeID attributeID)
3✔
706
{
707
    iterator->filterAttributeID = attributeID;
3✔
708
    iterator->filterGroup = SBAttributeGroupNone;
3✔
709

710
    /* Reset the iterator */
711
    iterator->currentIndex = SBInvalidIndex;
3✔
712
    InitializeAttributeRun(&iterator->currentRun);
3✔
713
}
3✔
714

715
void SBAttributeRunIteratorSetupAttributeCollection(SBAttributeRunIteratorRef iterator,
25✔
716
    SBAttributeGroup group, SBAttributeScope scope)
717
{
718
    iterator->filterAttributeID = SBAttributeIDNone;
25✔
719
    iterator->filterGroup = group;
25✔
720
    iterator->filterScope = scope;
25✔
721

722
    /* Reset the iterator */
723
    iterator->currentIndex = SBInvalidIndex;
25✔
724
    InitializeAttributeRun(&iterator->currentRun);
25✔
725
}
25✔
726

UNCOV
727
void SBAttributeRunIteratorReset(SBAttributeRunIteratorRef iterator,
×
728
    SBUInteger index, SBUInteger length)
729
{
730
    iterator->startIndex = index;
×
731
    iterator->endIndex = index + length;
×
732
    iterator->currentIndex = SBInvalidIndex;
×
UNCOV
733
    InitializeAttributeRun(&iterator->currentRun);
×
UNCOV
734
}
×
735

736
const SBAttributeRun *SBAttributeRunIteratorGetCurrent(SBAttributeRunIteratorRef iterator)
37✔
737
{
738
    return &iterator->currentRun;
37✔
739
}
740

741
SBBoolean SBAttributeRunIteratorMoveNext(SBAttributeRunIteratorRef iterator)
76✔
742
{
743
    SBBoolean hasRun = SBFalse;
76✔
744

745
    if (iterator->currentIndex == SBInvalidIndex) {
76✔
746
        iterator->currentIndex = iterator->startIndex;
36✔
747
    }
748

749
    while (iterator->currentIndex < iterator->endIndex) {
95✔
750
        if (iterator->filterAttributeID != SBAttributeIDNone) {
61✔
751
            hasRun = LoadOnwardAttributeRunByFilteringID(iterator);
3✔
752
        } else {
753
            hasRun = LoadOnwardAttributeRunByFilteringCollection(iterator);
58✔
754
        }
755

756
        /* Skip the empty run */
757
        if (hasRun && SBAttributeListSize(iterator->currentRun.attributes) == 0) {
61✔
758
            hasRun = SBFalse;
19✔
759
            continue;
19✔
760
        }
761

762
        break;
42✔
763
    }
764

765
    if (!hasRun) {
76✔
766
        InitializeAttributeRun(&iterator->currentRun);
34✔
767
    }
768

769
    return hasRun;
76✔
770
}
771

772
SBAttributeRunIteratorRef SBAttributeRunIteratorRetain(SBAttributeRunIteratorRef iterator)
1✔
773
{
774
    return ObjectRetain(iterator);
1✔
775
}
776

777
void SBAttributeRunIteratorRelease(SBAttributeRunIteratorRef iterator)
40✔
778
{
779
    ObjectRelease(iterator);
40✔
780
}
40✔
781

782
/* ==========================================================================
783
 * Visual Run Iterator Implementation
784
 * ========================================================================== */
785

786
/**
787
 * Initializes a visual run structure.
788
 *
789
 * Sets default values for a visual run's properties including its position, length, and
790
 * bidirectional embedding level in visual order.
791
 *
792
 * @param run
793
 *      Pointer to the visual run structure to initialize.
794
 */
795
static void InitializeVisualRun(SBVisualRun *run)
40✔
796
{
797
    run->index = SBInvalidIndex;
40✔
798
    run->length = 0;
40✔
799
    run->level = 0;
40✔
800
}
40✔
801

802
/**
803
 * Cleans up resources associated with a visual run iterator, including the bidirectional line
804
 * object and parent text iterator.
805
 *
806
 * @param object
807
 *      The visual run iterator to finalize.
808
 */
809
static void FinalizeVisualRunIterator(ObjectRef object)
11✔
810
{
811
    SBVisualRunIteratorRef iterator = object;
11✔
812

813
    if (iterator->bidiLine) {
11✔
UNCOV
814
        SBLineRelease(iterator->bidiLine);
×
815
    }
816

817
    FinalizeTextIterator(&iterator->parent);
11✔
818
}
11✔
819

820
SB_INTERNAL SBVisualRunIteratorRef SBVisualRunIteratorCreate(SBTextRef text)
11✔
821
{
822
    const SBUInteger size = sizeof(SBText);
11✔
823
    void *pointer = NULL;
11✔
824
    SBVisualRunIteratorRef iterator;
825

826
    /* Text MUST be available. */
827
    SBAssert(text != NULL);
11✔
828

829
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeVisualRunIterator);
11✔
830

831
    if (iterator) {
11✔
832
        InitializeTextIterator(&iterator->parent, text, SBTrue);
11✔
833
        InitializeVisualRun(&iterator->currentRun);
11✔
834

835
        iterator->bidiLine = NULL;
11✔
836
        iterator->runIndex = SBInvalidIndex;
11✔
837
    }
838

839
    return iterator;
11✔
840
}
841

842
SBTextRef SBVisualRunIteratorGetText(SBVisualRunIteratorRef iterator)
×
843
{
UNCOV
844
    return iterator->parent.text;
×
845
}
846

847
void SBVisualRunIteratorReset(SBVisualRunIteratorRef iterator, SBUInteger index, SBUInteger length)
3✔
848
{
849
    ResetTextIterator(&iterator->parent, index, length);
3✔
850
    InitializeVisualRun(&iterator->currentRun);
3✔
851

852
    iterator->bidiLine = NULL;
3✔
853
    iterator->runIndex = SBInvalidIndex;
3✔
854
}
3✔
855

856
const SBVisualRun *SBVisualRunIteratorGetCurrent(SBVisualRunIteratorRef iterator)
12✔
857
{
858
    return &iterator->currentRun;
12✔
859
}
860

861
SBBoolean SBVisualRunIteratorMoveNext(SBVisualRunIteratorRef iterator)
35✔
862
{
863
    SBLineRef bidiLine = iterator->bidiLine;
35✔
864

865
    if (!bidiLine) {
35✔
866
        TextIteratorRef parentIterator = &iterator->parent;
26✔
867

868
        /* Try to advance to the next paragraph */
869
        if (AdvanceTextIterator(parentIterator)) {
26✔
870
            /* Get paragraph information */
871
            TextParagraphRef textParagraph = parentIterator->currentParagraph;
15✔
872
            SBParagraphRef bidiParagraph = textParagraph->bidiParagraph;
15✔
873
            SBUInteger paragraphStart;
874
            SBUInteger paragraphLength;
875

876
            /* Calculate paragraph boundaries */
877
            paragraphStart = parentIterator->paragraphStart;
15✔
878
            paragraphLength = parentIterator->paragraphEnd - paragraphStart;
15✔
879

880
            /* Create a new bidirectional line from the paragraph, sourcing bidi types fresh from
881
               the text's current buffer rather than trusting the paragraph's own cached offset */
882
            bidiLine = SBLineCreate(
15✔
883
                BidiTypesBufferGetPtr((BidiTypesBufferRef)&parentIterator->text->bidiTypes, paragraphStart),
15✔
884
                &bidiParagraph->fixedLevels[paragraphStart - textParagraph->index],
15✔
885
                bidiParagraph->baseLevel, bidiParagraph->stringEncoding,
15✔
886
                paragraphStart, paragraphLength);
887

888
            /* Initialize line processing */
889
            iterator->bidiLine = bidiLine;
15✔
890
            iterator->runIndex = 0;
15✔
891
        }
892

893
        /* Reset the current run state */
894
        InitializeVisualRun(&iterator->currentRun);
26✔
895
    }
896

897
    if (bidiLine) {
35✔
898
        SBVisualRun *currentRun = &iterator->currentRun;
24✔
899
        SBRun *bidiRun;
900

901
        /* Get the current bidirectional run */
902
        bidiRun = &bidiLine->fixedRuns[iterator->runIndex];
24✔
903

904
        /* Update the current run with bidirectional properties */
905
        currentRun->index = bidiRun->offset;
24✔
906
        currentRun->length = bidiRun->length;
24✔
907
        currentRun->level = bidiRun->level;
24✔
908

909
        /* Move to the next run in the line */
910
        iterator->runIndex += 1;
24✔
911

912
        /* Check if all runs in the line are processed */
913
        if (iterator->runIndex == bidiLine->runCount) {
24✔
914
            /* Clean up and prepare for the next line */
915
            SBLineRelease(bidiLine);
15✔
916
            iterator->bidiLine = NULL;
15✔
917
        }
918

919
        return SBTrue;
24✔
920
    }
921

922
    /* No more runs available */
923
    return SBFalse;
11✔
924
}
925

926
SBVisualRunIteratorRef SBVisualRunIteratorRetain(SBVisualRunIteratorRef iterator)
1✔
927
{
928
    return ObjectRetain(iterator);
1✔
929
}
930

931
void SBVisualRunIteratorRelease(SBVisualRunIteratorRef iterator)
12✔
932
{
933
    ObjectRelease(iterator);
12✔
934
}
12✔
935

936
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc