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

Tehreer / SheenBidi / 30704294997

01 Aug 2026 02:41PM UTC coverage: 96.332% (+0.2%) from 96.139%
30704294997

push

github

mta452
[test] Update attribute manager testing mechanism

4123 of 4280 relevant lines covered (96.33%)

437619.64 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

188
        return SBTrue;
74 ✔
189
    }
190

191
    /* No more text to process */
192
    return SBFalse;
54 ✔
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
{
348
    return iterator->parent.text;
×
349
}
350

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

356
    iterator->levelIndex = SBInvalidIndex;
×
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)
32 ✔
450
{
451
    run->index = SBInvalidIndex;
32 ✔
452
    run->length = 0;
32 ✔
453
    run->script = SBScriptNil;
32 ✔
454
}
32 ✔
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)
16 ✔
462
{
463
    SBScriptRunIteratorRef iterator = object;
16 ✔
464
    FinalizeTextIterator(&iterator->parent);
16 ✔
465
}
16 ✔
466

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

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

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

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

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

485
    return iterator;
16 ✔
486
}
487

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

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

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

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

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

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

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

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

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

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

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

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

559
        return SBTrue;
27 ✔
560
    }
561

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

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

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

576
/* ==========================================================================
577
 * Attribute Filter Implementation
578
 * ========================================================================== */
579

580
SBAttributeFilter SBAttributeFilterMakeID(SBAttributeID attributeID)
3 ✔
581
{
582
    SBAttributeFilter filter;
583
    filter.kind = SBAttributeFilterKindID;
3 ✔
584
    filter.value.attributeID = attributeID;
3 ✔
585
    return filter;
3 ✔
586
}
587

588
SBAttributeFilter SBAttributeFilterMakeCollection(SBAttributeGroup attributeGroup,
26 ✔
589
    SBAttributeScope attributeScope)
590
{
591
    SBAttributeFilter filter;
592
    filter.kind = SBAttributeFilterKindCollection;
26 ✔
593
    filter.value.collection.group = attributeGroup;
26 ✔
594
    filter.value.collection.scope = attributeScope;
26 ✔
595
    return filter;
26 ✔
596
}
597

598
SBAttributeFilter SBAttributeFilterMakeAny(void)
19 ✔
599
{
600
    SBAttributeFilter filter;
601
    filter.kind = SBAttributeFilterKindAny;
19 ✔
602
    return filter;
19 ✔
603
}
604

605
/**
606
 * Resolves an `SBAttributeFilter` into the group/scope parameters expected by the group/scope
607
 * based attribute-filtering routines, treating `SBAttributeFilterKindAny` (and any other
608
 * non-collection kind) as "no group restriction, any scope."
609
 *
610
 * @param filter
611
 *      The filter to resolve.
612
 * @param filterGroup
613
 *      Receives the attribute group to filter by.
614
 * @param filterScope
615
 *      Receives the attribute scope to filter by.
616
 */
617
SB_INTERNAL void GetCollectionFilterParams(SBAttributeFilter filter, SBAttributeGroup *filterGroup,
62 ✔
618
    SBAttributeScope *filterScope)
619
{
620
    if (filter.kind == SBAttributeFilterKindCollection) {
62 ✔
621
        *filterGroup = filter.value.collection.group;
45 ✔
622
        *filterScope = filter.value.collection.scope;
45 ✔
623
    } else {
624
        *filterGroup = SBAttributeGroupNone;
17 ✔
625
        *filterScope = AttributeScopeAny;
17 ✔
626
    }
627
}
62 ✔
628

629
/* ==========================================================================
630
 * Uniform Run Iterator Implementation
631
 * ========================================================================== */
632

633
/**
634
 * Initializes a uniform run structure.
635
 *
636
 * Sets default values for a uniform run's properties including its position, length, level, script,
637
 * and attribute list.
638
 *
639
 * @param run
640
 *      Pointer to the uniform run structure to initialize.
641
 */
