Coverage Report

Created: 2025-11-05 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libass/libass/ass_outline.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2016 Vabishchevich Nikolay <vabnick@gmail.com>
3
 *
4
 * This file is part of libass.
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include "config.h"
20
#include "ass_compat.h"
21
22
#include "ass_utils.h"
23
#include "ass_outline.h"
24
25
26
27
/*
28
 * \brief Initialize ASS_Outline to an empty state
29
 * Equivalent to zeroing of outline object and doesn't free any memory.
30
 */
31
void ass_outline_clear(ASS_Outline *outline)
32
108k
{
33
108k
    outline->points = NULL;
34
108k
    outline->segments = NULL;
35
36
108k
    outline->n_points = outline->max_points = 0;
37
108k
    outline->n_segments = outline->max_segments = 0;
38
108k
}
39
40
/*
41
 * \brief Initialize ASS_Outline and allocate memory
42
 */
43
bool ass_outline_alloc(ASS_Outline *outline, size_t max_points, size_t max_segments)
44
70.9k
{
45
70.9k
    assert(max_points && max_segments);
46
70.9k
    if (max_points > SIZE_MAX / sizeof(ASS_Vector)) {
47
0
        ass_outline_clear(outline);
48
0
        return false;
49
0
    }
50
70.9k
    outline->points = malloc(sizeof(ASS_Vector) * max_points);
51
70.9k
    outline->segments = malloc(max_segments);
52
70.9k
    if (!outline->points || !outline->segments) {
53
0
        ass_outline_free(outline);
54
0
        return false;
55
0
    }
56
57
70.9k
    outline->max_points = max_points;
58
70.9k
    outline->max_segments = max_segments;
59
70.9k
    outline->n_points = outline->n_segments = 0;
60
70.9k
    return true;
61
70.9k
}
62
63
/*
64
 * \brief Free previously initialized ASS_Outline
65
 * Outline state after the call is the same as after ass_outline_clear().
66
 * Outline pointer can be NULL.
67
 */
68
void ass_outline_free(ASS_Outline *outline)
69
94.5k
{
70
94.5k
    if (!outline)
71
0
        return;
72
73
94.5k
    free(outline->points);
74
94.5k
    free(outline->segments);
75
76
94.5k
    ass_outline_clear(outline);
77
94.5k
}
78
79
80
static bool valid_point(const FT_Vector *pt)
81
208k
{
82
208k
    return labs(pt->x) <= OUTLINE_MAX && labs(pt->y) <= OUTLINE_MAX;
83
208k
}
84
85
/*
86
 * \brief Convert FT_Ouline into ASS_Outline
87
 * Outline should be preallocated to a sufficient size.
88
 */
89
bool ass_outline_convert(ASS_Outline *outline, const FT_Outline *source)
90
8.14k
{
91
8.14k
    enum Status {
92
8.14k
        S_ON, S_Q, S_C1, S_C2
93
8.14k
    };
94
95
19.7k
    for (int i = 0, j = 0; i < source->n_contours; i++) {
96
11.5k
        ASS_Vector pt;
97
11.5k
        int skip_last = 0;
98
11.5k
        enum Status st;
99
11.5k
        char seg;
100
101
11.5k
        int last = source->contours[i];
102
11.5k
        if (j > last || last >= source->n_points)
103
0
            return false;
104
105
        // skip degenerate 2-point contours from broken fonts
106
11.5k
        if (last - j < 2) {
107
0
            j = last + 1;
108
0
            continue;
109
0
        }
110
111
11.5k
        if (!valid_point(source->points + j))
112
0
            return false;
113
11.5k
        switch (FT_CURVE_TAG(source->tags[j])) {
114
9.87k
        case FT_CURVE_TAG_ON:
115
9.87k
            st = S_ON;
116
9.87k
            break;
117
118
1.70k
        case FT_CURVE_TAG_CONIC:
119
1.70k
            if (!valid_point(source->points + last))
120
0
                return false;
121
1.70k
            pt.x =  source->points[last].x;
122
1.70k
            pt.y = -source->points[last].y;
123
1.70k
            switch (FT_CURVE_TAG(source->tags[last])) {
124
1.28k
            case FT_CURVE_TAG_ON:
125
1.28k
                skip_last = 1;
126
1.28k
                last--;
127
1.28k
                break;
128
129
421
            case FT_CURVE_TAG_CONIC:
130
421
                pt.x = (pt.x + source->points[j].x) >> 1;
131
421
                pt.y = (pt.y - source->points[j].y) >> 1;
132
421
                break;
133
134
0
            default:
135
0
                return false;
136
1.70k
            }
137
1.70k
            assert(outline->n_points < outline->max_points);
138
1.70k
            outline->points[outline->n_points++] = pt;
139
1.70k
            st = S_Q;
140
1.70k
            break;
141
142
0
        default:
143
0
            return false;
144
11.5k
        }
145
11.5k
        pt.x =  source->points[j].x;
146
11.5k
        pt.y = -source->points[j].y;
147
11.5k
        assert(outline->n_points < outline->max_points);
148
11.5k
        outline->points[outline->n_points++] = pt;
149
150
206k
        for (j++; j <= last; j++) {
151
194k
            if (!valid_point(source->points + j))
152
0
                return false;
153
194k
            switch (FT_CURVE_TAG(source->tags[j])) {
154
121k
            case FT_CURVE_TAG_ON:
155
121k
                switch (st) {
156
63.4k
                case S_ON:
157
63.4k
                    seg = OUTLINE_LINE_SEGMENT;
158
63.4k
                    break;
159
160
58.2k
                case S_Q:
161
58.2k
                    seg = OUTLINE_QUADRATIC_SPLINE;
162
58.2k
                    break;
163
164
0
                case S_C2:
165
0
                    seg = OUTLINE_CUBIC_SPLINE;
166
0
                    break;
167
168
0
                default:
169
0
                    return false;
170
121k
                }
171
121k
                assert(outline->n_segments < outline->max_segments);
172
121k
                outline->segments[outline->n_segments++] = seg;
173
121k
                st = S_ON;
174
121k
                break;
175
176
73.0k
            case FT_CURVE_TAG_CONIC:
177
73.0k
                switch (st) {
178
61.0k
                case S_ON:
179
61.0k
                    st = S_Q;
180
61.0k
                    break;
181
182
11.9k
                case S_Q:
183
11.9k
                    assert(outline->n_segments < outline->max_segments);
184
11.9k
                    outline->segments[outline->n_segments++] = OUTLINE_QUADRATIC_SPLINE;
185
11.9k
                    pt.x = (pt.x + source->points[j].x) >> 1;
186
11.9k
                    pt.y = (pt.y - source->points[j].y) >> 1;
187
11.9k
                    assert(outline->n_points < outline->max_points);
188
11.9k
                    outline->points[outline->n_points++] = pt;
189
11.9k
                    break;
190
191
0
                default:
192
0
                    return false;
193
73.0k
                }
194
73.0k
                break;
195
196
73.0k
            case FT_CURVE_TAG_CUBIC:
197
0
                switch (st) {
198
0
                case S_ON:
199
0
                    st = S_C1;
200
0
                    break;
201
202
0
                case S_C1:
203
0
                    st = S_C2;
204
0
                    break;
205
206
0
                default:
207
0
                    return false;
208
0
                }
209
0
                break;
210
211
0
            default:
212
0
                return false;
213
194k
            }
214
194k
            pt.x =  source->points[j].x;
215
194k
            pt.y = -source->points[j].y;
216
194k
            assert(outline->n_points < outline->max_points);
217
194k
            outline->points[outline->n_points++] = pt;
218
194k
        }
219
220
11.5k
        switch (st) {
221
7.04k
        case S_ON:
222
7.04k
            seg = OUTLINE_LINE_SEGMENT | OUTLINE_CONTOUR_END;
223
7.04k
            break;
224
225
4.53k
        case S_Q:
226
4.53k
            seg = OUTLINE_QUADRATIC_SPLINE | OUTLINE_CONTOUR_END;
227
4.53k
            break;
228
229
0
        case S_C2:
230
0
            seg = OUTLINE_CUBIC_SPLINE | OUTLINE_CONTOUR_END;
231
0
            break;
232
233
0
        default:
234
0
            return false;
235
11.5k
        }
236
11.5k
        assert(outline->n_segments < outline->max_segments);
237
11.5k
        outline->segments[outline->n_segments++] = seg;
238
11.5k
        j += skip_last;
239
11.5k
    }
240
8.14k
    return true;
241
8.14k
}
242
243
/*
244
 * \brief Add a rectangle to the outline
245
 * Outline should be preallocated to a sufficient size
246
 * and coordinates should be in the allowable range.
247
 */
248
void ass_outline_add_rect(ASS_Outline *outline,
249
                          int32_t x0, int32_t y0, int32_t x1, int32_t y1)
