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

Tehreer / SheenBidi / 29015655877

09 Jul 2026 11:40AM UTC coverage: 96.177% (-0.1%) from 96.293%
29015655877

push

github

mta452
[lib] Fix paragraph segmentation issues

31 of 34 new or added lines in 1 file covered. (91.18%)

2 existing lines in 1 file now uncovered.

4000 of 4159 relevant lines covered (96.18%)

450282.22 hits per line

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

94.64
/Source/Text/TextAnalysis.c
1
/*
2
 * Copyright (C) 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
#include <stdlib.h>
23
#include <string.h>
24

25
#include <SheenBidi/SBCodepointSequence.h>
26
#include <SheenBidi/SBParagraph.h>
27
#include <SheenBidi/SBScriptLocator.h>
28

29
#include <API/SBAssert.h>
30
#include <API/SBCodepointSequence.h>
31
#include <API/SBParagraph.h>
32
#include <API/SBScriptLocator.h>
33
#include <Core/List.h>
34
#include <Text/BidiTypesBuffer.h>
35
#include <Text/TextBuffer.h>
36

37
#include "TextAnalysis.h"
38

39
/* =========================================================================
40
 * Copy-array helper (shared by GetScripts/GetResolvedLevels)
41
 * ========================================================================= */
42

43
enum {
44
    CopyArrayScripts = 0,
45
    CopyArrayLevels = 1
46
};
47
typedef SBUInt8 CopyArrayKind;
48

49
/* =========================================================================
50
 * Text Paragraph Implementation
51
 * ========================================================================= */
52

53
static void InitializeTextParagraph(TextParagraphRef paragraph)
355✔
54
{
55
    paragraph->index = SBInvalidIndex;
355✔
56
    paragraph->length = 0;
355✔
57
    paragraph->needsReanalysis = SBTrue;
355✔
58
    paragraph->bidiParagraph = NULL;
355✔
59

60
    ListInitialize(&paragraph->scripts, sizeof(SBScript));
355✔
61
}
355✔
62

63
static void FinalizeTextParagraph(TextParagraphRef paragraph)
359✔
64
{
65
    SBParagraphRef bidiParagraph = paragraph->bidiParagraph;
359✔
66

67
    if (bidiParagraph) {
359✔
68
        SBParagraphRelease(bidiParagraph);
352✔
69
    }
70

71
    ListFinalize(&paragraph->scripts);
359✔
72
}
359✔
73

74
/**
75
 * Comparison function for binary search to locate a paragraph containing a specific code unit.
76
 */
77
static int ParagraphIndexComparison(const void *key, const void *element)
239✔
78
{
79
    const SBUInteger *codeUnitIndex = key;
239✔
80
    const TextParagraph *paragraph = element;
239✔
81
    SBUInteger paragraphStart;
82
    SBUInteger paragraphEnd;
83

84
    paragraphStart = paragraph->index;
239✔
85
    paragraphEnd = paragraphStart + paragraph->length;
239✔
86

87
    if (*codeUnitIndex < paragraphStart) {
239✔
88
        return -1;
58✔
89
    }
90
    if (*codeUnitIndex >= paragraphEnd) {
181✔
91
        return 1;
2✔
92
    }
93

94
    return 0;
179✔
95
}
96

97
SB_INTERNAL SBUInteger TextAnalysisGetCodeUnitParagraphIndex(TextAnalysisRef analysis,
336✔
98
    SBUInteger codeUnitIndex)
99
{
100
    TextParagraph *array = analysis->paragraphs.items;
336✔
101
    SBUInteger count = analysis->paragraphs.count;
336✔
102
    void *item = NULL;
336✔
103

104
    if (array) {
336✔
105
        item = bsearch(&codeUnitIndex, array, count, sizeof(TextParagraph), ParagraphIndexComparison);
179✔
106
    }
107

108
    if (item) {
336✔
109
        return (SBUInteger)((TextParagraph *)item - array);
179✔
110
    }
111

112
    return SBInvalidIndex;
157✔
113
}
114

115
SB_INTERNAL void TextAnalysisGetBoundaryParagraphs(TextAnalysisRef analysis, SBUInteger codeUnitCount,
11✔
116
    SBUInteger rangeStart, SBUInteger rangeEnd,
117
    TextParagraphRef *firstParagraph, TextParagraphRef *lastParagraph)