642
static void InitializeUniformRun(SBUniformRun *run)
35 ✔
643
{
644
    run->index = SBInvalidIndex;
35 ✔
645
    run->length = 0;
35 ✔
646
    run->level = 0;
35 ✔
647
    run->script = SBScriptNil;
35 ✔
648
    run->attributes = NULL;
35 ✔
649
}
35 ✔
650

651
/**
652
 * Cleans up resources associated with a uniform run iterator, including its attribute item list and
653
 * parent text iterator.
654
 *
655
 * @param object
656
 *      The uniform run iterator to finalize.
657
 */
658
static void FinalizeUniformRunIterator(ObjectRef object)
16 ✔
659
{
660
    SBUniformRunIteratorRef iterator = object;
16 ✔
661

662
    AttributeDictionaryFinalize(&iterator->items);
16 ✔
663
    FinalizeTextIterator(&iterator->parent);
16 ✔
664
}
16 ✔
665

666
SB_INTERNAL SBUniformRunIteratorRef SBUniformRunIteratorCreate(SBTextRef text)
16 ✔
667
{
668
    const SBUInteger size = sizeof(SBUniformRunIterator);
16 ✔
669
    void *pointer = NULL;
16 ✔
670
    SBUniformRunIteratorRef iterator;
671

672
    /* Text MUST be available. */
673
    SBAssert(text != NULL);
16 ✔
674

675
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeUniformRunIterator);
16 ✔
676

677
    if (iterator) {
16 ✔
678
        InitializeTextIterator(&iterator->parent, text, SBFalse);
16 ✔
679
        InitializeUniformRun(&iterator->currentRun);
16 ✔
680

681
        iterator->rangeIndex = 0;
16 ✔
682
        iterator->rangeLength = text->buffer.codeUnits.count;
16 ✔
683
        iterator->boundaryIndex = SBInvalidIndex;
16 ✔
684
        iterator->filter = SBAttributeFilterMakeAny();
16 ✔
685

686
        AttributeDictionaryInitialize(&iterator->items, text->attributeRegistry);
16 ✔
687
    }
688

689
    return iterator;
16 ✔
690
}
691

692
SBTextRef SBUniformRunIteratorGetText(SBUniformRunIteratorRef iterator)
×
693
{
694
    return iterator->parent.text;
×
695
}
696

697
void SBUniformRunIteratorSetupFilter(SBUniformRunIteratorRef iterator, SBAttributeFilter filter)
4 ✔
698
{
699
    iterator->filter = filter;
4 ✔
700

701
    SBUniformRunIteratorReset(iterator, iterator->rangeIndex, iterator->rangeLength);
4 ✔
702
}
4 ✔
703

704
void SBUniformRunIteratorReset(SBUniformRunIteratorRef iterator, SBUInteger index, SBUInteger length)
5 ✔
705
{
706
    iterator->rangeIndex = index;
5 ✔
707
    iterator->rangeLength = length;
5 ✔
708

709
    ResetTextIterator(&iterator->parent, index, length);
5 ✔
710
    InitializeUniformRun(&iterator->currentRun);
5 ✔
711

712
    iterator->boundaryIndex = SBInvalidIndex;
5 ✔
713
}
5 ✔
714

715
const SBUniformRun *SBUniformRunIteratorGetCurrent(SBUniformRunIteratorRef iterator)
14 ✔
716
{
717
    return &iterator->currentRun;
14 ✔
718
}
719