250
19
{
251
19
    assert(outline->n_points + 4 <= outline->max_points);
252
19
    assert(outline->n_segments + 4 <= outline->max_segments);
253
19
    assert(abs(x0) <= OUTLINE_MAX && abs(y0) <= OUTLINE_MAX);
254
19
    assert(abs(x1) <= OUTLINE_MAX && abs(y1) <= OUTLINE_MAX);
255
19
    assert(!outline->n_segments ||
256
19
        (outline->segments[outline->n_segments - 1] & OUTLINE_CONTOUR_END));
257
258
19
    size_t pos = outline->n_points;
259
19
    outline->points[pos + 0].x = outline->points[pos + 3].x = x0;
260
19
    outline->points[pos + 1].x = outline->points[pos + 2].x = x1;
261
19
    outline->points[pos + 0].y = outline->points[pos + 1].y = y0;
262
19
    outline->points[pos + 2].y = outline->points[pos + 3].y = y1;
263
19
    outline->n_points = pos + 4;
264
265
19
    pos = outline->n_segments;
266
19
    outline->segments[pos + 0] = OUTLINE_LINE_SEGMENT;
267
19
    outline->segments[pos + 1] = OUTLINE_LINE_SEGMENT;
268
19
    outline->segments[pos + 2] = OUTLINE_LINE_SEGMENT;
269
19
    outline->segments[pos + 3] = OUTLINE_LINE_SEGMENT | OUTLINE_CONTOUR_END;
270
19
    outline->n_segments = pos + 4;
271
19
}
272
273
274
/*
275
 * \brief Add a single point to the outline
276
 * Outline should be allocated and will be enlarged if needed.
277
 * Also adds outline segment if segment parameter is nonzero.
278
 */
279
bool ass_outline_add_point(ASS_Outline *outline, ASS_Vector pt, char segment)
280
820k
{
281
820k
    assert(outline->max_points);
282
820k
    if (abs(pt.x) > OUTLINE_MAX || abs(pt.y) > OUTLINE_MAX)
283
0
        return false;
284
285
820k
    if (outline->n_points >= outline->max_points) {
286
10.0k
        size_t new_size = 2 * outline->max_points;
287
10.0k
        if (!ASS_REALLOC_ARRAY(outline->points, new_size))
288
0
            return false;
289
10.0k
        outline->max_points = new_size;
290
10.0k
    }
291
820k
    outline->points[outline->n_points] = pt;
292
820k
    outline->n_points++;
293
294
820k
    return !segment || ass_outline_add_segment(outline, segment);
295
820k
}
296
297
/*
298
 * \brief Add a segment to the outline
299
 * Outline should be allocated and will be enlarged if needed.
300
 */
301
bool ass_outline_add_segment(ASS_Outline *outline, char segment)
302
588k
{
303
588k
    assert(outline->max_segments);
304
588k
    if (outline->n_segments >= outline->max_segments) {
305
9.90k
        size_t new_size = 2 * outline->max_segments;
306
9.90k
        if (!ASS_REALLOC_ARRAY(outline->segments, new_size))
307
0
            return false;
308
9.90k
        outline->max_segments = new_size;
309
9.90k
    }
310
588k
    outline->segments[outline->n_segments] = segment;
311
588k
    outline->n_segments++;
312
588k
    return true;
313
588k
}
314
315
/*
316
 * \brief Close last contour
317
 */
318
void ass_outline_close_contour(ASS_Outline *outline)
319
22.8k
{
320
22.8k
    assert(outline->n_segments);
321
22.8k
    assert(!(outline->segments[outline->n_segments - 1] & ~OUTLINE_COUNT_MASK));
322
22.8k
    outline->segments[outline->n_segments - 1] |= OUTLINE_CONTOUR_END;
323
22.8k
}
324
325
326
/*
327
 * \brief Inplace rotate outline by 90 degrees and translate by offs
328
 */
329
bool ass_outline_rotate_90(ASS_Outline *outline, ASS_Vector offs)
330
0
{
331
0
    assert(abs(offs.x) <= INT32_MAX - OUTLINE_MAX);
332
0
    assert(abs(offs.y) <= INT32_MAX - OUTLINE_MAX);
333
0
    for (size_t i = 0; i < outline->n_points; i++) {
334
0
        ASS_Vector pt = { offs.x + outline->points[i].y,
335
0
                          offs.y - outline->points[i].x };
336
0
        if (abs(pt.x) > OUTLINE_MAX || abs(pt.y) > OUTLINE_MAX)
337
0
            return false;
338
0
        outline->points[i] = pt;
339
0
    }
340
0
    return true;
341
0
}
342
343
/*
344
 * \brief Scale outline by {2^scale_ord_x, 2^scale_ord_y}
345
 * Result outline should be uninitialized or empty.
346
 * Source outline can be NULL.
347
 */
348
bool ass_outline_scale_pow2(ASS_Outline *outline, const ASS_Outline *source,
349
                            int scale_ord_x, int scale_ord_y)
350
8.02k
{
351
8.02k
    if (!source || !source->n_points) {
352
0
        ass_outline_clear(outline);
353
0
        return true;
354
0
    }
355
356
8.02k
    int32_t lim_x = OUTLINE_MAX;
357
8.02k
    if (scale_ord_x > 0)
358
0
        lim_x = scale_ord_x < 32 ? lim_x >> scale_ord_x : 0;
359
8.02k
    else
360
8.02k
        scale_ord_x = FFMAX(scale_ord_x, -32);
361
362
8.02k
    int32_t lim_y = OUTLINE_MAX;
363
8.02k
    if (scale_ord_y > 0)
364
0
        lim_y = scale_ord_y < 32 ? lim_y >> scale_ord_y : 0;
365
8.02k
    else
366
8.02k
        scale_ord_y = FFMAX(scale_ord_y, -32);
367
368
8.02k
    if (!lim_x || !lim_y) {
369
0
        ass_outline_clear(outline);
370
0
        return false;
371
0
    }
372
373
8.02k
    if (!ass_outline_alloc(outline, source->n_points, source->n_segments))
374
0
        return false;
375
376
8.02k
    int sx = scale_ord_x + 32;
377
8.02k
    int sy = scale_ord_y + 32;
378
8.02k
    const ASS_Vector *pt = source->points;
379
224k
    for (size_t i = 0; i < source->n_points; i++) {
380
216k
        if (abs(pt[i].x) > lim_x || abs(pt[i].y) > lim_y) {
381
0
            ass_outline_free(outline);
382
0
            return false;
383
0
        }
384
        // that's equivalent to pt[i].x << scale_ord_x,
385
        // but works even for negative coordinate and/or shift amount
386
216k
        outline->points[i].x = pt[i].x * ((int64_t) 1 << sx) >> 32;
387
216k
        outline->points[i].y = pt[i].y * ((int64_t) 1 << sy) >> 32;
388
216k
    }
389
8.02k
    memcpy(outline->segments, source->segments, source->n_segments);
390
8.02k
    outline->n_points = source->n_points;
391
8.02k
    outline->n_segments = source->n_segments;
392
8.02k
    return true;
393
8.02k
}
394
395
/*
396
 * \brief Transform outline by 2x3 matrix
397
 * Result outline should be uninitialized or empty.
398
 * Source outline can be NULL.
399
 */
400
bool ass_outline_transform_2d(ASS_Outline *outline, const ASS_Outline *source,
401
                              const double m[2][3])
402
52.7k
{
403
52.7k
    if (!source || !source->n_points) {
404
14.2k
        ass_outline_clear(outline);
405
14.2k
        return true;
406
14.2k
    }
407
408
38.5k
    if (!ass_outline_alloc(outline, source->n_points, source->n_segments))
409
0
        return false;
410
411
38.5k
    const ASS_Vector *pt = source->points;
412
1.60M
    for (size_t i = 0; i < source->n_points; i++) {
413
1.57M
        double v[2];
414
4.71M
        for (int k = 0; k < 2; k++)
415
3.14M
            v[k] = m[k][0] * pt[i].x + m[k][1] * pt[i].y + m[k][2];
416
417
1.57M
        if (!(fabs(v[0]) < OUTLINE_MAX && fabs(v[1]) < OUTLINE_MAX)) {
418
0
            ass_outline_free(outline);
419
0
            return false;
420
0
        }
421
1.57M
        outline->points[i].x = ass_lrint(v[0]);
422
1.57M
        outline->points[i].y = ass_lrint(v[1]);
423
1.57M
    }
424
38.5k
    memcpy(outline->segments, source->segments, source->n_segments);
425
38.5k
    outline->n_points = source->n_points;
426
38.5k
    outline->n_segments = source->n_segments;
427
38.5k
    return true;
428
38.5k
}
429
430
/*
431
 * \brief Apply perspective transform by 3x3 matrix to the outline
432
 * Result outline should be uninitialized or empty.
433
 * Source outline can be NULL.
434
 */
435
bool ass_outline_transform_3d(ASS_Outline *outline, const ASS_Outline *source,
436
                              const double m[3][3])