118
{
119
    SBAssert(firstParagraph && lastParagraph);
11✔
120

121
    *firstParagraph = NULL;
11✔
122
    *lastParagraph = NULL;
11✔
123

124
    /* Find the first paragraph intersecting the range */
125
    if (rangeStart < codeUnitCount) {
11✔
126
        SBUInteger paragraphIndex;
127
        SBUInteger paragraphStart;
128
        SBUInteger paragraphEnd;
129

130
        paragraphIndex = TextAnalysisGetCodeUnitParagraphIndex(analysis, rangeStart);
11✔
131
        *firstParagraph = ListGetRef(&analysis->paragraphs, paragraphIndex);
11✔
132

133
        paragraphStart = (*firstParagraph)->index;
11✔
134
        paragraphEnd = paragraphStart + (*firstParagraph)->length;
11✔
135

136
        /* If the range doesn't extend beyond the first paragraph, they're the same */
137
        if (paragraphEnd >= rangeEnd) {
11✔
138
            *lastParagraph = *firstParagraph;
11✔
139
            return;
11✔
140
        }
141
    }
142

143
    /* Find the last paragraph if it's different from the first */
144
    if (rangeEnd <= codeUnitCount) {
×
145
        SBUInteger paragraphIndex;
146

147
        paragraphIndex = TextAnalysisGetCodeUnitParagraphIndex(analysis, rangeEnd - 1);
×
148
        *lastParagraph = ListGetRef(&analysis->paragraphs, paragraphIndex);
×
149
    }
150
}
151

152
/* =========================================================================
153
 * Paragraph Segmentation
154
 * ========================================================================= */
155

156
static TextParagraphRef InsertEmptyParagraph(TextAnalysisRef analysis, SBUInteger listIndex)
355✔
157
{
158
    SBBoolean succeeded;
159
    TextParagraph paragraph;
355✔
160

161
    InitializeTextParagraph(&paragraph);
355✔
162
    succeeded = ListInsert(&analysis->paragraphs, listIndex, &paragraph);
355✔
163

164
    return (succeeded ? ListGetRef(&analysis->paragraphs, listIndex) : NULL);
355✔
165
}
166

167
static void RemoveParagraphRange(TextAnalysisRef analysis, SBUInteger index, SBUInteger length)
229✔
168
{
169
    SBUInteger endIndex = index + length;
229✔
170
    SBUInteger paragraphIndex;
171

172
    /* Finalize each paragraph's resources */
173
    for (paragraphIndex = index; paragraphIndex < endIndex; paragraphIndex++) {
313✔
174
        TextParagraphRef paragraph = ListGetRef(&analysis->paragraphs, paragraphIndex);
84✔
175
        FinalizeTextParagraph(paragraph);
84✔
176
    }
177

178
    ListRemoveRange(&analysis->paragraphs, index, length);
229✔
179
}
229✔
180

181
/**
182
 * Adjusts the start index of all paragraphs from a given position onward by a delta.
183
 */
184
static void ShiftParagraphRanges(TextAnalysisRef analysis, SBUInteger listIndex, SBInteger indexDelta)
221✔
185
{
186
    while (listIndex < analysis->paragraphs.count) {
232✔
187
        TextParagraphRef paragraph = ListGetRef(&analysis->paragraphs, listIndex);
11✔
188
        paragraph->index += indexDelta;
11✔
189
        listIndex += 1;
11✔
190
    }
191
}
221✔
192

193
/* =========================================================================
194
 * Replacement / Flush
195
 * ========================================================================= */
196