720
SBBoolean SBUniformRunIteratorMoveNext(SBUniformRunIteratorRef iterator)
40 ✔
721
{
722
    /* Get parent iterator and current paragraph */
723
    TextIteratorRef parent = &iterator->parent;
40 ✔
724
    TextParagraphRef textParagraph = parent->currentParagraph;
40 ✔
725

726
    /* Check if we need to load a new paragraph */
727
    if (iterator->boundaryIndex == SBInvalidIndex) {
40 ✔
728
        SBUniformRun *currentRun = &iterator->currentRun;
29 ✔
729
        SBUInteger runStart = parent->startIndex;
29 ✔
730

731
        /* Attempt to load the next paragraph */
732
        if (AdvanceTextIterator(parent)) {
29 ✔
733
            textParagraph = parent->currentParagraph;
15 ✔
734
            iterator->boundaryIndex = 0;
15 ✔
735
            currentRun->index = runStart;
15 ✔
736
            currentRun->length = 0;
15 ✔
737
        } else {
738
            /* No more paragraphs available */
739
            textParagraph = NULL;
14 ✔
740
            InitializeUniformRun(currentRun);
14 ✔
741
        }
742
    }
743

744
    if (textParagraph) {
40 ✔
745
        SBTextRef text = parent->text;
26 ✔
746
        AttributeManagerRef manager = (AttributeManagerRef)&text->attributeManager;
26 ✔
747
        SBUniformRun *currentRun = &iterator->currentRun;
26 ✔
748
        SBUInteger paragraphOffset = parent->paragraphStart - textParagraph->index;
26 ✔
749
        SBUInteger paragraphLength = parent->paragraphEnd - parent->paragraphStart;
26 ✔
750
        const SBLevel *embeddingLevels;
751
        const SBScript *scriptArray;
752
        SBUInteger runStart = iterator->boundaryIndex;
26 ✔
753
        SBLevel currentLevel;
754
        SBScript currentScript;
755
        SBUInteger levelEnd = runStart;
26 ✔
756
        SBUInteger scriptEnd = runStart;
26 ✔
757
        SBUInteger attributeEnd = parent->paragraphStart + runStart;
26 ✔
758
        SBUInteger mergedEnd;
759
        SBAttributeGroup filterGroup;
26 ✔
760
        SBAttributeScope filterScope;
26 ✔
761

762
        /* Get bidirectional and script information for the paragraph */
763
        embeddingLevels = &textParagraph->bidiParagraph->fixedLevels[paragraphOffset];
26 ✔
764
        scriptArray = &textParagraph->scripts.items[paragraphOffset];
26 ✔
765
        currentLevel = embeddingLevels[runStart];
26 ✔
766
        currentScript = scriptArray[runStart];
26 ✔
767

768
        /* Find the end of the current level run */
769
        while (++levelEnd < paragraphLength && embeddingLevels[levelEnd] == currentLevel) {
84 ✔
770
        }
771

772
        /* Find the end of the current script run */
773
        while (++scriptEnd < paragraphLength && scriptArray[scriptEnd] == currentScript) {
79 ✔
774
        }
775

776
        /* Find the end of the current attribute run, clamped to the paragraph boundary */
777
        if (iterator->filter.kind == SBAttributeFilterKindID) {
26 ✔
778
            AttributeManagerGetOnwardRunByFilteringID(manager, &attributeEnd, parent->paragraphEnd,
12 ✔
779
                iterator->filter.value.attributeID, &iterator->items);
12 ✔
780
        } else {
781
            GetCollectionFilterParams(iterator->filter, &filterGroup, &filterScope);
14 ✔
782
            AttributeManagerGetOnwardRunByFilteringCollection(manager, &attributeEnd, parent->paragraphEnd,
14 ✔
783
                filterScope, filterGroup, &iterator->items);
14 ✔
784
        }
785

786
        /* Take the nearest of the level, script, and attribute boundaries */
787
        mergedEnd = levelEnd;
26 ✔
788
        if (scriptEnd < mergedEnd) {
26 ✔
789
            mergedEnd = scriptEnd;
2 ✔
790
        }
791
        if (attributeEnd - parent->paragraphStart < mergedEnd) {
26 ✔
792
            mergedEnd = attributeEnd - parent->paragraphStart;
3 ✔
793
        }
794

795
        /* Update the run information */
796
        currentRun->index += currentRun->length;
26 ✔
797
        currentRun->length = mergedEnd - runStart;
26 ✔
798
        currentRun->level = currentLevel;
26 ✔
799
        currentRun->script = currentScript;
26 ✔
800
        currentRun->attributes = iterator->items._list;
26 ✔
801

802
        iterator->boundaryIndex = mergedEnd;
26 ✔
803

804
        /* Check if the end of the paragraph is reached */
805
        if (mergedEnd == paragraphLength) {
26 ✔
806
            /* Prepare for the next paragraph */
807
            iterator->boundaryIndex = SBInvalidIndex;
15 ✔
808
        }
809

810
        return SBTrue;
26 ✔
811
    }
812

813
    /* No more runs available */
814
    return SBFalse;
14 ✔
815
}
816