437
2
{
438
2
    if (!source || !source->n_points) {
439
0
        ass_outline_clear(outline);
440
0
        return true;
441
0
    }
442
443
2
    if (!ass_outline_alloc(outline, source->n_points, source->n_segments))
444
0
        return false;
445
446
2
    const ASS_Vector *pt = source->points;
447
30
    for (size_t i = 0; i < source->n_points; i++) {
448
28
        double v[3];
449
112
        for (int k = 0; k < 3; k++)
450
84
            v[k] = m[k][0] * pt[i].x + m[k][1] * pt[i].y + m[k][2];
451
452
28
        double w = 1 / FFMAX(v[2], 0.1);
453
28
        v[0] *= w;
454
28
        v[1] *= w;
455
456
28
        if (!(fabs(v[0]) < OUTLINE_MAX && fabs(v[1]) < OUTLINE_MAX)) {
457
0
            ass_outline_free(outline);
458
0
            return false;
459
0
        }
460
28
        outline->points[i].x = ass_lrint(v[0]);
461
28
        outline->points[i].y = ass_lrint(v[1]);
462
28
    }
463
2
    memcpy(outline->segments, source->segments, source->n_segments);
464
2
    outline->n_points = source->n_points;
465
2
    outline->n_segments = source->n_segments;
466
2
    return true;
467
2
}
468
469
/*
470
 * \brief Find minimal X-coordinate of control points after perspective transform
471
 */
472
void ass_outline_update_min_transformed_x(const ASS_Outline *outline,
473
                                          const double m[3][3],
474
10.8k
                                          int32_t *min_x) {
475
10.8k
    const ASS_Vector *pt = outline->points;
476
311k
    for (size_t i = 0; i < outline->n_points; i++) {
477
300k
        double z = m[2][0] * pt[i].x + m[2][1] * pt[i].y + m[2][2];
478
300k
        double x = (m[0][0] * pt[i].x + m[0][1] * pt[i].y + m[0][2]) / FFMAX(z, 0.1);
479
300k
        if (isnan(x))
480
0
            continue;
481
300k
        int32_t ix = ass_lrint(FFMINMAX(x, -OUTLINE_MAX, OUTLINE_MAX));
482
300k
        *min_x = FFMIN(*min_x, ix);
483
300k
    }
484
10.8k
}
485
486
/*
487
 * \brief Update bounding box of control points
488
 */
489
void ass_outline_update_cbox(const ASS_Outline *outline, ASS_Rect *cbox)
490
33.4k
{
491
1.07M
    for (size_t i = 0; i < outline->n_points; i++)
492
1.04M
        rectangle_update(cbox,
493
1.04M
                         outline->points[i].x, outline->points[i].y,
494
1.04M
                         outline->points[i].x, outline->points[i].y);
495
33.4k
}
496
497
498
/*
499
 * Outline Stroke Algorithm
500
 *
501
 * Goal:
502
 * Given source outline, construct two border outlines, such as that
503
 * for any point inside any border outline (nonzero winding rule)
504
 * minimal distance to points of source outline (same rule)
505
 * is less than 1 with given precision, and for any point outside
506
 * both border outlines minimal distance is more than approximately 1.
507
 * Distance here is defined in normal space that is scaled by [1 / xbord, 1 / ybord].
508
 * Correspondingly distance is equal to hypot(dx / xbord, dy / ybord) and
509
 * approximate allowable error is eps / max(xbord, ybord).
510
 *
511
 * Two border outlines correspond to ±1 offset curves and
512
 * are required in case of self-intersecting source outline.
513
 *
514
 * Each of source segment that can be line segment, quadratic or cubic spline,
515
 * and also connection between them is stroked mostly independently.
516
 * Line segments can be offset quite straightforwardly.
517
 * For splines algorithm first tries to offset individual points,
518
 * then estimates error of such approximation and subdivide recursively if necessary.
519
 *
520
 * List of border cases handled by this algorithm:
521
 * 1) Too close points lead to random derivatives or even division by zero.
522
 *    Algorithm solves that by merging such points into one.
523
 * 2) Degenerate cases--near zero derivative at some spline points.
524
 *    Algorithm adds circular cap in such cases.
525
 * 3) Negative curvature--offset amount is larger than radius of curvature.
526
 *    Algorithm checks if produced splines can potentially have self-intersection
527
 *    and handles them accordingly. It's mostly done by skipping
528
 *    problematic spline and replacing it with polyline that covers only
529
 *    positive winging part of mathematical offset curve.
530
 *
531
 * Error estimation for splines is done by analyzing offset spline.
532
 * Offset spline is the difference between result and source spline in normal space.
533
 * Such spline should consist of vectors with length 1 and orthogonal to source spline.
534
 * Correspondingly error estimator have radial and angular part.
535
 *
536
 * Useful facts about B-splines.
537
 * 1) Derivative of B-spline of order N is B-spline of order N-1.
538
 * 2) Multiplication of B-splines of order N and M is B-spline of order N+M.
539
 * 3) B-spline is fully contained inside convex hull of its control points.
540
 *
541
 * So, for radial error it's possible to check only control points of
542
 * offset spline multiplication by itself. And for angular error it's
543
 * possible to check control points of cross and dot product between
544
 * offset spline and derivative spline.
545
 */
546
547
548
typedef struct {
549
    ASS_DVector v;
550
    double len;
551
} Normal;
552
553
typedef struct {
554
    ASS_Outline *result[2];   // result outlines
555
    size_t contour_first[2];  // start position of last contours
556
    double xbord, ybord;      // border sizes
557
    double xscale, yscale;    // inverse border sizes
558
    int eps;                  // allowable error in coordinate space
559
560
    // true if where are points in current contour
561
    bool contour_start;
562
    // skip flags for first and last point
563
    int first_skip, last_skip;
564
    // normal at first and last point
565
    ASS_DVector first_normal, last_normal;
566
    // first and last points of current contour
567
    ASS_Vector first_point, last_point;
568
569
    // cosinus of maximal angle that do not require cap
570
    double merge_cos;
571
    // cosinus of maximal angle of circular arc that can be approximated with quadratic spline
572
    double split_cos;
573
    // maximal distance between control points in normal space that triggers handling of degenerates
574
    double min_len;
575
    // constant that used in exact radial error checking in quadratic case
576
    double err_q;
577
    // constant that used in approximate radial error checking in cubic case
578
    double err_c;
579
    // tangent of maximal angular error
580
    double err_a;
581
} StrokerState;
582
583
/**
584
 * \brief 2D vector dot product
585
 */
586
static inline double vec_dot(ASS_DVector vec1, ASS_DVector vec2)
587
237k
{
588
237k
    return vec1.x * vec2.x + vec1.y * vec2.y;
589
237k
}
590
591
/**
592
 * \brief 2D vector cross product
593
 */
594
static inline double vec_crs(ASS_DVector vec1, ASS_DVector vec2)
595
166k
{
596
166k
    return vec1.x * vec2.y - vec1.y * vec2.x;
597
166k
}
598
599
/**
600
 * \brief 2D vector length
601
 */
602
static inline double vec_len(ASS_DVector vec)
603
226k
{
604
226k
    return sqrt(vec.x * vec.x + vec.y * vec.y);
605
226k
}
606
607
608
/**
609
 * \brief Add point to one or two border outlines
610
 * \param str stroker state
611
 * \param pt source point
612
 * \param offs offset in normal space
613
 * \param segment outline segment type
614
 * \param dir destination outline flags
615
 * \return false on allocation failure
616
 */
617
static bool emit_point(StrokerState *str, ASS_Vector pt,
618
                       ASS_DVector offs, char segment, int dir)
619
764k
{
620
764k
    int32_t dx = (int32_t) (str->xbord * offs.x);
621
764k
    int32_t dy = (int32_t) (str->ybord * offs.y);
622
623
764k
    if (dir & 1) {
624
434k
        ASS_Vector res = { pt.x + dx, pt.y + dy };
625
434k
        if (!ass_outline_add_point(str->result[0], res, segment))
626
0
            return false;
627
434k
    }
628
764k
    if (dir & 2) {
629
385k
        ASS_Vector res = { pt.x - dx, pt.y - dy };
630
385k
        if (!ass_outline_add_point(str->result[1], res, segment))
631
0
            return false;
632
385k
    }
633
764k
    return true;
634
764k
}
635
636
/**
637
 * \brief Replace first point of current contour
638
 * \param str stroker state
639
 * \param pt source point
640
 * \param offs offset in normal space
641
 * \param dir destination outline flags
642
 */
643
static void fix_first_point(StrokerState *str, ASS_Vector pt,
644
                            ASS_DVector offs, int dir)
645
1.86k
{
646
1.86k
    int32_t dx = (int32_t) (str->xbord * offs.x);
647
1.86k
    int32_t dy = (int32_t) (str->ybord * offs.y);
648
649
1.86k
    if (dir & 1) {
650
1.22k
        ASS_Vector res = { pt.x + dx, pt.y + dy };
651
1.22k
        ASS_Outline *ol = str->result[0];
652
1.22k
        ol->points[str->contour_first[0]] = res;
653
1.22k
    }
654
1.86k
    if (dir & 2) {
655
902
        ASS_Vector res = { pt.x - dx, pt.y - dy };
656
902
        ASS_Outline *ol = str->result[1];
657
902
        ol->points[str->contour_first[1]] = res;
658
902
    }
659
1.86k
}
660
661
/**
662
 * \brief Helper function for circular arc construction
663
 * \param str stroker state
664
 * \param pt center point
665
 * \param normal0 first normal
666
 * \param normal1 last normal
667
 * \param mul precalculated coefficients
668
 * \param level subdivision level
669
 * \param dir destination outline flags
670
 * \return false on allocation failure
671
 */
