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

Tehreer / SheenBidi / 28885974711

07 Jul 2026 05:31PM UTC coverage: 96.293% (-0.1%) from 96.404%
28885974711

push

github

mta452
[lib] Fix memory leaks

17 of 17 new or added lines in 4 files covered. (100.0%)

34 existing lines in 5 files now uncovered.

4000 of 4154 relevant lines covered (96.29%)

450834.3 hits per line

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

94.76
/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,
48✔
67
    SBBoolean visualDirectionMode)
68
{
69
    iterator->text = SBTextRetain(text);
48✔
70
    iterator->visualDirectionMode = visualDirectionMode;
48✔
71

72
    ResetTextIterator(iterator, 0, text->buffer.codeUnits.count);
48✔
73
}
48✔
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)
48✔
82
{
83
    SBTextRef text = iterator->text;
48✔
84

85
    if (text) {
48✔
86
        SBTextRelease(text);
48✔
87
    }
88
}
48✔
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)
58✔
105
{
106
    SBTextRef text = iterator->text;
58✔
107
    SBUInteger startIndex;
108
    SBUInteger endIndex;
109
    SBBoolean forwardMode;
110
    SBUInteger paragraphIndex;
111

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

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

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

124
        if (iterator->visualDirectionMode) {
52✔
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;
58✔
142
    iterator->startIndex = startIndex;
58✔
143
    iterator->endIndex = endIndex;
58✔
144
    iterator->paragraphIndex = paragraphIndex;
58✔
145

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

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

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

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

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

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

188
        return SBTrue;
52✔
189
    }
190

191
    /* No more text to process */
192
    return SBFalse;
37✔
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)
14✔
209
{
210
    info->index = SBInvalidIndex;
14✔
211
    info->length = 0;
14✔
212
    info->baseLevel = 0;
14✔
213
}
14✔
214

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

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

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

236
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeParagraphIterator);
9✔
237

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

243
    return iterator;
9✔
244
}
245

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

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

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

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

266
    if (AdvanceTextIterator(parent)) {
16✔
267
        TextParagraphRef textParagraph = parent->currentParagraph;
12✔
268
        SBParagraphInfo *currentInfo;
269

270
        currentInfo = &iterator->currentParagraph;
12✔
271
        currentInfo->index = parent->paragraphStart;
12✔
272
        currentInfo->length = parent->paragraphEnd - parent->paragraphStart;
12✔
273
        currentInfo->baseLevel = textParagraph->bidiParagraph->baseLevel;
12✔
274

275
        return SBTrue;
12✔
276
    }
277

278
    return SBFalse;
4✔
279
}
280

281
SBParagraphIteratorRef SBParagraphIteratorRetain(SBParagraphIteratorRef iterator)
1✔
282
{
283
    return ObjectRetain(iterator);
1✔
284
}
285

286
void SBParagraphIteratorRelease(SBParagraphIteratorRef iterator)
10✔
287
{
288
    ObjectRelease(iterator);
10✔
289
}
10✔
290

291
/* ==========================================================================
292
 * Logical Run Iterator Implementation
293
 * ========================================================================== */
294

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

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

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

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

332
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeLogicalRunIterator);
13✔
333

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

338
        iterator->levelIndex = SBInvalidIndex;
13✔
339
    }
340

341
    return iterator;
13✔
342
}
343

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

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

354
    iterator->levelIndex = SBInvalidIndex;
×
UNCOV
355
}
×
356

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

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

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

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

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

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

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

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

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

417
        return SBTrue;
21✔
418
    }
419

420
    /* No more runs available */
421
    return SBFalse;
10✔
422
}
423

424
SBLogicalRunIteratorRef SBLogicalRunIteratorRetain(SBLogicalRunIteratorRef iterator)
1✔
425
{
426
    return ObjectRetain(iterator);
1✔
427
}
428

429
void SBLogicalRunIteratorRelease(SBLogicalRunIteratorRef iterator)
14✔
430
{
431
    ObjectRelease(iterator);
14✔
432
}
14✔
433

434
/* ==========================================================================
435
 * Script Run Iterator Implementation
436
 * ========================================================================== */
437

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

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

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

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

474
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeScriptRunIterator);
15✔
475

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

480
        iterator->scriptIndex = SBInvalidIndex;
15✔
481
    }
482

483
    return iterator;
15✔
484
}
485

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

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

496
    iterator->scriptIndex = SBInvalidIndex;
2✔
497
}
2✔
498

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

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

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

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

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

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

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

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

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

557
        return SBTrue;
25✔
558
    }
559

560
    /* No more runs available */
561
    return SBFalse;
12✔
562
}
563

564
SBScriptRunIteratorRef SBScriptRunIteratorRetain(SBScriptRunIteratorRef iterator)
1✔
565
{
566
    return ObjectRetain(iterator);
1✔
567
}
568

569
void SBScriptRunIteratorRelease(SBScriptRunIteratorRef iterator)
16✔
570
{
571
    ObjectRelease(iterator);
16✔
572
}
16✔
573

574
/* ==========================================================================
575
 * Attribute Run Iterator Implementation
576
 * ========================================================================== */
577

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

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

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

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

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

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

635
    iterator->currentIndex = index;
3✔
636

637
    return result;
3✔
638
}
639

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

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

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

666
    iterator->currentIndex = index;
58✔
667

668
    return result;
58✔
669
}
670

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

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

680
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeAttributeRunIterator);
39✔
681

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

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

695
    return iterator;
39✔
696
}
697

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

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

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

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

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

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

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

739
SBBoolean SBAttributeRunIteratorMoveNext(SBAttributeRunIteratorRef iterator)
76✔
740
{
741
    SBBoolean hasRun = SBFalse;
76✔
742

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

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

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

760
        break;
42✔
761
    }
762

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

767
    return hasRun;
76✔
768
}
769

770
SBAttributeRunIteratorRef SBAttributeRunIteratorRetain(SBAttributeRunIteratorRef iterator)
1✔
771
{
772
    return ObjectRetain(iterator);
1✔
773
}
774

775
void SBAttributeRunIteratorRelease(SBAttributeRunIteratorRef iterator)
40✔
776
{
777
    ObjectRelease(iterator);
40✔
778
}
40✔
779

780
/* ==========================================================================
781
 * Visual Run Iterator Implementation
782
 * ========================================================================== */
783

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

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

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

815
    FinalizeTextIterator(&iterator->parent);
11✔
816
}
11✔
817

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

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

827
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeVisualRunIterator);
11✔
828

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

833
        iterator->bidiLine = NULL;
11✔
834
        iterator->runIndex = SBInvalidIndex;
11✔
835
    }
836

837
    return iterator;
11✔
838
}
839

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

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

850
    iterator->bidiLine = NULL;
3✔
851
    iterator->runIndex = SBInvalidIndex;
3✔
852
}
3✔
853

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

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

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

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

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

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

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

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

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

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

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

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

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

917
        return SBTrue;
24✔
918
    }
919

920
    /* No more runs available */
921
    return SBFalse;
11✔
922
}
923

924
SBVisualRunIteratorRef SBVisualRunIteratorRetain(SBVisualRunIteratorRef iterator)
1✔
925
{
926
    return ObjectRetain(iterator);
1✔
927
}
928

929
void SBVisualRunIteratorRelease(SBVisualRunIteratorRef iterator)
12✔
930
{
931
    ObjectRelease(iterator);
12✔
932
}
12✔
933

934
#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