817
SBUniformRunIteratorRef SBUniformRunIteratorRetain(SBUniformRunIteratorRef iterator)
1 ✔
818
{
819
    return ObjectRetain(iterator);
1 ✔
820
}
821

822
void SBUniformRunIteratorRelease(SBUniformRunIteratorRef iterator)
17 ✔
823
{
824
    ObjectRelease(iterator);
17 ✔
825
}
17 ✔
826

827
/* ==========================================================================
828
 * Visual Run Iterator Implementation
829
 * ========================================================================== */
830

831
/**
832
 * Initializes a visual run structure.
833
 *
834
 * Sets default values for a visual run's properties including its position, length, and
835
 * bidirectional embedding level in visual order.
836
 *
837
 * @param run
838
 *      Pointer to the visual run structure to initialize.
839
 */
840
static void InitializeVisualRun(SBVisualRun *run)
40 ✔
841
{
842
    run->index = SBInvalidIndex;
40 ✔
843
    run->length = 0;
40 ✔
844
    run->level = 0;
40 ✔
845
}
40 ✔
846

847
/**
848
 * Cleans up resources associated with a visual run iterator, including the bidirectional line
849
 * object and parent text iterator.
850
 *
851
 * @param object
852
 *      The visual run iterator to finalize.
853
 */
854
static void FinalizeVisualRunIterator(ObjectRef object)
11 ✔
855
{
856
    SBVisualRunIteratorRef iterator = object;
11 ✔
857

858
    if (iterator->bidiLine) {
11 ✔
859
        SBLineRelease(iterator->bidiLine);
×
860
    }
861

862
    FinalizeTextIterator(&iterator->parent);
11 ✔
863
}
11 ✔
864

865
SB_INTERNAL SBVisualRunIteratorRef SBVisualRunIteratorCreate(SBTextRef text)
11 ✔
866
{
867
    const SBUInteger size = sizeof(SBVisualRunIterator);
11 ✔
868
    void *pointer = NULL;
11 ✔
869
    SBVisualRunIteratorRef iterator;
870

871
    /* Text MUST be available. */
872
    SBAssert(text != NULL);
11 ✔
873

874
    iterator = ObjectCreate(&size, 1, &pointer, FinalizeVisualRunIterator);
11 ✔
875

876
    if (iterator) {
11 ✔
877
        InitializeTextIterator(&iterator->parent, text, SBTrue);
11 ✔
878
        InitializeVisualRun(&iterator->currentRun);
11 ✔
879

880
        iterator->bidiLine = NULL;
11 ✔
881
        iterator->runIndex = SBInvalidIndex;
11 ✔
882
    }
883

884
    return iterator;
11 ✔
885
}
886

887
SBTextRef SBVisualRunIteratorGetText(SBVisualRunIteratorRef iterator)
×
888
{
889
    return iterator->parent.text;
×
890
}
891

892
void SBVisualRunIteratorReset(SBVisualRunIteratorRef iterator, SBUInteger index, SBUInteger length)
3 ✔
893
{
894
    ResetTextIterator(&iterator->parent, index, length);
3 ✔
895
    InitializeVisualRun(&iterator->currentRun);
3 ✔
896

897
    iterator->bidiLine = NULL;
3 ✔
898
    iterator->runIndex = SBInvalidIndex;
3 ✔
899
}
3 ✔
900