672
static bool process_arc(StrokerState *str, ASS_Vector pt,
673
                        ASS_DVector normal0, ASS_DVector normal1,
674
                        const double *mul, int level, int dir)
675
180k
{
676
180k
    ASS_DVector center;
677
180k
    center.x = (normal0.x + normal1.x) * mul[level];
678
180k
    center.y = (normal0.y + normal1.y) * mul[level];
679
180k
    if (level)
680
48.9k
        return process_arc(str, pt, normal0, center, mul, level - 1, dir) &&
681
48.9k
               process_arc(str, pt, center, normal1, mul, level - 1, dir);
682
131k
    return emit_point(str, pt, normal0, OUTLINE_QUADRATIC_SPLINE, dir) &&
683
131k
           emit_point(str, pt, center, 0, dir);
684
180k
}
685
686
/**
687
 * \brief Construct circular arc
688
 * \param str stroker state
689
 * \param pt center point
690
 * \param normal0 first normal
691
 * \param normal1 last normal
692
 * \param c dot product between normal0 and normal1
693
 * \param dir destination outline flags
694
 * \return false on allocation failure
695
 */
696
static bool draw_arc(StrokerState *str, ASS_Vector pt,
697
                     ASS_DVector normal0, ASS_DVector normal1, double c, int dir)
698
71.8k
{
699
71.8k
    enum { max_subdiv = 15 };
700
71.8k
    double mul[max_subdiv + 1];
701
702
71.8k
    ASS_DVector center;
703
71.8k
    bool small_angle = true;
704
71.8k
    if (c < 0) {
705
10.8k
        double mul = dir & 2 ? -sqrt(0.5) : sqrt(0.5);
706
10.8k
        mul /= sqrt(1 - c);
707
10.8k
        center.x = (normal1.y - normal0.y) * mul;
708
10.8k
        center.y = (normal0.x - normal1.x) * mul;
709
10.8k
        c = sqrt(FFMAX(0, 0.5 + 0.5 * c));
710
10.8k
        small_angle = false;
711
10.8k
    }
712
713
71.8k
    int pos = max_subdiv;
714
120k
    while (c < str->split_cos && pos) {
715
48.8k
        mul[pos] = sqrt(0.5) / sqrt(1 + c);
716
48.8k
        c = (1 + c) * mul[pos];
717
48.8k
        pos--;
718
48.8k
    }
719
71.8k
    mul[pos] = 1 / (1 + c);
720
71.8k
    return small_angle ?
721
61.0k
        process_arc(str, pt, normal0, normal1, mul + pos, max_subdiv - pos, dir) :
722
71.8k
        process_arc(str, pt, normal0, center,  mul + pos, max_subdiv - pos, dir) &&
723
10.8k
        process_arc(str, pt, center,  normal1, mul + pos, max_subdiv - pos, dir);
724
71.8k
}
725
726
/**
727
 * \brief Construct full circle
728
 * \param str stroker state
729
 * \param pt center point
730
 * \param dir destination outline flags
731
 * \return false on allocation failure
732
 */
733
static bool draw_circle(StrokerState *str, ASS_Vector pt, int dir)
734
3
{
735
3
    enum { max_subdiv = 15 };
736
3
    double mul[max_subdiv + 1], c = 0;
737
738
3
    int pos = max_subdiv;
739
6
    while (c < str->split_cos && pos) {
740
3
        mul[pos] = sqrt(0.5) / sqrt(1 + c);
741
3
        c = (1 + c) * mul[pos];
742
3
        pos--;
743
3
    }
744
3
    mul[pos] = 1 / (1 + c);
745
746
3
    ASS_DVector normal[4] = {
747
3
        { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }
748
3
    };
749
3
    return process_arc(str, pt, normal[0], normal[1], mul + pos, max_subdiv - pos, dir) &&
750
3
           process_arc(str, pt, normal[1], normal[2], mul + pos, max_subdiv - pos, dir) &&
751
3
           process_arc(str, pt, normal[2], normal[3], mul + pos, max_subdiv - pos, dir) &&
752
3
           process_arc(str, pt, normal[3], normal[0], mul + pos, max_subdiv - pos, dir);
753
3
}
754
755
/**
756
 * \brief Start new segment and add circular cap if necessary
757
 * \param str stroker state
758
 * \param pt start point of the new segment
759
 * \param normal normal at start of the new segment
760
 * \param dir destination outline flags
761
 * \return false on allocation failure
762
 */
763
static bool start_segment(StrokerState *str, ASS_Vector pt,
764
                          ASS_DVector normal, int dir)
765
154k
{
766
154k
    if (str->contour_start) {
767
11.4k
        str->contour_start = false;
768
11.4k
        str->first_skip = str->last_skip = 0;
769
11.4k
        str->first_normal = str->last_normal = normal;
770
11.4k
        str->first_point = pt;
771
11.4k
        return true;
772
11.4k
    }
773
774
142k
    ASS_DVector prev = str->last_normal;
775
142k
    double c = vec_dot(prev, normal);
776
142k
    if (c > str->merge_cos) {  // merge without cap
777
70.8k
        double mul = 1 / (1 + c);
778
70.8k
        str->last_normal.x = (str->last_normal.x + normal.x) * mul;
779
70.8k
        str->last_normal.y = (str->last_normal.y + normal.y) * mul;
780
70.8k
        return true;
781
70.8k
    }
782
71.8k
    str->last_normal = normal;
783
784
    // check for negative curvature
785
71.8k
    double s = vec_crs(prev, normal);
786
71.8k
    int skip_dir = s < 0 ? 1 : 2;
787
71.8k
    if (dir & skip_dir) {
788
71.8k
        if (!emit_point(str, pt, prev, OUTLINE_LINE_SEGMENT, ~str->last_skip & skip_dir))
789
0
            return false;
790
71.8k
        ASS_DVector zero_normal = {0, 0};
791
71.8k
        if (!emit_point(str, pt, zero_normal, OUTLINE_LINE_SEGMENT, skip_dir))
792
0
            return false;
793
71.8k
    }
794
71.8k
    str->last_skip = skip_dir;
795
796
71.8k
    dir &= ~skip_dir;
797
71.8k
    return !dir || draw_arc(str, pt, prev, normal, c, dir);
798
71.8k
}
799
800
/**
801
 * \brief Same as emit_point() but also updates skip flags
802
 */
803
static bool emit_first_point(StrokerState *str, ASS_Vector pt, char segment, int dir)
804
153k
{
805
153k
    str->last_skip &= ~dir;
806
153k
    return emit_point(str, pt, str->last_normal, segment, dir);
807
153k
}
808
809
/**
810
 * \brief Prepare to skip part of curve
811
 * \param str stroker state
812
 * \param pt start point of the skipped part
813
 * \param dir destination outline flags
814
 * \param first true if the skipped part is at start of the segment
815
 * \return false on allocation failure
816
 */
817
static bool prepare_skip(StrokerState *str, ASS_Vector pt, int dir, bool first)
818
55.4k
{
819
55.4k
    if (first)
820
2.72k
        str->first_skip |= dir;
821
52.7k
    else if (!emit_point(str, pt, str->last_normal, OUTLINE_LINE_SEGMENT, ~str->last_skip & dir))
822
0
        return false;
823
55.4k
    str->last_skip |= dir;
824
55.4k
    return true;
825
55.4k
}
826
827
/**
828
 * \brief Process source line segment
829
 * \param str stroker state
830
 * \param pt1 end point of the line segment
831
 * \param dir destination outline flags
832
 * \return false on allocation failure
833
 */
834
static bool add_line(StrokerState *str, ASS_Vector pt1, int dir)
835
82.1k
{
836
82.1k
    int32_t dx = pt1.x - str->last_point.x;
837
82.1k
    int32_t dy = pt1.y - str->last_point.y;
838
82.1k
    if (dx > -str->eps && dx < str->eps && dy > -str->eps && dy < str->eps)
839
11.8k
        return true;
840
841
70.3k
    ASS_DVector deriv = { dy * str->yscale, -dx * str->xscale };
842
70.3k
    double scale = 1 / vec_len(deriv);
843
70.3k
    ASS_DVector normal = { deriv.x * scale, deriv.y * scale };
844
70.3k
    if (!start_segment(str, str->last_point, normal, dir))
845
0
        return false;
846
70.3k
    if (!emit_first_point(str, str->last_point, OUTLINE_LINE_SEGMENT, dir))
847
0
        return false;
848
70.3k
    str->last_normal = normal;
849
70.3k
    str->last_point = pt1;
850
70.3k
    return true;
851
70.3k
}
852
853
854
/**
855
 * \brief Estimate error for quadratic spline
856
 * \param str stroker state
857
 * \param c dot product between normal[0] and normal[1]
858
 * \param s cross product between normal[0] and normal[1]
859
 * \param normal first and last spline normal
860
 * \param result best offset for central spline point
861
 * \return false if error is too large
862
 */
863
static bool estimate_quadratic_error(StrokerState *str, double c, double s,
864
                                     const Normal *normal, ASS_DVector *result)