197
/*
198
 * Re-segments the paragraph list after a code-unit replacement.
199
 *
200
 * Strategy: identify the contiguous block of existing paragraphs whose boundaries the edit can
201
 * affect, discard exactly that block, re-scan the corresponding span of the (already updated) buffer
202
 * to regenerate fresh paragraphs, and shift the untouched survivors that follow.
203
 *
204
 * Correctness hinges on choosing the affected block precisely:
205
 *
206
 *   - First affected paragraph: the one containing `replaceStart - 1` (or the list head). Probing the
207
 *     code unit *before* the edit captures the case where the edit deletes a paragraph separator and
208
 *     merges the edited paragraph with its predecessor.
209
 *
210
 *   - First surviving paragraph: the first existing paragraph that starts strictly after `oldEnd`. Its
211
 *     leading separator sits at or beyond `oldEnd`, i.e. outside the replaced range `[replaceStart,
212
 *     oldEnd)`, so that separator (and hence the survivor's content and internal boundaries) is
213
 *     unaffected and only needs its start index shifted by `lengthDelta`. Because that separator still
214
 *     exists in the new buffer, the re-scan of the affected span naturally terminates exactly at the
215
 *     survivor's shifted start, keeping the two regions perfectly aligned.
216
 */
217
static void UpdateParagraphsForTextReplacement(TextAnalysisRef analysis, TextBufferRef buffer,
225✔
218
    BidiTypesBufferRef bidiTypesBuffer, SBUInteger replaceStart, SBUInteger oldLength, SBUInteger newLength)
219
{
220
    SBUInteger oldEnd = replaceStart + oldLength;
225✔
221
    SBInteger lengthDelta = (SBInteger)(newLength - oldLength);
225✔
222
    SBUInteger paragraphCount = analysis->paragraphs.count;
225✔
223
    SBUInteger firstIndex;
224
    SBUInteger survivorIndex;
225
    SBUInteger rescanStart;
226
    SBUInteger listIndex;
227
    SBUInteger scanIndex;
228
    SBCodepointSequence sequence;
225✔
229
    TextParagraphRef paragraph;
230

231
    /* Locate the first affected paragraph (see the note above for why replaceStart - 1 is probed). */
232
    firstIndex = TextAnalysisGetCodeUnitParagraphIndex(analysis, replaceStart > 0 ? replaceStart - 1 : 0);
225✔
233
    if (firstIndex == SBInvalidIndex) {
225✔
234
        firstIndex = paragraphCount;
157✔
235
    }
236

237
    if (firstIndex < paragraphCount) {
225✔
238
        paragraph = ListGetRef(&analysis->paragraphs, firstIndex);
68✔
239
        rescanStart = paragraph->index;
68✔
240
    } else {
241
        rescanStart = replaceStart;
157✔
242
    }
243

244
    /* Discard the paragraphs that start within the replaced range; they must be regenerated. Any
245
     * paragraph starting after the range is a potential survivor and is handled by the re-scan below. */
246
    for (survivorIndex = firstIndex; survivorIndex < paragraphCount; survivorIndex++) {
309✔
247
        paragraph = ListGetRef(&analysis->paragraphs, survivorIndex);
95✔
248
        if (paragraph->index > oldEnd) {
95✔
249
            break;
11✔
250
        }
251
    }
252
    RemoveParagraphRange(analysis, firstIndex, survivorIndex - firstIndex);
225✔
253

254
    TextBufferGetCodepointSequence(buffer, &sequence);
225✔
255

256
    /*
257
     * Re-scan the affected span, regenerating paragraphs. A surviving paragraph after the edited
258
     * region can be reused only when a freshly-computed boundary lands exactly on its (shifted) start;
259
     * this is always the case for well-formed text. If a regenerated paragraph instead overruns a
260
     * survivor's start - which can happen when an edit alters how later code units decode (e.g. a
261
     * malformed multi-byte sequence) so that a previous boundary no longer exists - that survivor is
262
     * absorbed and removed. Re-scanning therefore continues until it re-aligns with a survivor or
263
     * reaches the end of the text, guaranteeing a gap-free, overlap-free segmentation in all cases.
264
     */
265
    listIndex = firstIndex;
225✔
266
    scanIndex = rescanStart;
225✔
267

268
    while (scanIndex < sequence.stringLength) {
580✔
269
        SBUInteger separatorLength;
366✔
270
        SBUInteger paraLength;
366✔
271

272
        /* Re-align with, or absorb, the next survivor. */
273
        if (listIndex < analysis->paragraphs.count) {
366✔
274
            TextParagraphRef survivor = ListGetRef(&analysis->paragraphs, listIndex);
25✔
275
            SBUInteger survivorStart = (SBUInteger)(survivor->index + lengthDelta);
25✔
276

277
            if (scanIndex == survivorStart) {
25✔
278
                break;
11✔
279
            }
280
            if (survivorStart < scanIndex) {
14✔
NEW
281
                RemoveParagraphRange(analysis, listIndex, 1);
×
NEW
282
                continue;
×
283
            }
284
        }
285

286
        SBCodepointSequenceGetParagraphBoundary(&sequence, BidiTypesBufferGetPtr(bidiTypesBuffer, 0),
355✔
287
            scanIndex, sequence.stringLength - scanIndex, &paraLength, &separatorLength);
355✔
288

289
        paragraph = InsertEmptyParagraph(analysis, listIndex);
355✔
290
        paragraph->index = scanIndex;
355✔
291
        paragraph->length = paraLength;
355✔
292
        paragraph->needsReanalysis = SBTrue;
355✔
293

294
        scanIndex += paraLength;
355✔
295
        listIndex += 1;
355✔
296
    }
297

298
    /* Absorb any survivors overrun by the final regenerated paragraph (e.g. when the re-scan ran to
299
     * the end of the text without re-aligning on a survivor boundary). */
300
    while (listIndex < analysis->paragraphs.count) {
225✔
301
        TextParagraphRef survivor = ListGetRef(&analysis->paragraphs, listIndex);
11✔
302
        SBUInteger survivorStart = (SBUInteger)(survivor->index + lengthDelta);
11✔
303

304
        if (survivorStart >= scanIndex) {
11✔
305
            break;
11✔
306
        }
NEW
307
        RemoveParagraphRange(analysis, listIndex, 1);
×
308
    }
309

310
    /* Shift the untouched survivors that follow the regenerated region. */
311
    if (lengthDelta != 0) {
225✔
312
        ShiftParagraphRanges(analysis, listIndex, lengthDelta);
221✔
313
    }
314
}
225✔
315