901
const SBVisualRun *SBVisualRunIteratorGetCurrent(SBVisualRunIteratorRef iterator)
12 ✔
902
{
903
    return &iterator->currentRun;
12 ✔
904
}
905

906
SBBoolean SBVisualRunIteratorMoveNext(SBVisualRunIteratorRef iterator)
35 ✔
907
{
908
    SBLineRef bidiLine = iterator->bidiLine;
35 ✔
909

910
    if (!bidiLine) {
35 ✔
911
        TextIteratorRef parentIterator = &iterator->parent;
26 ✔
912

913
        /* Try to advance to the next paragraph */
914
        if (AdvanceTextIterator(parentIterator)) {
26 ✔
915
            /* Get paragraph information */
916
            TextParagraphRef textParagraph = parentIterator->currentParagraph;
15 ✔
917
            SBParagraphRef bidiParagraph = textParagraph->bidiParagraph;
15 ✔
918
            SBUInteger paragraphStart;
919
            SBUInteger paragraphLength;
920

921
            /* Calculate paragraph boundaries */
922
            paragraphStart = parentIterator->paragraphStart;
15 ✔
923
            paragraphLength = parentIterator->paragraphEnd - paragraphStart;
15 ✔
924

925
            /* Create a new bidirectional line from the paragraph, sourcing bidi types fresh from
926
               the text's current buffer rather than trusting the paragraph's own cached offset */
927
            bidiLine = SBLineCreate(
15 ✔
928
                BidiTypesBufferGetPtr((BidiTypesBufferRef)&parentIterator->text->bidiTypes, paragraphStart),
15 ✔
929
                &bidiParagraph->fixedLevels[paragraphStart - textParagraph->index],
15 ✔
930
                bidiParagraph->baseLevel, bidiParagraph->stringEncoding,
15 ✔
931
                paragraphStart, paragraphLength);
932

933
            /* Initialize line processing */
934
            iterator->bidiLine = bidiLine;
15 ✔
935
            iterator->runIndex = 0;
15 ✔
936
        }
937

938
        /* Reset the current run state */
939
        InitializeVisualRun(&iterator->currentRun);
26 ✔
940
    }
941

942
    if (bidiLine) {
35 ✔
943
        SBVisualRun *currentRun = &iterator->currentRun;
24 ✔
944
        SBRun *bidiRun;
945

946
        /* Get the current bidirectional run */
947
        bidiRun = &bidiLine->fixedRuns[iterator->runIndex];
24 ✔
948

949
        /* Update the current run with bidirectional properties */
950
        currentRun->index = bidiRun->offset;
24 ✔
951
        currentRun->length = bidiRun->length;
24 ✔
952
        currentRun->level = bidiRun->level;
24 ✔
953

954
        /* Move to the next run in the line */
955
        iterator->runIndex += 1;
24 ✔
956

957
        /* Check if all runs in the line are processed */
958
        if (iterator->runIndex == bidiLine->runCount) {
24 ✔
959
            /* Clean up and prepare for the next line */
960
            SBLineRelease(bidiLine);
15 ✔
961
            iterator->bidiLine = NULL;
15 ✔
962
        }
963

964
        return SBTrue;
24 ✔
965
    }
966

967
    /* No more runs available */
968
    return SBFalse;
11 ✔
969
}
970

971
SBVisualRunIteratorRef SBVisualRunIteratorRetain(SBVisualRunIteratorRef iterator)
1 ✔
972
{
973
    return ObjectRetain(iterator);
1 ✔
974
}
975

976
void SBVisualRunIteratorRelease(SBVisualRunIteratorRef iterator)
12 ✔
977
{
978
    ObjectRelease(iterator);
12 ✔
979
}
12 ✔
980

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

© 2026 Coveralls, Inc