865
88.1k
{
866
    // check radial error
867
88.1k
    if (!((3 + c) * (3 + c) < str->err_q * (1 + c)))
868
4.17k
        return false;
869
870
83.9k
    double mul = 1 / (1 + c);
871
83.9k
    double l0 = 2 * normal[0].len, l1 = 2 * normal[1].len;
872
83.9k
    double dot0 = l0 + normal[1].len * c, crs0 = (l0 * mul - normal[1].len) * s;
873
83.9k
    double dot1 = l1 + normal[0].len * c, crs1 = (l1 * mul - normal[0].len) * s;
874
    // check angular error
875
83.9k
    if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1))
876
377
        return false;
877
878
83.6k
    result->x = (normal[0].v.x + normal[1].v.x) * mul;
879
83.6k
    result->y = (normal[0].v.y + normal[1].v.y) * mul;
880
83.6k
    return true;
881
83.9k
}
882
883
/**
884
 * \brief Helper function for quadratic spline construction
885
 * \param str stroker state
886
 * \param pt array of 3 source spline points
887
 * \param deriv array of 2 differences between adjacent points in normal space
888
 * \param normal first and last spline normal
889
 * \param dir destination outline flags
890
 * \param first true if the current part is at start of the segment
891
 * \return false on allocation failure
892
 */
893
static bool process_quadratic(StrokerState *str, const ASS_Vector *pt,
894
                              const ASS_DVector *deriv, const Normal *normal,
895
                              int dir, bool first)
896
94.8k
{
897
94.8k
    double c = vec_dot(normal[0].v, normal[1].v);
898
94.8k
    double s = vec_crs(normal[0].v, normal[1].v);
899
94.8k
    int check_dir = dir, skip_dir = s < 0 ? 1 : 2;
900
94.8k
    if (dir & skip_dir) {
901
86.1k
        double abs_s = fabs(s);
902
86.1k
        double f0 = normal[0].len * c + normal[1].len;
903
86.1k
        double f1 = normal[1].len * c + normal[0].len;
904
86.1k
        double g0 = normal[0].len * abs_s;
905
86.1k
        double g1 = normal[1].len * abs_s;
906
        // check for self-intersection
907
86.1k
        if (f0 < abs_s && f1 < abs_s) {
908
62.1k
            double d2 = (f0 * normal[1].len + f1 * normal[0].len) / 2;
909
62.1k
            if (d2 < g0 && d2 < g1) {
910
55.4k
                if (!prepare_skip(str, pt[0], skip_dir, first))
911
0
                    return false;
912
55.4k
                if (f0 < 0 || f1 < 0) {
913
0
                    ASS_DVector zero_normal = {0, 0};
914
0
                    if (!emit_point(str, pt[0], zero_normal, OUTLINE_LINE_SEGMENT, skip_dir) ||
915
0
                        !emit_point(str, pt[2], zero_normal, OUTLINE_LINE_SEGMENT, skip_dir))
916
0
                        return false;
917
55.4k
                } else {
918
55.4k
                    double mul = f0 / abs_s;
919
55.4k
                    ASS_DVector offs = { normal[0].v.x * mul, normal[0].v.y * mul };
920
55.4k
                    if (!emit_point(str, pt[0], offs, OUTLINE_LINE_SEGMENT, skip_dir))
921
0
                        return false;
922
55.4k
                }
923
55.4k
                dir &= ~skip_dir;
924
55.4k
                if (!dir) {
925
6.66k
                    str->last_normal = normal[1].v;
926
6.66k
                    return true;
927
6.66k
                }
928
55.4k
            }
929
55.5k
            check_dir ^= skip_dir;
930
55.5k
        } else if (c + g0 < 1 && c + g1 < 1)
931
0
            check_dir ^= skip_dir;
932
86.1k
    }
933
934
88.1k
    ASS_DVector result;
935
88.1k
    if (check_dir && estimate_quadratic_error(str, c, s, normal, &result)) {
936
83.6k
        if (!emit_first_point(str, pt[0], OUTLINE_QUADRATIC_SPLINE, check_dir))
937
0
            return false;
938
83.6k
        if (!emit_point(str, pt[1], result, 0, check_dir))
939
0
            return false;
940
83.6k
        dir &= ~check_dir;
941
83.6k
        if (!dir) {
942
76.9k
            str->last_normal = normal[1].v;
943
76.9k
            return true;
944
76.9k
        }
945
83.6k
    }
946
947
11.2k
    ASS_Vector next[5];
948
11.2k
    next[1].x = pt[0].x + pt[1].x;
949
11.2k
    next[1].y = pt[0].y + pt[1].y;
950
11.2k
    next[3].x = pt[1].x + pt[2].x;
951
11.2k
    next[3].y = pt[1].y + pt[2].y;
952
11.2k
    next[2].x = (next[1].x + next[3].x + 2) >> 2;
953
11.2k
    next[2].y = (next[1].y + next[3].y + 2) >> 2;
954
11.2k
    next[1].x >>= 1;
955
11.2k
    next[1].y >>= 1;
956
11.2k
    next[3].x >>= 1;
957
11.2k
    next[3].y >>= 1;
958
11.2k
    next[0] = pt[0];
959
11.2k
    next[4] = pt[2];
960
961
11.2k
    ASS_DVector next_deriv[3];
962
11.2k
    next_deriv[0].x = deriv[0].x / 2;
963
11.2k
    next_deriv[0].y = deriv[0].y / 2;
964
11.2k
    next_deriv[2].x = deriv[1].x / 2;
965
11.2k
    next_deriv[2].y = deriv[1].y / 2;
966
11.2k
    next_deriv[1].x = (next_deriv[0].x + next_deriv[2].x) / 2;
967
11.2k
    next_deriv[1].y = (next_deriv[0].y + next_deriv[2].y) / 2;
968
969
11.2k
    double len = vec_len(next_deriv[1]);
970
11.2k
    if (len < str->min_len) {  // check degenerate case
971
7
        if (!emit_first_point(str, next[0], OUTLINE_LINE_SEGMENT, dir))
972
0
            return false;
973
7
        if (!start_segment(str, next[2], normal[1].v, dir))
974
0
            return false;
975
7
        str->last_skip &= ~dir;
976
7
        return emit_point(str, next[2], normal[1].v, OUTLINE_LINE_SEGMENT, dir);
977
7
    }
978
979
11.2k
    double scale = 1 / len;
980
11.2k
    Normal next_normal[3] = {
981
11.2k
        { normal[0].v, normal[0].len / 2 },
982
11.2k
        { { next_deriv[1].x * scale, next_deriv[1].y * scale }, len },
983
11.2k
        { normal[1].v, normal[1].len / 2 }
984
11.2k
    };
985
11.2k
    return process_quadratic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first) &&
986
11.2k
           process_quadratic(str, next + 2, next_deriv + 1, next_normal + 1, dir, false);
987
11.2k
}
988
989
/**
990
 * \brief Process source quadratic spline
991
 * \param str stroker state
992
 * \param pt1 middle control point
993
 * \param pt2 final spline point
994
 * \param dir destination outline flags
995
 * \return false on allocation failure
996
 */
997
static bool add_quadratic(StrokerState *str, ASS_Vector pt1, ASS_Vector pt2, int dir)
998
73.7k
{
999
73.7k
    int32_t dx0 = pt1.x - str->last_point.x;
1000
73.7k
    int32_t dy0 = pt1.y - str->last_point.y;
1001
73.7k
    if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps)
1002
697
        return add_line(str, pt2, dir);
1003
1004
73.0k
    int32_t dx1 = pt2.x - pt1.x;
1005
73.0k
    int32_t dy1 = pt2.y - pt1.y;
1006
73.0k
    if (dx1 > -str->eps && dx1 < str->eps && dy1 > -str->eps && dy1 < str->eps)
1007
628
        return add_line(str, pt2, dir);
1008
1009
72.4k
    ASS_Vector pt[3] = { str->last_point, pt1, pt2 };
1010
72.4k
    str->last_point = pt2;
1011
1012
72.4k
    ASS_DVector deriv[2] = {
1013
72.4k
        { dy0 * str->yscale, -dx0 * str->xscale },
1014
72.4k
        { dy1 * str->yscale, -dx1 * str->xscale }
1015
72.4k
    };
1016
72.4k
    double len0 = vec_len(deriv[0]), scale0 = 1 / len0;
1017
72.4k
    double len1 = vec_len(deriv[1]), scale1 = 1 / len1;
1018
72.4k
    Normal normal[2] = {
1019
72.4k
        { { deriv[0].x * scale0, deriv[0].y * scale0 }, len0 },
1020
72.4k
        { { deriv[1].x * scale1, deriv[1].y * scale1 }, len1 }
1021
72.4k
    };
1022
1023
72.4k
    bool first = str->contour_start;
1024
72.4k
    return start_segment(str, pt[0], normal[0].v, dir) &&
1025
72.4k
        process_quadratic(str, pt, deriv, normal, dir, first);