316
SB_INTERNAL void TextAnalysisReplaceRange(TextAnalysisRef analysis, TextBufferRef buffer,
225✔
317
    BidiTypesBufferRef bidiTypesBuffer, SBUInteger replaceStart, SBUInteger oldLength, SBUInteger newLength)
318
{
319
    UpdateParagraphsForTextReplacement(analysis, buffer, bidiTypesBuffer, replaceStart, oldLength, newLength);
225✔
320
}
225✔
321

322
static void GenerateBidiParagraph(TextAnalysisRef analysis, TextBufferRef buffer,
348✔
323
    BidiTypesBufferRef bidiTypesBuffer, TextParagraphRef paragraph)
324
{
325
    SBCodepointSequence codepointSequence;
348✔
326
    const SBBidiType *bidiTypes;
327

328
    TextBufferGetCodepointSequence(buffer, &codepointSequence);
348✔
329
    bidiTypes = BidiTypesBufferGetPtr(bidiTypesBuffer, 0);
348✔
330

331
    if (paragraph->bidiParagraph) {
348✔
332
        /* Release old bidi paragraph */
UNCOV
333
        SBParagraphRelease(paragraph->bidiParagraph);
×
UNCOV
334
        paragraph->bidiParagraph = NULL;
×
335
    }
336

337
    paragraph->bidiParagraph = SBParagraphCreateWithCodepointSequence(
348✔
338
        &codepointSequence, bidiTypes, paragraph->index, paragraph->length, analysis->baseLevel);
348✔
339
}
348✔
340

341
static void PopulateParagraphScripts(TextAnalysisRef analysis, TextBufferRef buffer,
348✔
342
    TextParagraphRef paragraph)
343
{
344
    SBScriptLocatorRef scriptLocator;
345
    SBCodepointSequence codepointSequence;
348✔
346
    const SBScriptAgent *scriptAgent;
347

348
    scriptLocator = analysis->scriptLocator;
348✔
349

350
    codepointSequence.stringEncoding = buffer->encoding;
348✔
351
    codepointSequence.stringBuffer = TextBufferGetCodeUnitsPtr(buffer, paragraph->index);
348✔
352
    codepointSequence.stringLength = paragraph->length;
348✔
353

354
    ListRemoveAll(&paragraph->scripts);
348✔
355
    ListReserveRange(&paragraph->scripts, 0, paragraph->length);
348✔
356

357
    scriptAgent = &scriptLocator->agent;
348✔
358
    SBScriptLocatorLoadCodepoints(scriptLocator, &codepointSequence);
348✔
359

360
    while (SBScriptLocatorMoveNext(scriptLocator)) {
730✔
361
        SBUInteger runStart = scriptAgent->offset;
382✔
362
        SBUInteger runEnd = runStart + scriptAgent->length;
382✔
363
        SBScript runScript = scriptAgent->script;
382✔
364

365
        while (runStart < runEnd) {
6,068✔
366
            ListSetVal(&paragraph->scripts, runStart, runScript);
5,686✔
367
            runStart += 1;
5,686✔
368
        }
369
    }
370
}
348✔
371

372
SB_INTERNAL void TextAnalysisFlush(TextAnalysisRef analysis, TextBufferRef buffer,
225✔
373
    BidiTypesBufferRef bidiTypesBuffer)
374
{
375
    SBUInteger paragraphCount = analysis->paragraphs.count;
225✔
376
    SBUInteger paragraphIndex;
377

378
    for (paragraphIndex = 0; paragraphIndex < paragraphCount; paragraphIndex++) {
593✔
379
        TextParagraphRef paragraph = ListGetRef(&analysis->paragraphs, paragraphIndex);
368✔
380

381
        if (paragraph->needsReanalysis) {
368✔
382
            GenerateBidiParagraph(analysis, buffer, bidiTypesBuffer, paragraph);
348✔
383
            PopulateParagraphScripts(analysis, buffer, paragraph);
348✔
384

385
            paragraph->needsReanalysis = SBFalse;
348✔
386
        }
387
    }
388
}
225✔
389

390
/* =========================================================================
391
 * Queries
392
 * ========================================================================= */
393

394
static void CopyPerParagraphArray(TextAnalysisRef analysis, SBUInteger index, SBUInteger length,
4✔
395
    CopyArrayKind kind, void *buffer)
396
{
397
    SBUInteger rangeStart = index;
4✔
398
    SBUInteger rangeEnd = index + length;
4✔
399
    SBUInteger paragraphIndex = TextAnalysisGetCodeUnitParagraphIndex(analysis, rangeStart);
4✔
400
    SBUInt8 *cursor = buffer;
4✔
401

402
    while (rangeStart < rangeEnd) {
8✔
403
        const TextParagraph *textParagraph = ListGetRef(&analysis->paragraphs, paragraphIndex);
4✔
404
        SBUInteger copyStart = textParagraph->index;
4✔
405
        SBUInteger copyEnd = copyStart + textParagraph->length;
4✔
406
        SBUInteger copyCount;
407
        SBUInteger byteCount;
408
        const void *source;
409

410
        /* Clamp copy range to requested range */
411
        if (copyStart < rangeStart) {
4✔
412
            copyStart = rangeStart;
×
413
        }
414
        if (copyEnd > rangeEnd) {
4✔
415
            copyEnd = rangeEnd;
×
416
        }
417

418
        copyCount = copyEnd - copyStart;
4✔
419

420
        if (kind == CopyArrayScripts) {
4✔
421
            source = ListGetRef(&textParagraph->scripts, copyStart - textParagraph->index);
2✔
422
            byteCount = copyCount * sizeof(SBScript);
2✔
423
        } else {
424
            SBParagraphRef bidiParagraph = textParagraph->bidiParagraph;
2✔
425

426
            source = &bidiParagraph->fixedLevels[copyStart - textParagraph->index];
2✔
427
            byteCount = copyCount * sizeof(SBLevel);
2✔
428
        }
429

430
        memcpy(cursor, source, byteCount);
4✔
431

432
        cursor += byteCount;
4✔
433
        rangeStart = copyEnd;
4✔
434
        paragraphIndex += 1;
4✔
435
    }
436
}
4✔
437

438
SB_INTERNAL void TextAnalysisGetScripts(TextAnalysisRef analysis, SBUInteger index, SBUInteger length,
2✔
439
    SBScript *buffer)
440
{
441
    CopyPerParagraphArray(analysis, index, length, CopyArrayScripts, buffer);
2✔
442
}
2✔
443

444
SB_INTERNAL void TextAnalysisGetResolvedLevels(TextAnalysisRef analysis, SBUInteger index,
2✔
445
    SBUInteger length, SBLevel *buffer)