1026
73.0k
}
1027
1028
1029
enum {
1030
    FLAG_INTERSECTION =  1,
1031
    FLAG_ZERO_0       =  2,
1032
    FLAG_ZERO_1       =  4,
1033
    FLAG_CLIP_0       =  8,
1034
    FLAG_CLIP_1       = 16,
1035
    FLAG_DIR_2        = 32,
1036
1037
    FLAG_COUNT        =  6,
1038
1039
    MASK_INTERSECTION = FLAG_INTERSECTION << FLAG_COUNT,
1040
    MASK_ZERO_0       = FLAG_ZERO_0       << FLAG_COUNT,
1041
    MASK_ZERO_1       = FLAG_ZERO_1       << FLAG_COUNT,
1042
    MASK_CLIP_0       = FLAG_CLIP_0       << FLAG_COUNT,
1043
    MASK_CLIP_1       = FLAG_CLIP_1       << FLAG_COUNT,
1044
};
1045
1046
/**
1047
 * \brief Estimate error for cubic spline
1048
 * \param str stroker state
1049
 * \param c dot product between normal[0] and normal[1]
1050
 * \param s cross product between normal[0] and normal[1]
1051
 * \param dc dot products between normals and central points difference in normal space
1052
 * \param dc cross products between normals and central points difference in normal space
1053
 * \param normal first and last spline normal
1054
 * \param result best offsets for central spline points (second & third)
1055
 * \return flags for destination outlines that do not require subdivision
1056
 */
1057
static int estimate_cubic_error(StrokerState *str, double c, double s,
1058
                                const double *dc, const double *ds,
1059
                                const Normal *normal, ASS_DVector *result,
1060
                                int check_flags, int dir)
1061
0
{
1062
0
    double t = (ds[0] + ds[1]) / (dc[0] + dc[1]), c1 = 1 + c, ss = s * s;
1063
0
    double ts = t * s, tt = t * t, ttc = tt * c1, ttcc = ttc * c1;
1064
1065
0
    const double w = 0.4;
1066
0
    double f0[] = {
1067
0
        10 * w * (c - 1) + 9 * w * tt * c,
1068
0
        2 * (c - 1) + 3 * tt + 2 * ts,
1069
0
        2 * (c - 1) + 3 * tt - 2 * ts,
1070
0
    };
1071
0
    double f1[] = {
1072
0
        18 * w * (ss - ttc * c),
1073
0
        2 * ss - 6 * ttc - 2 * ts * (c + 4),
1074
0
        2 * ss - 6 * ttc + 2 * ts * (c + 4),
1075
0
    };
1076
0
    double f2[] = {
1077
0
        9 * w * (ttcc - ss) * c,
1078
0
        3 * ss + 3 * ttcc + 6 * ts * c1,
1079
0
        3 * ss + 3 * ttcc - 6 * ts * c1,
1080
0
    };
1081
1082
0
    double aa = 0, ab = 0;
1083
0
    double ch = sqrt(c1 / 2);
1084
0
    double inv_ro0 = 1.5 * ch * (ch + 1);
1085
0
    for (int i = 0; i < 3; i++) {
1086
0
        double a = 2 * f2[i] + f1[i] * inv_ro0;
1087
0
        double b = f2[i] - f0[i] * inv_ro0 * inv_ro0;
1088
0
        aa += a * a;  ab += a * b;
1089
0
    }
1090
0
    double ro = ab / (aa * inv_ro0 + 1e-9);  // best fit
1091
1092
0
    double err2 = 0;
1093
0
    for (int i = 0; i < 3; i++) {
1094
0
        double err = f0[i] + ro * (f1[i] + ro * f2[i]);
1095
0
        err2 += err * err;
1096
0
    }
1097
0
    if (!(err2 < str->err_c))  // check radial error
1098
0
        return 0;
1099
1100
0
    double r = ro * c1 - 1;
1101
0
    double ro0 = t * r - ro * s;
1102
0
    double ro1 = t * r + ro * s;
1103
1104
0
    int check_dir = check_flags & FLAG_DIR_2 ? 2 : 1;
1105
0
    if (dir & check_dir) {
1106
0
        double test_s = s, test0 = ro0, test1 = ro1;
1107
0
        if (check_flags & FLAG_DIR_2) {
1108
0
            test_s = -test_s;
1109
0
            test0 = -test0;
1110
0
            test1 = -test1;
1111
0
        }
1112
0
        int flags = 0;
1113
0
        if (2 * test_s * r < dc[0] + dc[1]) flags |= FLAG_INTERSECTION;
1114
0
        if (normal[0].len - test0 < 0) flags |= FLAG_ZERO_0;
1115
0
        if (normal[1].len + test1 < 0) flags |= FLAG_ZERO_1;
1116
0
        if (normal[0].len + dc[0] + test_s - test1 * c < 0) flags |= FLAG_CLIP_0;
1117
0
        if (normal[1].len + dc[1] + test_s + test0 * c < 0) flags |= FLAG_CLIP_1;
1118
0
        if ((flags ^ check_flags) & (check_flags >> FLAG_COUNT)) {
1119
0
            dir &= ~check_dir;
1120
0
            if (!dir)
1121
0
                return 0;
1122
0
        }
1123
0
    }
1124
1125
0
    double d0c = 2 * dc[0], d0s = 2 * ds[0];
1126
0
    double d1c = 2 * dc[1], d1s = 2 * ds[1];
1127
0
    double dot0 = d0c + 3 * normal[0].len, crs0 = d0s + 3 * ro0 * normal[0].len;
1128
0
    double dot1 = d1c + 3 * normal[1].len, crs1 = d1s + 3 * ro1 * normal[1].len;
1129
    // check angular error (stage 1)
1130
0
    if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1))
1131
0
        return 0;
1132
1133
0
    double cl0 = c * normal[0].len, sl0 = +s * normal[0].len;
1134
0
    double cl1 = c * normal[1].len, sl1 = -s * normal[1].len;
1135
0
    dot0 = d0c - ro0 * d0s + cl0 + ro1 * sl0 + cl1 / 3;
1136
0
    dot1 = d1c - ro1 * d1s + cl1 + ro0 * sl1 + cl0 / 3;
1137
0
    crs0 = d0s + ro0 * d0c - sl0 + ro1 * cl0 - sl1 / 3;
1138
0
    crs1 = d1s + ro1 * d1c - sl1 + ro0 * cl1 - sl0 / 3;
1139
    // check angular error (stage 2)
1140
0
    if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1))
1141
0
        return 0;
1142
1143
0
    result[0].x = normal[0].v.x + normal[0].v.y * ro0;
1144
0
    result[0].y = normal[0].v.y - normal[0].v.x * ro0;
1145
0
    result[1].x = normal[1].v.x + normal[1].v.y * ro1;
1146
0
    result[1].y = normal[1].v.y - normal[1].v.x * ro1;
1147
0
    return dir;
1148
0
}
1149
1150
/**
1151
 * \brief Helper function for cubic spline construction
1152
 * \param str stroker state
1153
 * \param pt array of 4 source spline points
1154
 * \param deriv array of 3 differences between adjacent points in normal space
1155
 * \param normal first and last spline normal
1156
 * \param dir destination outline flags
1157
 * \param first true if the current part is at start of the segment
1158
 * \return false on allocation failure
1159
 */
1160
static bool process_cubic(StrokerState *str, const ASS_Vector *pt,
1161
                          const ASS_DVector *deriv, const Normal *normal,
1162
                          int dir, bool first)
1163
0
{
1164
0
    double c = vec_dot(normal[0].v, normal[1].v);
1165
0
    double s = vec_crs(normal[0].v, normal[1].v);
1166
0
    double dc[] = { vec_dot(normal[0].v, deriv[1]), vec_dot(normal[1].v, deriv[1]) };
1167
0
    double ds[] = { vec_crs(normal[0].v, deriv[1]), vec_crs(normal[1].v, deriv[1]) };
1168
0
    double f0 = normal[0].len * c + normal[1].len + dc[1];
1169
0
    double f1 = normal[1].len * c + normal[0].len + dc[0];
1170
0
    double g0 = normal[0].len * s - ds[1];
1171
0
    double g1 = normal[1].len * s + ds[0];
1172
1173
0
    double abs_s = s;
1174
0
    int check_dir = dir, skip_dir = 2;
1175
0
    int flags = FLAG_INTERSECTION | FLAG_DIR_2;
1176
0
    if (s < 0) {
1177
0
        abs_s = -s;
1178
0
        skip_dir = 1;
1179
0
        flags = 0;
1180
0
        g0 = -g0;
1181
0
        g1 = -g1;
1182
0
    }
1183
1184
0
    if (!(dc[0] + dc[1] > 0))
1185
0
        check_dir = 0;
1186
0
    else if (dir & skip_dir) {
1187
0
        if (f0 < abs_s && f1 < abs_s) {  // check for self-intersection
1188
0
            double d2 = (f0 + dc[1]) * normal[1].len + (f1 + dc[0]) * normal[0].len;
1189
0
            d2 = (d2 + vec_dot(deriv[1], deriv[1])) / 2;
1190
0
            if (d2 < g0 && d2 < g1) {
1191
0
                double q = sqrt(d2 / (2 - d2));
1192
0
                double h0 = (f0 * q + g0) * normal[1].len;
1193
0
                double h1 = (f1 * q + g1) * normal[0].len;
1194
0
                q *= (4.0 / 3) * d2;
1195
0
                if (h0 > q && h1 > q) {
1196
0
                    if (!prepare_skip(str, pt[0], skip_dir, first))
1197
0
                        return false;
1198
0
                    if (f0 < 0 || f1 < 0) {
1199
0
                        ASS_DVector zero_normal = {0, 0};
1200
0
                        if (!emit_point(str, pt[0], zero_normal, OUTLINE_LINE_SEGMENT, skip_dir) ||
1201
0
                            !emit_point(str, pt[3], zero_normal, OUTLINE_LINE_SEGMENT, skip_dir))
1202
0
                            return false;
1203
0
                    } else {
1204
0
                        double mul = f0 / abs_s;
1205
0
                        ASS_DVector offs = { normal[0].v.x * mul, normal[0].v.y * mul };
1206
0
                        if (!emit_point(str, pt[0], offs, OUTLINE_LINE_SEGMENT, skip_dir))
1207
0
                            return false;
1208
0
                    }
1209
0
                    dir &= ~skip_dir;
1210
0
                    if (!dir) {
1211
0
                        str->last_normal = normal[1].v;
1212
0
                        return true;
1213
0
                    }
1214
0
                }
1215
0
            }
1216
0
            check_dir ^= skip_dir;
1217
0
        } else {
1218
0
            if (ds[0] < 0)
1219
0
                flags ^= MASK_INTERSECTION;
1220
0
            if (ds[1] < 0)
1221
0
                flags ^= MASK_INTERSECTION | FLAG_INTERSECTION;
1222
0
            bool parallel = flags & MASK_INTERSECTION;
1223
0
            int badness = parallel ? 0 : 1;
1224
0
            if (c + g0 < 1) {
1225
0
                if (parallel) {
1226
0
                    flags ^= MASK_ZERO_0 | FLAG_ZERO_0;
1227
0
                    if (c < 0)
1228
0
                        flags ^= MASK_CLIP_0;
1229
0
                    if (f0 > abs_s)
1230
0
                        flags ^= FLAG_ZERO_0 | FLAG_CLIP_0;
1231
0
                }
1232
0
                badness++;
1233
0
            } else {
1234
0
                flags ^= MASK_INTERSECTION | FLAG_INTERSECTION;
1235
0
                if (!parallel) {
1236
0
                    flags ^= MASK_ZERO_0;
1237
0
                    if (c > 0)
1238
0
                        flags ^= MASK_CLIP_0;
1239
0
                }
1240
0
            }
1241
0
            if (c + g1 < 1) {
1242
0
                if (parallel) {
1243
0
                    flags ^= MASK_ZERO_1 | FLAG_ZERO_1;
1244
0
                    if (c < 0)
1245
0
                        flags ^= MASK_CLIP_1;
1246
0
                    if (f1 > abs_s)
1247
0
                        flags ^= FLAG_ZERO_1 | FLAG_CLIP_1;
1248
0
                }
1249
0
                badness++;
1250
0
            } else {
1251
0
                flags ^= MASK_INTERSECTION;
1252
0
                if (!parallel) {
1253
0
                    flags ^= MASK_ZERO_1;
1254
0
                    if (c > 0)
1255
0
                        flags ^= MASK_CLIP_1;
1256
0
                }
1257
0
            }
1258
0
            if (badness > 2)
1259
0
                check_dir ^= skip_dir;
1260
0
        }
1261
0
    }
1262
1263
0
    ASS_DVector result[2];
1264
0
    if (check_dir)
1265
0
        check_dir = estimate_cubic_error(str, c, s, dc, ds,
1266
0
                                         normal, result, flags, check_dir);
1267
0
    if (check_dir) {
1268
0
        if (!emit_first_point(str, pt[0], OUTLINE_CUBIC_SPLINE, check_dir))
1269
0
            return false;
1270
0
        if (!emit_point(str, pt[1], result[0], 0, check_dir) ||
1271
0
            !emit_point(str, pt[2], result[1], 0, check_dir))
1272
0
            return false;
1273
0
        dir &= ~check_dir;
1274
0
        if (!dir) {
1275
0
            str->last_normal = normal[1].v;
1276
0
            return true;
1277
0
        }
1278
0
    }
1279
1280
0
    ASS_Vector next[7], center;
1281
0
    next[1].x = pt[0].x + pt[1].x;
1282
0
    next[1].y = pt[0].y + pt[1].y;
1283
0
    center.x = pt[1].x + pt[2].x + 2;
1284
0
    center.y = pt[1].y + pt[2].y + 2;
1285
0
    next[5].x = pt[2].x + pt[3].x;
1286
0
    next[5].y = pt[2].y + pt[3].y;
1287
0
    next[2].x = next[1].x + center.x;
1288
0
    next[2].y = next[1].y + center.y;
1289
0
    next[4].x = center.x + next[5].x;
1290
0
    next[4].y = center.y + next[5].y;
1291
0
    next[3].x = (next[2].x + next[4].x - 1) >> 3;
1292
0
    next[3].y = (next[2].y + next[4].y - 1) >> 3;
1293
0
    next[2].x >>= 2;
1294
0
    next[2].y >>= 2;
1295
0
    next[4].x >>= 2;
1296
0
    next[4].y >>= 2;
1297
0
    next[1].x >>= 1;
1298
0
    next[1].y >>= 1;
1299
0
    next[5].x >>= 1;
1300
0
    next[5].y >>= 1;
1301
0
    next[0] = pt[0];
1302
0
    next[6] = pt[3];
1303
1304
0
    ASS_DVector next_deriv[5], center_deriv;
1305
0
    next_deriv[0].x = deriv[0].x / 2;
1306
0
    next_deriv[0].y = deriv[0].y / 2;
1307
0
    center_deriv.x = deriv[1].x / 2;
1308
0
    center_deriv.y = deriv[1].y / 2;
1309
0
    next_deriv[4].x = deriv[2].x / 2;
1310
0
    next_deriv[4].y = deriv[2].y / 2;
1311
0
    next_deriv[1].x = (next_deriv[0].x + center_deriv.x) / 2;
1312
0
    next_deriv[1].y = (next_deriv[0].y + center_deriv.y) / 2;
1313
0
    next_deriv[3].x = (center_deriv.x + next_deriv[4].x) / 2;
1314
0
    next_deriv[3].y = (center_deriv.y + next_deriv[4].y) / 2;
1315
0
    next_deriv[2].x = (next_deriv[1].x + next_deriv[3].x) / 2;
1316
0
    next_deriv[2].y = (next_deriv[1].y + next_deriv[3].y) / 2;
1317
1318
0
    double len = vec_len(next_deriv[2]);
1319
0
    if (len < str->min_len) {  // check degenerate case
1320
0
        Normal next_normal[4];
1321
0
        next_normal[0].v = normal[0].v;
1322
0
        next_normal[0].len = normal[0].len / 2;
1323
0
        next_normal[3].v = normal[1].v;
1324
0
        next_normal[3].len = normal[1].len / 2;
1325
1326
0
        next_deriv[1].x += next_deriv[2].x;
1327
0
        next_deriv[1].y += next_deriv[2].y;
1328
0
        next_deriv[3].x += next_deriv[2].x;
1329
0
        next_deriv[3].y += next_deriv[2].y;
1330
0
        next_deriv[2].x = next_deriv[2].y = 0;
1331
1332
0
        double len1 = vec_len(next_deriv[1]);
1333
0
        if (len1 < str->min_len) {
1334
0
            next_normal[1] = normal[0];
1335
0
        } else {
1336
0
            double scale = 1 / len1;
1337
0
            next_normal[1].v.x = next_deriv[1].x * scale;
1338
0
            next_normal[1].v.y = next_deriv[1].y * scale;
1339
0
            next_normal[1].len = len1;
1340
0
        }
1341
1342
0
        double len2 = vec_len(next_deriv[3]);
1343
0
        if (len2 < str->min_len) {
1344
0
            next_normal[2] = normal[1];
1345
0
        } else {
1346
0
            double scale = 1 / len2;
1347
0
            next_normal[2].v.x = next_deriv[3].x * scale;
1348
0
            next_normal[2].v.y = next_deriv[3].y * scale;
1349
0
            next_normal[2].len = len2;
1350
0
        }
1351
1352
0
        if (len1 < str->min_len) {
1353
0
            if (!emit_first_point(str, next[0], OUTLINE_LINE_SEGMENT, dir))
1354
0
                return false;
1355
0
        } else {
1356
0
            if (!process_cubic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first))
1357
0
                return false;
1358
0
        }
1359
0
        if (!start_segment(str, next[2], next_normal[2].v, dir))
1360
0
            return false;
1361
0
        if (len2 < str->min_len) {
1362
0
            if (!emit_first_point(str, next[3], OUTLINE_LINE_SEGMENT, dir))
1363
0
                return false;
1364
0
        } else {
1365
0
            if (!process_cubic(str, next + 3, next_deriv + 2, next_normal + 2, dir, false))
1366
0
                return false;
1367
0
        }
1368
0
        return true;
1369
0
    }
1370
1371
0
    double scale = 1 / len;
1372
0
    Normal next_normal[3] = {
1373
0
        { normal[0].v, normal[0].len / 2 },
1374
0
        { { next_deriv[2].x * scale, next_deriv[2].y * scale }, len },
1375
0
        { normal[1].v, normal[1].len / 2 }
1376
0
    };