446
{
447
    CopyPerParagraphArray(analysis, index, length, CopyArrayLevels, buffer);
2✔
448
}
2✔
449

450
SB_INTERNAL void TextAnalysisGetCodeUnitParagraphInfo(TextAnalysisRef analysis, SBUInteger index,
2✔
451
    SBParagraphInfo *paragraphInfo)
452
{
453
    SBUInteger paragraphIndex = TextAnalysisGetCodeUnitParagraphIndex(analysis, index);
2✔
454
    const TextParagraph *textParagraph = ListGetRef(&analysis->paragraphs, paragraphIndex);
2✔
455
    SBParagraphRef bidiParagraph = textParagraph->bidiParagraph;
2✔
456

457
    paragraphInfo->index = textParagraph->index;
2✔
458
    paragraphInfo->length = textParagraph->length;
2✔
459
    paragraphInfo->baseLevel = bidiParagraph->baseLevel;
2✔
460
}
2✔
461

462
/* =========================================================================
463
 * Lifecycle
464
 * ========================================================================= */
465

466
SB_INTERNAL void TextAnalysisInitialize(TextAnalysisRef analysis, SBLevel baseLevel)
172✔
467
{
468
    analysis->baseLevel = baseLevel;
172✔
469
    analysis->scriptLocator = SBScriptLocatorCreate();
172✔
470

471
    ListInitialize(&analysis->paragraphs, sizeof(TextParagraph));
172✔
472
}
172✔
473

474
SB_INTERNAL void TextAnalysisCopyParagraphs(TextAnalysisRef analysis, const TextAnalysis *source)
4✔
475
{
476
    SBUInteger paragraphCount;
477
    SBUInteger paragraphIndex;
478

479
    /* Clear any existing paragraphs, releasing their owned resources first */
480
    RemoveParagraphRange(analysis, 0, analysis->paragraphs.count);
4✔
481

482
    paragraphCount = source->paragraphs.count;
4✔
483
    ListReserveRange(&analysis->paragraphs, 0, paragraphCount);
4✔
484

485
    for (paragraphIndex = 0; paragraphIndex < paragraphCount; paragraphIndex++) {
8✔
486
        const TextParagraph *sourceParagraph = ListGetRef(&source->paragraphs, paragraphIndex);
4✔
487
        TextParagraphRef destParagraph = ListGetRef(&analysis->paragraphs, paragraphIndex);
4✔
488

489
        destParagraph->index = sourceParagraph->index;
4✔
490
        destParagraph->length = sourceParagraph->length;
4✔
491
        ListInitialize(&destParagraph->scripts, sizeof(SBScript));
4✔
492

493
        if (sourceParagraph->needsReanalysis) {
4✔
494
            destParagraph->needsReanalysis = SBTrue;
×
495
            destParagraph->bidiParagraph = NULL;
×
496
        } else {
497
            SBUInteger scriptCount = sourceParagraph->scripts.count;
4✔
498
            SBUInteger byteCount;
499

500
            destParagraph->needsReanalysis = SBFalse;
4✔
501
            destParagraph->bidiParagraph = SBParagraphRetain(sourceParagraph->bidiParagraph);
4✔
502

503
            ListReserveRange(&destParagraph->scripts, 0, scriptCount);
4✔
504
            byteCount = scriptCount * sizeof(SBScript);
4✔
505
            memcpy(destParagraph->scripts.items, sourceParagraph->scripts.items, byteCount);
4✔
506
        }
507
    }
508
}
4✔
509

510
SB_INTERNAL void TextAnalysisFinalize(TextAnalysisRef analysis)
172✔
511
{
512
    SBUInteger paragraphIndex;
513

514
    for (paragraphIndex = 0; paragraphIndex < analysis->paragraphs.count; paragraphIndex++) {
447✔
515
        FinalizeTextParagraph(ListGetRef(&analysis->paragraphs, paragraphIndex));
275✔
516
    }
517

518
    ListFinalize(&analysis->paragraphs);
172✔
519

520
    if (analysis->scriptLocator) {
172✔
521
        SBScriptLocatorRelease(analysis->scriptLocator);
172✔
522
    }
523
}
172✔
524

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