1377
0
    return process_cubic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first) &&
1378
0
           process_cubic(str, next + 3, next_deriv + 2, next_normal + 1, dir, false);
1379
0
}
1380
1381
/**
1382
 * \brief Process source cubic spline
1383
 * \param str stroker state
1384
 * \param pt1 first middle control point
1385
 * \param pt2 second middle control point
1386
 * \param pt3 final spline point
1387
 * \param dir destination outline flags
1388
 * \return false on allocation failure
1389
 */
1390
static bool add_cubic(StrokerState *str, ASS_Vector pt1, ASS_Vector pt2, ASS_Vector pt3, int dir)
1391
0
{
1392
0
    int flags = 9;
1393
1394
0
    int32_t dx0 = pt1.x - str->last_point.x;
1395
0
    int32_t dy0 = pt1.y - str->last_point.y;
1396
0
    if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps) {
1397
0
        dx0 = pt2.x - str->last_point.x;
1398
0
        dy0 = pt2.y - str->last_point.y;
1399
0
        if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps)
1400
0
            return add_line(str, pt3, dir);
1401
0
        flags ^= 1;
1402
0
    }
1403
1404
0
    int32_t dx2 = pt3.x - pt2.x;
1405
0
    int32_t dy2 = pt3.y - pt2.y;
1406
0
    if (dx2 > -str->eps && dx2 < str->eps && dy2 > -str->eps && dy2 < str->eps) {
1407
0
        dx2 = pt3.x - pt1.x;
1408
0
        dy2 = pt3.y - pt1.y;
1409
0
        if (dx2 > -str->eps && dx2 < str->eps && dy2 > -str->eps && dy2 < str->eps)
1410
0
            return add_line(str, pt3, dir);
1411
0
        flags ^= 4;
1412
0
    }
1413
1414
0
    if (flags == 12)
1415
0
        return add_line(str, pt3, dir);
1416
1417
0
    ASS_Vector pt[4] = { str->last_point, pt1, pt2, pt3 };
1418
0
    str->last_point = pt3;
1419
1420
0
    int32_t dx1 = pt[flags >> 2].x - pt[flags & 3].x;
1421
0
    int32_t dy1 = pt[flags >> 2].y - pt[flags & 3].y;
1422
1423
0
    ASS_DVector deriv[3] = {
1424
0
        { dy0 * str->yscale, -dx0 * str->xscale },
1425
0
        { dy1 * str->yscale, -dx1 * str->xscale },
1426
0
        { dy2 * str->yscale, -dx2 * str->xscale }
1427
0
    };
1428
0
    double len0 = vec_len(deriv[0]), scale0 = 1 / len0;
1429
0
    double len2 = vec_len(deriv[2]), scale2 = 1 / len2;
1430
0
    Normal normal[2] = {
1431
0
        { { deriv[0].x * scale0, deriv[0].y * scale0 }, len0 },
1432
0
        { { deriv[2].x * scale2, deriv[2].y * scale2 }, len2 }
1433
0
    };
1434
1435
0
    bool first = str->contour_start;
1436
0
    return start_segment(str, pt[0], normal[0].v, dir) &&
1437
0
        process_cubic(str, pt, deriv, normal, dir, first);
1438
0
}
1439
1440
1441
/**
1442
 * \brief Process contour closing
1443
 * \param str stroker state
1444
 * \param dir destination outline flags
1445
 * \return false on allocation failure
1446
 */
1447
static bool close_contour(StrokerState *str, int dir)
1448
11.4k
{
1449
11.4k
    if (str->contour_start) {
1450
3
        if ((dir & 3) == 3)
1451
3
            dir = 1;
1452
3
        if (!draw_circle(str, str->last_point, dir))
1453
0
            return false;
1454
11.4k
    } else {
1455
11.4k
        if (!add_line(str, str->first_point, dir))
1456
0
            return false;
1457
11.4k
        if (!start_segment(str, str->first_point, str->first_normal, dir))
1458
0
            return false;
1459
11.4k
        if (!emit_point(str, str->first_point, str->first_normal, OUTLINE_LINE_SEGMENT,
1460
11.4k
                       ~str->last_skip & dir & str->first_skip))
1461
0
            return false;
1462
11.4k
        if (str->last_normal.x != str->first_normal.x ||
1463
9.54k
            str->last_normal.y != str->first_normal.y)
1464
1.86k
            fix_first_point(str, str->first_point, str->last_normal,
1465
1.86k
                           ~str->last_skip & dir & ~str->first_skip);
1466
11.4k
        str->contour_start = true;
1467
11.4k
    }
1468
11.4k
    if (dir & 1)
1469
11.4k
        ass_outline_close_contour(str->result[0]);
1470
11.4k
    if (dir & 2)
1471
11.4k
        ass_outline_close_contour(str->result[1]);
1472
11.4k
    str->contour_first[0] = str->result[0]->n_points;
1473
11.4k
    str->contour_first[1] = str->result[1]->n_points;
1474
11.4k
    return true;
1475
11.4k
}
1476
1477
1478
/*
1479
 * Stroke an outline glyph in x/y direction.
1480
 * \param result first result outline
1481
 * \param result1 second result outline
1482
 * \param path source outline
1483
 * \param xbord border size in X direction
1484
 * \param ybord border size in Y direction
1485
 * \param eps approximate allowable error
1486
 * \return false on allocation failure
1487
 */
1488
bool ass_outline_stroke(ASS_Outline *result, ASS_Outline *result1,
1489
                        const ASS_Outline *path, int xbord, int ybord, int eps)
1490
8.02k
{
1491
8.02k
    ass_outline_alloc(result,  2 * path->n_points, 2 * path->n_segments);
1492
8.02k
    ass_outline_alloc(result1, 2 * path->n_points, 2 * path->n_segments);
1493
8.02k
    if (!result->max_points || !result1->max_points)
1494
0
        return false;
1495
1496
8.02k
    const int dir = 3;
1497
8.02k
    int rad = FFMAX(xbord, ybord);
1498
8.02k
    assert(rad >= eps && rad <= OUTLINE_MAX);
1499
1500
8.02k
    StrokerState str;
1501
8.02k
    str.result[0] = result;
1502
8.02k
    str.result[1] = result1;
1503
8.02k
    str.contour_first[0] = 0;
1504
8.02k
    str.contour_first[1] = 0;
1505
8.02k
    str.xbord = xbord;
1506
8.02k
    str.ybord = ybord;
1507
8.02k
    str.xscale = 1.0 / FFMAX(eps, xbord);
1508
8.02k
    str.yscale = 1.0 / FFMAX(eps, ybord);
1509
8.02k
    str.eps = eps;
1510
1511
8.02k
    str.contour_start = true;
1512
8.02k
    double rel_err = (double) eps / rad;
1513
8.02k
    str.merge_cos = 1 - rel_err;
1514
8.02k
    double e = sqrt(2 * rel_err);
1515
8.02k
    str.split_cos = 1 + 8 * rel_err - 4 * (1 + rel_err) * e;
1516
8.02k
    str.min_len = rel_err / 4;
1517
8.02k
    str.err_q = 8 * (1 + rel_err) * (1 + rel_err);
1518
8.02k
    str.err_c = 390 * rel_err * rel_err;
1519
8.02k
    str.err_a = e;
1520
1521
8.02k
#ifndef NDEBUG
1522
224k
    for (size_t i = 0; i < path->n_points; i++)
1523
216k
        assert(abs(path->points[i].x) <= OUTLINE_MAX && abs(path->points[i].y) <= OUTLINE_MAX);
1524
8.02k
#endif
1525
1526
8.02k
    ASS_Vector *start = path->points, *cur = start;
1527
151k
    for (size_t i = 0; i < path->n_segments; i++) {
1528
143k
        if (start == cur)
1529
11.4k
            str.last_point = *start;
1530
1531
143k
        int n = path->segments[i] & OUTLINE_COUNT_MASK;
1532
143k
        cur += n;
1533
1534
143k
        ASS_Vector *end = cur;
1535
143k
        if (path->segments[i] & OUTLINE_CONTOUR_END) {
1536
11.4k
            end = start;
1537
11.4k
            start = cur;
1538
11.4k
        }
1539
1540
143k
        switch (n) {
1541
69.4k
        case OUTLINE_LINE_SEGMENT:
1542
69.4k
            if (!add_line(&str, *end, dir))
1543
0
                return false;
1544
69.4k
            break;
1545
1546
73.7k
        case OUTLINE_QUADRATIC_SPLINE:
1547
73.7k
            if (!add_quadratic(&str, cur[-1], *end, dir))
1548
0
                return false;
1549
73.7k
            break;
1550
1551
73.7k
        case OUTLINE_CUBIC_SPLINE:
1552
0
            if (!add_cubic(&str, cur[-2], cur[-1], *end, dir))
1553
0
                return false;
1554
0
            break;
1555
1556
0
        default:
1557
0
            return false;
1558
143k
        }
1559
1560
143k
        if (start == cur && !close_contour(&str, dir))
1561
0
            return false;
1562
143k
    }
1563
8.02k
    assert(start == cur && cur == path->points + path->n_points);
1564
8.02k
    return true;
1565
8.02k
}