Coverage Report

Created: 2026-01-25 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libass/libass/ass_render.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@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 <assert.h>
23
#include <math.h>
24
#include <string.h>
25
#include <stdbool.h>
26
27
#ifdef CONFIG_UNIBREAK
28
#include <linebreak.h>
29
#endif
30
31
#include "ass.h"
32
#include "ass_outline.h"
33
#include "ass_render.h"
34
#include "ass_parse.h"
35
#include "ass_priv.h"
36
#include "ass_shaper.h"
37
38
47.7k
#define MAX_GLYPHS_INITIAL 1024
39
23.8k
#define MAX_LINES_INITIAL 64
40
23.8k
#define MAX_BITMAPS_INITIAL 16
41
167k
#define MAX_SUB_BITMAPS_INITIAL 64
42
4.85k
#define SUBPIXEL_MASK 63
43
1.49M
#define STROKER_PRECISION 16     // stroker error in integer units, unrelated to final accuracy
44
11.9k
#define RASTERIZER_PRECISION 16  // rasterizer spline approximation error in 1/64 pixel units
45
2.86M
#define POSITION_PRECISION 8.0   // rough estimate of transform error in 1/64 pixel units
46
#define MAX_PERSP_SCALE 16.0
47
5.96M
#define SUBPIXEL_ORDER 3  // ~ log2(64 / POSITION_PRECISION)
48
345k
#define BLUR_PRECISION (1.0 / 256)  // blur error as fraction of full input range
49
50
51
static bool text_info_init(TextInfo* text_info)
52
11.9k
{
53
11.9k
    text_info->max_bitmaps = MAX_BITMAPS_INITIAL;
54
11.9k
    text_info->max_glyphs = MAX_GLYPHS_INITIAL;
55
11.9k
    text_info->max_lines = MAX_LINES_INITIAL;
56
11.9k
    text_info->n_bitmaps = 0;
57
11.9k
    text_info->combined_bitmaps = calloc(MAX_BITMAPS_INITIAL, sizeof(CombinedBitmapInfo));
58
11.9k
    text_info->glyphs = calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo));
59
11.9k
    text_info->event_text = calloc(MAX_GLYPHS_INITIAL, sizeof(FriBidiChar));
60
11.9k
    text_info->breaks = malloc(MAX_GLYPHS_INITIAL);
61
11.9k
    text_info->lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo));
62
63
11.9k
    if (!text_info->combined_bitmaps || !text_info->glyphs || !text_info->lines ||
64
11.9k
        !text_info->breaks || !text_info->event_text)
65
0
        return false;
66
67
11.9k
    return true;
68
11.9k
}
69
70
static void text_info_done(TextInfo* text_info)
71
11.9k
{
72
11.9k
    free(text_info->glyphs);
73
11.9k
    free(text_info->event_text);
74
11.9k
    free(text_info->breaks);
75
11.9k
    free(text_info->lines);
76
11.9k
    free(text_info->combined_bitmaps);
77
11.9k
}
78
79
static bool render_context_init(RenderContext *state, ASS_Renderer *priv)
80
11.9k
{
81
11.9k
    state->renderer = priv;
82
83
11.9k
    if (!text_info_init(&state->text_info))
84
0
        return false;
85
86
11.9k
    if (!(state->shaper = ass_shaper_new(priv->cache.metrics_cache, priv->cache.face_size_metrics_cache)))
87
0
        return false;
88
89
11.9k
    return ass_rasterizer_init(&priv->engine, &state->rasterizer, RASTERIZER_PRECISION);
90
11.9k
}
91
92
static void render_context_done(RenderContext *state)
93
11.9k
{
94
11.9k
    ass_rasterizer_done(&state->rasterizer);
95
96
11.9k
    if (state->shaper)
97
11.9k
        ass_shaper_free(state->shaper);
98
99
11.9k
    text_info_done(&state->text_info);
100
11.9k
}
101
102
ASS_Renderer *ass_renderer_init(ASS_Library *library)
103
11.9k
{
104
11.9k
    int error;
105
11.9k
    FT_Library ft;
106
11.9k
    ASS_Renderer *priv = 0;
107
11.9k
    int vmajor, vminor, vpatch;
108
109
11.9k
    ass_msg(library, MSGL_INFO, "libass API version: 0x%X", LIBASS_VERSION);
110
11.9k
    ass_msg(library, MSGL_INFO, "libass source: %s", CONFIG_SOURCEVERSION);
111
112
11.9k
    error = FT_Init_FreeType(&ft);
113
11.9k
    if (error) {
114
0
        ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType");
115
0
        goto fail;
116
0
    }
117
118
11.9k
    FT_Library_Version(ft, &vmajor, &vminor, &vpatch);
119
11.9k
    ass_msg(library, MSGL_V, "Raster: FreeType %d.%d.%d",
120
11.9k
           vmajor, vminor, vpatch);
121
122
11.9k
    priv = calloc(1, sizeof(ASS_Renderer));
123
11.9k
    if (!priv) {
124
0
        FT_Done_FreeType(ft);
125
0
        goto fail;
126
0
    }
127
128
11.9k
    priv->library = library;
129
11.9k
    priv->ftlibrary = ft;
130
    // images_root and related stuff is zero-filled in calloc
131
132
11.9k
    unsigned flags = ASS_CPU_FLAG_ALL;
133
#if CONFIG_LARGE_TILES
134
    flags |= ASS_FLAG_LARGE_TILES;
135
#endif
136
11.9k
    priv->engine = ass_bitmap_engine_init(flags);
137
138
11.9k
    priv->cache.font_cache = ass_font_cache_create();
139
11.9k
    priv->cache.bitmap_cache = ass_bitmap_cache_create();
140
11.9k
    priv->cache.composite_cache = ass_composite_cache_create();
141
11.9k
    priv->cache.outline_cache = ass_outline_cache_create();
142
11.9k
    priv->cache.face_size_metrics_cache = ass_face_size_metrics_cache_create();
143
11.9k
    priv->cache.metrics_cache = ass_glyph_metrics_cache_create();
144
11.9k
    if (!priv->cache.font_cache || !priv->cache.bitmap_cache ||
145
11.9k
        !priv->cache.composite_cache || !priv->cache.outline_cache ||
146
11.9k
        !priv->cache.face_size_metrics_cache || !priv->cache.metrics_cache)
147
0
        goto fail;
148
149
11.9k
    priv->cache.glyph_max = GLYPH_CACHE_MAX;
150
11.9k
    priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE;
151
11.9k
    priv->cache.composite_max_size = COMPOSITE_CACHE_MAX_SIZE;
152
153
11.9k
    if (!render_context_init(&priv->state, priv))
154
0
        goto fail;
155
156
11.9k
    priv->user_override_style.Name = "OverrideStyle"; // name insignificant
157
158
11.9k
    priv->settings.font_size_coeff = 1.;
159
11.9k
    priv->settings.selective_style_overrides = ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE;
160
161
11.9k
    ass_shaper_info(library);
162
11.9k
    priv->settings.shaper = ASS_SHAPING_COMPLEX;
163
164
11.9k
    ass_msg(library, MSGL_V, "Initialized");
165
166
11.9k
    return priv;
167
168
0
fail:
169
0
    ass_msg(library, MSGL_ERR, "Initialization failed");
170
0
    ass_renderer_done(priv);
171
172
0
    return NULL;
173
11.9k
}
174
175
void ass_renderer_done(ASS_Renderer *render_priv)
176
11.9k
{
177
11.9k
    if (!render_priv)
178
0
        return;
179
180
11.9k
    ass_frame_unref(render_priv->images_root);
181
11.9k
    ass_frame_unref(render_priv->prev_images_root);
182
183
11.9k
    ass_cache_done(render_priv->cache.composite_cache);
184
11.9k
    ass_cache_done(render_priv->cache.bitmap_cache);
185
11.9k
    ass_cache_done(render_priv->cache.outline_cache);
186
11.9k
    ass_cache_done(render_priv->cache.face_size_metrics_cache);
187
11.9k
    ass_cache_done(render_priv->cache.metrics_cache);
188
11.9k
    ass_cache_done(render_priv->cache.font_cache);
189
190
11.9k
    if (render_priv->fontselect)
191
11.9k
        ass_fontselect_free(render_priv->fontselect);
192
11.9k
    if (render_priv->ftlibrary)
193
11.9k
        FT_Done_FreeType(render_priv->ftlibrary);
194
11.9k
    free(render_priv->eimg);
195
196
11.9k
    render_context_done(&render_priv->state);
197
198
11.9k
    free(render_priv->settings.default_font);
199
11.9k
    free(render_priv->settings.default_family);
200
201
11.9k
    free(render_priv->user_override_style.FontName);
202
203
11.9k
    free(render_priv);
204
11.9k
}
205
206
/**
207
 * \brief Create a new ASS_Image
208
 * Parameters are the same as ASS_Image fields.
209
 */
210
static ASS_Image *my_draw_bitmap(unsigned char *bitmap, int bitmap_w,
211
                                 int bitmap_h, int stride, int dst_x,
212
                                 int dst_y, uint32_t color,
213
                                 CompositeHashValue *source)
214
227k
{
215
227k
    ASS_ImagePriv *img = malloc(sizeof(ASS_ImagePriv));
216
227k
    if (!img) {
217
0
        if (!source)
218
0
            ass_aligned_free(bitmap);
219
0
        return NULL;
220
0
    }
221
222
227k
    img->result.w = bitmap_w;
223
227k
    img->result.h = bitmap_h;
224
227k
    img->result.stride = stride;
225
227k
    img->result.bitmap = bitmap;
226
227k
    img->result.color = color;
227
227k
    img->result.dst_x = dst_x;
228
227k
    img->result.dst_y = dst_y;
229
230
227k
    img->source = source;
231
227k
    ass_cache_inc_ref(source);
232
227k
    img->buffer = source ? NULL : bitmap;
233
227k
    img->ref_count = 0;
234
235
227k
    return &img->result;
236
227k
}
237
238
/**
239
 * \brief Mapping between script and screen coordinates
240
 */
241
static double x2scr_pos(ASS_Renderer *render_priv, double x)
242
258k
{
243
258k
    return x * render_priv->frame_content_width / render_priv->par_scale_x / render_priv->track->PlayResX +
244
258k
        render_priv->settings.left_margin;
245
258k
}
246
static double x2scr_left(RenderContext *state, double x)
247
169k
{
248
169k
    ASS_Renderer *render_priv = state->renderer;
249
169k
    if (state->explicit || !render_priv->settings.use_margins)
250
169k
        return x2scr_pos(render_priv, x);
251
0
    return x * render_priv->fit_width / render_priv->par_scale_x /
252
0
        render_priv->track->PlayResX;
253
169k
}
254
static double x2scr_right(RenderContext *state, double x)
255
85.9k
{
256
85.9k
    ASS_Renderer *render_priv = state->renderer;
257
85.9k
    if (state->explicit || !render_priv->settings.use_margins)
258
85.9k
        return x2scr_pos(render_priv, x);
259
0
    return x * render_priv->fit_width / render_priv->par_scale_x /
260
0
        render_priv->track->PlayResX +
261
0
        (render_priv->width - render_priv->fit_width);
262
85.9k
}
263
static double x2scr_pos_scaled(ASS_Renderer *render_priv, double x)
264
172k
{
265
172k
    return x * render_priv->frame_content_width / render_priv->track->PlayResX +
266
172k
        render_priv->settings.left_margin;
267
172k
}
268
/**
269
 * \brief Mapping between script and screen coordinates
270
 */
271
static double y2scr_pos(ASS_Renderer *render_priv, double y)
272
341k
{
273
341k
    return y * render_priv->frame_content_height / render_priv->track->PlayResY +
274
341k
        render_priv->settings.top_margin;
275
341k
}
276
static double y2scr(RenderContext *state, double y)
277
710
{
278
710
    ASS_Renderer *render_priv = state->renderer;
279
710
    if (state->explicit || !render_priv->settings.use_margins)
280
710
        return y2scr_pos(render_priv, y);
281
0
    return y * render_priv->fit_height /
282
0
        render_priv->track->PlayResY +
283
0
        (render_priv->height - render_priv->fit_height) * 0.5;
284
710
}
285
286
// the same for toptitles
287
static double y2scr_top(RenderContext *state, double y)
288
83.5k
{
289
83.5k
    ASS_Renderer *render_priv = state->renderer;
290
83.5k
    if (state->explicit || !render_priv->settings.use_margins)
291
83.5k
        return y2scr_pos(render_priv, y);
292
0
    return y * render_priv->fit_height /
293
0
        render_priv->track->PlayResY;
294
83.5k
}
295
// the same for subtitles
296
static double y2scr_sub(RenderContext *state, double y)
297
83.1k
{
298
83.1k
    ASS_Renderer *render_priv = state->renderer;
299
83.1k
    if (state->explicit || !render_priv->settings.use_margins)
300
83.1k
        return y2scr_pos(render_priv, y);
301
0
    return y * render_priv->fit_height /
302
0
        render_priv->track->PlayResY +
303
0
        (render_priv->height - render_priv->fit_height);
304
83.1k
}
305
306
/*
307
 * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping
308
 *
309
 * Inverse clipping with the following strategy:
310
 * - find rectangle from (x0, y0) to (cx0, y1)
311
 * - find rectangle from (cx0, y0) to (cx1, cy0)
312
 * - find rectangle from (cx0, cy1) to (cx1, y1)
313
 * - find rectangle from (cx1, y0) to (x1, y1)
314
 * These rectangles can be invalid and in this case are discarded.
315
 * Afterwards, they are clipped against the screen coordinates.
316
 * In an additional pass, the rectangles need to be split up left/right for
317
 * karaoke effects.  This can result in a lot of bitmaps (6 to be exact).
318
 */
319
static ASS_Image **render_glyph_i(RenderContext *state,
320
                                  Bitmap *bm, int dst_x, int dst_y,
321
                                  uint32_t color, uint32_t color2, int brk,
322
                                  ASS_Image **tail, unsigned type,
323
                                  CompositeHashValue *source)
324
63
{
325
63
    ASS_Renderer *render_priv = state->renderer;
326
63
    int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
327
63
    Rect r[4];
328
63
    ASS_Image *img;
329
330
63
    dst_x += bm->left;
331
63
    dst_y += bm->top;
332
63
    brk -= dst_x;
333
334
    // we still need to clip against screen boundaries
335
63
    zx = x2scr_pos_scaled(render_priv, 0);
336
63
    zy = y2scr_pos(render_priv, 0);
337
63
    sx = x2scr_pos_scaled(render_priv, render_priv->track->PlayResX);
338
63
    sy = y2scr_pos(render_priv, render_priv->track->PlayResY);
339
340
63
    x0 = 0;
341
63
    y0 = 0;
342
63
    x1 = bm->w;
343
63
    y1 = bm->h;
344
63
    cx0 = state->clip_x0 - dst_x;
345
63
    cy0 = state->clip_y0 - dst_y;
346
63
    cx1 = state->clip_x1 - dst_x;
347
63
    cy1 = state->clip_y1 - dst_y;
348
349
    // calculate rectangles and discard invalid ones while we're at it.
350
63
    i = 0;
351
63
    r[i].x0 = x0;
352
63
    r[i].y0 = y0;
353
63
    r[i].x1 = (cx0 > x1) ? x1 : cx0;
354
63
    r[i].y1 = y1;
355
63
    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
356
63
    r[i].x0 = (cx0 < 0) ? x0 : cx0;
357
63
    r[i].y0 = y0;
358
63
    r[i].x1 = (cx1 > x1) ? x1 : cx1;
359
63
    r[i].y1 = (cy0 > y1) ? y1 : cy0;
360
63
    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
361
63
    r[i].x0 = (cx0 < 0) ? x0 : cx0;
362
63
    r[i].y0 = (cy1 < 0) ? y0 : cy1;
363
63
    r[i].x1 = (cx1 > x1) ? x1 : cx1;
364
63
    r[i].y1 = y1;
365
63
    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
366
63
    r[i].x0 = (cx1 < 0) ? x0 : cx1;
367
63
    r[i].y0 = y0;
368
63
    r[i].x1 = x1;
369
63
    r[i].y1 = y1;
370
63
    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
371
372
    // clip each rectangle to screen coordinates
373
126
    for (j = 0; j < i; j++) {
374
63
        r[j].x0 = (r[j].x0 + dst_x < zx) ? zx - dst_x : r[j].x0;
375
63
        r[j].y0 = (r[j].y0 + dst_y < zy) ? zy - dst_y : r[j].y0;
376
63
        r[j].x1 = (r[j].x1 + dst_x > sx) ? sx - dst_x : r[j].x1;
377
63
        r[j].y1 = (r[j].y1 + dst_y > sy) ? sy - dst_y : r[j].y1;
378
63
    }
379
380
    // draw the rectangles
381
126
    for (j = 0; j < i; j++) {
382
63
        int lbrk = brk;
383
        // kick out rectangles that are invalid now
384
63
        if (r[j].x1 <= r[j].x0 || r[j].y1 <= r[j].y0)
385
0
            continue;
386
        // split up into left and right for karaoke, if needed
387
63
        if (lbrk > r[j].x0) {
388
63
            if (lbrk > r[j].x1) lbrk = r[j].x1;
389
63
            img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + r[j].x0,
390
63
                                 lbrk - r[j].x0, r[j].y1 - r[j].y0, bm->stride,
391
63
                                 dst_x + r[j].x0, dst_y + r[j].y0, color, source);
392
63
            if (!img) break;
393
63
            img->type = type;
394
63
            *tail = img;
395
63
            tail = &img->next;
396
63
        }
397
63
        if (lbrk < r[j].x1) {
398
0
            if (lbrk < r[j].x0) lbrk = r[j].x0;
399
0
            img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + lbrk,
400
0
                                 r[j].x1 - lbrk, r[j].y1 - r[j].y0, bm->stride,
401
0
                                 dst_x + lbrk, dst_y + r[j].y0, color2, source);
402
0
            if (!img) break;
403
0
            img->type = type;
404
0
            *tail = img;
405
0
            tail = &img->next;
406
0
        }
407
63
    }
408
409
63
    return tail;
410
63
}
411
412
/**
413
 * \brief convert bitmap glyph into ASS_Image struct(s)
414
 * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY
415
 * \param dst_x bitmap x coordinate in video frame
416
 * \param dst_y bitmap y coordinate in video frame
417
 * \param color first color, RGBA
418
 * \param color2 second color, RGBA
419
 * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right
420
 * \param tail pointer to the last image's next field, head of the generated list should be stored here
421
 * \return pointer to the new list tail
422
 * Performs clipping. Uses my_draw_bitmap for actual bitmap conversion.
423
 */
424
static ASS_Image **
425
render_glyph(RenderContext *state, Bitmap *bm, int dst_x, int dst_y,
426
             uint32_t color, uint32_t color2, int brk, ASS_Image **tail,
427
             unsigned type, CompositeHashValue *source)
428
233k
{
429
    // Inverse clipping in use?
430
233k
    if (state->clip_mode)
431
63
        return render_glyph_i(state, bm, dst_x, dst_y, color, color2,
432
63
                              brk, tail, type, source);
433
434
    // brk is absolute
435
    // color = color left of brk
436
    // color2 = color right of brk
437
233k
    int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap
438
233k
    int clip_x0, clip_y0, clip_x1, clip_y1;
439
233k
    int tmp;
440
233k
    ASS_Image *img;
441
233k
    ASS_Renderer *render_priv = state->renderer;
442
443
233k
    dst_x += bm->left;
444
233k
    dst_y += bm->top;
445
233k
    brk -= dst_x;
446
447
    // clipping
448
233k
    clip_x0 = FFMINMAX(state->clip_x0, 0, render_priv->width);
449
233k
    clip_y0 = FFMINMAX(state->clip_y0, 0, render_priv->height);
450
233k
    clip_x1 = FFMINMAX(state->clip_x1, 0, render_priv->width);
451
233k
    clip_y1 = FFMINMAX(state->clip_y1, 0, render_priv->height);
452
233k
    b_x0 = 0;
453
233k
    b_y0 = 0;
454
233k
    b_x1 = bm->w;
455
233k
    b_y1 = bm->h;
456
457
233k
    tmp = dst_x - clip_x0;
458
233k
    if (tmp < 0)
459
4.95k
        b_x0 = -tmp;
460
233k
    tmp = dst_y - clip_y0;
461
233k
    if (tmp < 0)
462
4.40k
        b_y0 = -tmp;
463
233k
    tmp = clip_x1 - dst_x - bm->w;
464
233k
    if (tmp < 0)
465
2.36k
        b_x1 = bm->w + tmp;
466
233k
    tmp = clip_y1 - dst_y - bm->h;
467
233k
    if (tmp < 0)
468
2.61k
        b_y1 = bm->h + tmp;
469
470
233k
    if ((b_y0 >= b_y1) || (b_x0 >= b_x1))
471
6.22k
        return tail;
472
473
227k
    if (brk > b_x0) {           // draw left part
474
226k
        if (brk > b_x1)
475
226k
            brk = b_x1;
476
226k
        img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + b_x0,
477
226k
                             brk - b_x0, b_y1 - b_y0, bm->stride,
478
226k
                             dst_x + b_x0, dst_y + b_y0, color, source);
479
226k
        if (!img) return tail;
480
226k
        img->type = type;
481
226k
        *tail = img;
482
226k
        tail = &img->next;
483
226k
    }
484
227k
    if (brk < b_x1) {           // draw right part
485
422
        if (brk < b_x0)
486
188
            brk = b_x0;
487
422
        img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + brk,
488
422
                             b_x1 - brk, b_y1 - b_y0, bm->stride,
489
422
                             dst_x + brk, dst_y + b_y0, color2, source);
490
422
        if (!img) return tail;
491
422
        img->type = type;
492
422
        *tail = img;
493
422
        tail = &img->next;
494
422
    }
495
227k
    return tail;
496
227k
}
497
498
static bool quantize_transform(double m[3][3], ASS_Vector *pos,
499
                               ASS_DVector *offset, bool first,
500
                               BitmapHashKey *key)
501
989k
{
502
    // Full transform:
503
    // x_out = (m_xx * x + m_xy * y + m_xz) / z,
504
    // y_out = (m_yx * x + m_yy * y + m_yz) / z,
505
    // z     =  m_zx * x + m_zy * y + m_zz.
506
507
989k
    const double max_val = 1000000;
508
509
989k
    const ASS_Rect *bbox = &key->outline->cbox;
510
989k
    double x0 = (bbox->x_min + bbox->x_max) / 2.0;
511
989k
    double y0 = (bbox->y_min + bbox->y_max) / 2.0;
512
989k
    double dx = (bbox->x_max - bbox->x_min) / 2.0 + 64;
513
989k
    double dy = (bbox->y_max - bbox->y_min) / 2.0 + 64;
514
515
    // Change input coordinates' origin to (x0, y0),
516
    // after that transformation x:[-dx, dx], y:[-dy, dy],
517
    // max|x| = dx and max|y| = dy.
518
3.95M
    for (int i = 0; i < 3; i++)
519
2.96M
        m[i][2] += m[i][0] * x0 + m[i][1] * y0;
520
521
989k
    if (m[2][2] <= 0)
522
0
        return false;
523
524
989k
    double w = 1 / m[2][2];
525
    // Transformed center of bounding box
526
989k
    double center[2] = { m[0][2] * w, m[1][2] * w };
527
    // Change output coordinates' origin to center,
528
    // m_xz and m_yz is skipped as it becomes 0 and no longer needed.
529
2.96M
    for (int i = 0; i < 2; i++)
530
5.93M
        for (int j = 0; j < 2; j++)
531
3.95M
            m[i][j] -= m[2][j] * center[i];
532
533
989k
    double delta[2] = {0};
534
989k
    if (!first) {
535
904k
        delta[0] = offset->x;
536
904k
        delta[1] = offset->y;
537
904k
    }
538
539
989k
    int32_t qr[2];  // quantized center position
540
2.96M
    for (int i = 0; i < 2; i++) {
541
1.97M
        center[i] /= 64 >> SUBPIXEL_ORDER;
542
1.97M
        center[i] -= delta[i];
543
1.97M
        if (!(fabs(center[i]) < max_val))
544
1.65k
            return false;
545
1.97M
        qr[i] = ass_lrint(center[i]);
546
1.97M
    }
547
548
    // Minimal bounding box z coordinate
549
987k
    double z0 = m[2][2] - fabs(m[2][0]) * dx - fabs(m[2][1]) * dy;
550
    // z0 clamped to z_center / MAX_PERSP_SCALE to mitigate problems with small z
551
987k
    w = 1.0 / POSITION_PRECISION / FFMAX(z0, m[2][2] / MAX_PERSP_SCALE);
552
987k
    double mul[2] = { dx * w, dy * w };  // 1 / q_x, 1 / q_y
553
554
    // z0 = m_zz - |m_zx| * dx - |m_zy| * dy,
555
    // m_zz = z0 + |m_zx| * dx + |m_zy| * dy,
556
    // z = m_zx * x + m_zy * y + m_zz
557
    //  = m_zx * (x + sign(m_zx) * dx) + m_zy * (y + sign(m_zy) * dy) + z0.
558
559
    // Let D(f) denote the absolute error of a quantity f.
560
    // Our goal is to determine tolerable error for matrix coefficients,
561
    // so that the total error of the output x_out, y_out is still acceptable.
562
    // As glyph dimensions are usually larger than a couple of pixels, errors
563
    // will be relatively small and we can use first order approximation.
564
565
    // z0 is effectively a scale factor and can thus be treated as a constant.
566
    // Error of constants is obviously zero, so:  D(dx) = D(dy) = D(z0) = 0.
567
    // For arbitrary quantities A, B, C with C not zero, the following holds true:
568
    //   D(A * B) <= D(A) * max|B| + max|A| * D(B),
569
    //   D(1 / C) <= D(C) * max|1 / C^2|.
570
    // Write ~ for 'same magnitude' and ~= for 'approximately'.
571
572
    // D(x_out) = D((m_xx * x + m_xy * y) / z)
573
    //  <= D(m_xx * x + m_xy * y) * max|1 / z| + max|m_xx * x + m_xy * y| * D(1 / z)
574
    //  <= (D(m_xx) * dx + D(m_xy) * dy) / z0 + (|m_xx| * dx + |m_xy| * dy) * D(z) / z0^2,
575
    // D(y_out) = D((m_yx * x + m_yy * y) / z)
576
    //  <= D(m_yx * x + m_yy * y) * max|1 / z| + max|m_yx * x + m_yy * y| * D(1 / z)
577
    //  <= (D(m_yx) * dx + D(m_yy) * dy) / z0 + (|m_yx| * dx + |m_yy| * dy) * D(z) / z0^2,
578
    // |m_xx| * dx + |m_xy| * dy = x_lim,
579
    // |m_yx| * dx + |m_yy| * dy = y_lim,
580
    // D(z) <= 2 * (D(m_zx) * dx + D(m_zy) * dy),
581
    // D(x_out) <= (D(m_xx) * dx + D(m_xy) * dy) / z0
582
    //       + 2 * (D(m_zx) * dx + D(m_zy) * dy) * x_lim / z0^2,
583
    // D(y_out) <= (D(m_yx) * dx + D(m_yy) * dy) / z0
584
    //       + 2 * (D(m_zx) * dx + D(m_zy) * dy) * y_lim / z0^2.
585
586
    // To estimate acceptable error in a matrix coefficient, pick ACCURACY for this substep,
587
    // set error in all other coefficients to zero and solve the system
588
    // D(x_out) <= ACCURACY, D(y_out) <= ACCURACY for desired D(m_ij).
589
    // Note that ACCURACY isn't equal to total error.
590
    // Total error is larger than each ACCURACY, but still of the same magnitude.
591
    // Via our choice of ACCURACY, we get a total error of up to several POSITION_PRECISION.
592
593
    // Quantization steps (pick: ACCURACY = POSITION_PRECISION):
594
    // D(m_xx), D(m_yx) ~ q_x = POSITION_PRECISION * z0 / dx,
595
    // D(m_xy), D(m_yy) ~ q_y = POSITION_PRECISION * z0 / dy,
596
    // qm_xx = round(m_xx / q_x), qm_xy = round(m_xy / q_y),
597
    // qm_yx = round(m_yx / q_x), qm_yy = round(m_yy / q_y).
598
599
987k
    int32_t qm[3][2];
600
2.96M
    for (int i = 0; i < 2; i++)
601
5.92M
        for (int j = 0; j < 2; j++) {
602
3.95M
            double val = m[i][j] * mul[j];
603
3.95M
            if (!(fabs(val) < max_val))
604
0
                return false;
605
3.95M
            qm[i][j] = ass_lrint(val);
606
3.95M
        }
607
608
    // x_lim = |m_xx| * dx + |m_xy| * dy
609
    //  ~= |qm_xx| * q_x * dx + |qm_xy| * q_y * dy
610
    //  = (|qm_xx| + |qm_xy|) * POSITION_PRECISION * z0,
611
    // y_lim = |m_yx| * dx + |m_yy| * dy
612
    //  ~= |qm_yx| * q_x * dx + |qm_yy| * q_y * dy
613
    //  = (|qm_yx| + |qm_yy|) * POSITION_PRECISION * z0,
614
    // max(x_lim, y_lim) / z0 ~= w
615
    //  = max(|qm_xx| + |qm_xy|, |qm_yx| + |qm_yy|) * POSITION_PRECISION.
616
617
    // Quantization steps (pick: ACCURACY = 2 * POSITION_PRECISION):
618
    // D(m_zx) ~ POSITION_PRECISION * z0^2 / max(x_lim, y_lim) / dx ~= q_zx = q_x / w,
619
    // D(m_zy) ~ POSITION_PRECISION * z0^2 / max(x_lim, y_lim) / dy ~= q_zy = q_y / w,
620
    // qm_zx = round(m_zx / q_zx), qm_zy = round(m_zy / q_zy).
621
622
987k
    int32_t qmx = abs(qm[0][0]) + abs(qm[0][1]);
623
987k
    int32_t qmy = abs(qm[1][0]) + abs(qm[1][1]);
624
987k
    w = POSITION_PRECISION * FFMAX(qmx, qmy);
625
987k
    mul[0] *= w;
626
987k
    mul[1] *= w;
627
628
2.96M
    for (int j = 0; j < 2; j++) {
629
1.97M
        double val = m[2][j] * mul[j];
630
1.97M
        if (!(fabs(val) < max_val))
631
0
            return false;
632
1.97M
        qm[2][j] = ass_lrint(val);
633
1.97M
    }
634
635
987k
    if (first && offset) {
636
83.5k
        offset->x = center[0] - qr[0];
637
83.5k
        offset->y = center[1] - qr[1];
638
83.5k
    }
639
987k
    *pos = (ASS_Vector) {
640
987k
        .x = qr[0] >> SUBPIXEL_ORDER,
641
987k
        .y = qr[1] >> SUBPIXEL_ORDER,
642
987k
    };
643
987k
    key->offset.x = qr[0] & ((1 << SUBPIXEL_ORDER) - 1);
644
987k
    key->offset.y = qr[1] & ((1 << SUBPIXEL_ORDER) - 1);
645
987k
    key->matrix_x.x = qm[0][0];  key->matrix_x.y = qm[0][1];
646
987k
    key->matrix_y.x = qm[1][0];  key->matrix_y.y = qm[1][1];
647
987k
    key->matrix_z.x = qm[2][0];  key->matrix_z.y = qm[2][1];
648
987k
    return true;
649
987k
}
650
651
static void restore_transform(double m[3][3], const BitmapHashKey *key)
652
17.5k
{
653
17.5k
    const ASS_Rect *bbox = &key->outline->cbox;
654
17.5k
    double x0 = (bbox->x_min + bbox->x_max) / 2.0;
655
17.5k
    double y0 = (bbox->y_min + bbox->y_max) / 2.0;
656
17.5k
    double dx = (bbox->x_max - bbox->x_min) / 2.0 + 64;
657
17.5k
    double dy = (bbox->y_max - bbox->y_min) / 2.0 + 64;
658
659
    // Arbitrary scale has chosen so that z0 = 1
660
17.5k
    double q_x = POSITION_PRECISION / dx;
661
17.5k
    double q_y = POSITION_PRECISION / dy;
662
17.5k
    m[0][0] = key->matrix_x.x * q_x;
663
17.5k
    m[0][1] = key->matrix_x.y * q_y;
664
17.5k
    m[1][0] = key->matrix_y.x * q_x;
665
17.5k
    m[1][1] = key->matrix_y.y * q_y;
666
667
17.5k
    int32_t qmx = abs(key->matrix_x.x) + abs(key->matrix_x.y);
668
17.5k
    int32_t qmy = abs(key->matrix_y.x) + abs(key->matrix_y.y);
669
17.5k
    double scale_z = 1.0 / POSITION_PRECISION / FFMAX(qmx, qmy);
670
17.5k
    m[2][0] = key->matrix_z.x * q_x * scale_z;  // qm_zx * q_zx
671
17.5k
    m[2][1] = key->matrix_z.y * q_y * scale_z;  // qm_zy * q_zy
672
673
17.5k
    m[0][2] = m[1][2] = 0;
674
17.5k
    m[2][2] = 1 + fabs(m[2][0]) * dx + fabs(m[2][1]) * dy;
675
17.5k
    m[2][2] = FFMIN(m[2][2], MAX_PERSP_SCALE);
676
677
17.5k
    double center[2] = {
678
17.5k
        key->offset.x * (64 >> SUBPIXEL_ORDER),
679
17.5k
        key->offset.y * (64 >> SUBPIXEL_ORDER),
680
17.5k
    };
681
52.5k
    for (int i = 0; i < 2; i++)
682
140k
        for (int j = 0; j < 3; j++)
683
105k
            m[i][j] += m[2][j] * center[i];
684
685
70.0k
    for (int i = 0; i < 3; i++)
686
52.5k
        m[i][2] -= m[i][0] * x0 + m[i][1] * y0;
687
17.5k
}
688
689
// Calculate bitmap memory footprint
690
static inline size_t bitmap_size(const Bitmap *bm)
691
24.8k
{
692
24.8k
    return bm->stride * bm->h;
693
24.8k
}
694
695
/**
696
 * Iterate through a list of bitmaps and blend with clip vector, if
697
 * applicable. The blended bitmaps are added to a free list which is freed
698
 * at the start of a new frame.
699
 */
700
static void blend_vector_clip(RenderContext *state, ASS_Image *head)
701
85.9k
{
702
85.9k
    if (!state->clip_drawing_text.str)
703
85.8k
        return;
704
705
132
    ASS_Renderer *render_priv = state->renderer;
706
707
132
    OutlineHashKey ol_key;
708
132
    ol_key.type = OUTLINE_DRAWING;
709
132
    ol_key.u.drawing.text = state->clip_drawing_text;
710
711
132
    double m[3][3] = {{0}};
712
132
    int32_t scale_base = lshiftwrapi(1, state->clip_drawing_scale - 1);
713
132
    double w = scale_base > 0 ? (1.0 / scale_base) : 0;
714
132
    m[0][0] = state->screen_scale_x * w;
715
132
    m[1][1] = state->screen_scale_y * w;
716
132
    m[2][2] = 1;
717
718
132
    m[0][2] = int_to_d6(render_priv->settings.left_margin);
719
132
    m[1][2] = int_to_d6(render_priv->settings.top_margin);
720
721
132
    ASS_Vector pos;
722
132
    BitmapHashKey key;
723
132
    key.outline = ass_cache_get(render_priv->cache.outline_cache, &ol_key, render_priv);
724
132
    if (!key.outline || !key.outline->valid ||
725
132
            !quantize_transform(m, &pos, NULL, true, &key))
726
0
        return;
727
728
132
    Bitmap *clip_bm = ass_cache_get(render_priv->cache.bitmap_cache, &key, state);
729
132
    if (!clip_bm)
730
0
        return;
731
732
    // Iterate through bitmaps and blend/clip them
733
528
    for (ASS_Image *cur = head; cur; cur = cur->next) {
734
396
        int left, top, right, bottom, w, h;
735
396
        int ax, ay, aw, ah, as;
736
396
        int bx, by, bw, bh, bs;
737
396
        int aleft, atop, bleft, btop;
738
396
        unsigned char *abuffer, *bbuffer, *nbuffer;
739
740
396
        abuffer = cur->bitmap;
741
396
        bbuffer = clip_bm->buffer;
742
396
        ax = cur->dst_x;
743
396
        ay = cur->dst_y;
744
396
        aw = cur->w;
745
396
        ah = cur->h;
746
396
        as = cur->stride;
747
396
        bx = pos.x + clip_bm->left;
748
396
        by = pos.y + clip_bm->top;
749
396
        bw = clip_bm->w;
750
396
        bh = clip_bm->h;
751
396
        bs = clip_bm->stride;
752
753
        // Calculate overlap coordinates
754
396
        left = (ax > bx) ? ax : bx;
755
396
        top = (ay > by) ? ay : by;
756
396
        right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw);
757
396
        bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh);
758
396
        aleft = left - ax;
759
396
        atop = top - ay;
760
396
        w = right - left;
761
396
        h = bottom - top;
762
396
        bleft = left - bx;
763
396
        btop = top - by;
764
765
396
        unsigned align = 1 << render_priv->engine.align_order;
766
396
        if (state->clip_drawing_mode) {
767
            // Inverse clip
768
66
            if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
769
66
                ay > by + bh || !h || !w) {
770
66
                continue;
771
66
            }
772
773
            // Allocate new buffer and add to free list
774
0
            nbuffer = ass_aligned_alloc(align, as * ah + align, false);
775
0
            if (!nbuffer)
776
0
                break;
777
778
            // Blend together
779
0
            memcpy(nbuffer, abuffer, ((ah - 1) * as) + aw);
780
0
            render_priv->engine.imul_bitmaps(nbuffer + atop * as + aleft, as,
781
0
                                             bbuffer + btop * bs + bleft, bs,
782
0
                                             w, h);
783
330
        } else {
784
            // Regular clip
785
330
            if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
786
330
                ay > by + bh || !h || !w) {
787
330
                cur->w = cur->h = cur->stride = 0;
788
330
                continue;
789
330
            }
790
791
            // Allocate new buffer and add to free list
792
0
            unsigned ns = ass_align(align, w);
793
0
            nbuffer = ass_aligned_alloc(align, ns * h + align, false);
794
0
            if (!nbuffer)
795
0
                break;
796
797
            // Blend together
798
0
            render_priv->engine.mul_bitmaps(nbuffer, ns,
799
0
                                            abuffer + atop * as + aleft, as,
800
0
                                            bbuffer + btop * bs + bleft, bs,
801
0
                                            w, h);
802
0
            cur->dst_x += aleft;
803
0
            cur->dst_y += atop;
804
0
            cur->w = w;
805
0
            cur->h = h;
806
0
            cur->stride = ns;
807
0
        }
808
809
0
        ASS_ImagePriv *priv = (ASS_ImagePriv *) cur;
810
0
        priv->buffer = cur->bitmap = nbuffer;
811
0
        ass_cache_dec_ref(priv->source);
812
0
        priv->source = NULL;
813
0
    }
814
132
}
815
816
/**
817
 * \brief Convert TextInfo struct to ASS_Image list
818
 * Splits glyphs in halves when needed (for \kf karaoke).
819
 */
820
static ASS_Image *render_text(RenderContext *state)
821
85.9k
{
822
85.9k
    ASS_Image *head;
823
85.9k
    ASS_Image **tail = &head;
824
85.9k
    unsigned n_bitmaps = state->text_info.n_bitmaps;
825
85.9k
    CombinedBitmapInfo *bitmaps = state->text_info.combined_bitmaps;
826
827
169k
    for (unsigned i = 0; i < n_bitmaps; i++) {
828
83.8k
        CombinedBitmapInfo *info = &bitmaps[i];
829
83.8k
        if (!info->bm_s || state->border_style == 4)
830
5.97k
            continue;
831
832
77.9k
        tail =
833
77.9k
            render_glyph(state, info->bm_s, info->x, info->y, info->c[3], 0,
834
77.9k
                         1000000, tail, IMAGE_TYPE_SHADOW, info->image);
835
77.9k
    }
836
837
169k
    for (unsigned i = 0; i < n_bitmaps; i++) {
838
83.8k
        CombinedBitmapInfo *info = &bitmaps[i];
839
83.8k
        if (!info->bm_o)
840
6.32k
            continue;
841
842
77.5k
        if ((info->effect_type == EF_KARAOKE_KO)
843
367
                && (info->effect_timing <= 0)) {
844
            // do nothing
845
77.5k
        } else {
846
77.5k
            tail =
847
77.5k
                render_glyph(state, info->bm_o, info->x, info->y, info->c[2],
848
77.5k
                             0, 1000000, tail, IMAGE_TYPE_OUTLINE, info->image);
849
77.5k
        }
850
77.5k
    }
851
852
169k
    for (unsigned i = 0; i < n_bitmaps; i++) {
853
83.8k
        CombinedBitmapInfo *info = &bitmaps[i];
854
83.8k
        if (!info->bm)
855
5.80k
            continue;
856
857
78.0k
        if ((info->effect_type == EF_KARAOKE)
858
77.0k
                || (info->effect_type == EF_KARAOKE_KO)) {
859
1.38k
            if (info->effect_timing > 0)
860
1.26k
                tail =
861
1.26k
                    render_glyph(state, info->bm, info->x, info->y,
862
1.26k
                                 info->c[0], 0, 1000000, tail,
863
1.26k
                                 IMAGE_TYPE_CHARACTER, info->image);
864
114
            else
865
114
                tail =
866
114
                    render_glyph(state, info->bm, info->x, info->y,
867
114
                                 info->c[1], 0, 1000000, tail,
868
114
                                 IMAGE_TYPE_CHARACTER, info->image);
869
76.7k
        } else if (info->effect_type == EF_KARAOKE_KF) {
870
1.49k
            tail =
871
1.49k
                render_glyph(state, info->bm, info->x, info->y, info->c[0],
872
1.49k
                             info->c[1], info->effect_timing, tail,
873
1.49k
                             IMAGE_TYPE_CHARACTER, info->image);
874
1.49k
        } else
875
75.2k
            tail =
876
75.2k
                render_glyph(state, info->bm, info->x, info->y, info->c[0],
877
75.2k
                             0, 1000000, tail, IMAGE_TYPE_CHARACTER, info->image);
878
78.0k
    }
879
880
85.9k
    *tail = 0;
881
85.9k
    blend_vector_clip(state, head);
882
883
85.9k
    return head;
884
85.9k
}
885
886
static void compute_string_bbox(TextInfo *text, ASS_DRect *bbox)
887
85.9k
{
888
85.9k
    if (text->length > 0) {
889
85.9k
        bbox->x_min = +32000;
890
85.9k
        bbox->x_max = -32000;
891
85.9k
        bbox->y_min = -text->lines[0].asc;
892
85.9k
        bbox->y_max = bbox->y_min + text->height;
893
894
659k
        for (int i = 0; i < text->length; i++) {
895
573k
            GlyphInfo *info = text->glyphs + i;
896
573k
            if (info->skip) continue;
897
569k
            double s = d6_to_double(info->pos.x);
898
569k
            double e = s + d6_to_double(info->cluster_advance.x);
899
569k
            bbox->x_min = FFMIN(bbox->x_min, s);
900
569k
            bbox->x_max = FFMAX(bbox->x_max, e);
901
569k
        }
902
85.9k
    } else
903
0
        bbox->x_min = bbox->x_max = bbox->y_min = bbox->y_max = 0;
904
85.9k
}
905
906
static ASS_Style *handle_selective_style_overrides(RenderContext *state,
907
                                                   ASS_Style *rstyle)
908
322k
{
909
    // The script style is the one the event was declared with.
910
322k
    ASS_Renderer *render_priv = state->renderer;
911
322k
    ASS_Style *script = render_priv->track->styles +
912
322k
                        state->event->Style;
913
    // The user style was set with ass_set_selective_style_override().
914
322k
    ASS_Style *user = &render_priv->user_override_style;
915
322k
    ASS_Style *new = &state->override_style_temp_storage;
916
322k
    int explicit = state->explicit;
917
322k
    int requested = render_priv->settings.selective_style_overrides;
918
322k
    double scale;
919
920
    // Either the event's style, or the style forced with a \r tag.
921
322k
    if (!rstyle)
922
321k
        rstyle = script;
923
924
    // Create a new style that contains a mix of the original style and
925
    // user_style (the user's override style). Copy only fields from the
926
    // script's style that are deemed necessary.
927
322k
    *new = *rstyle;
928
929
322k
    state->apply_font_scale =
930
322k
        !explicit || !(requested & ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE);
931
932
    // On positioned events, do not apply most overrides.
933
322k
    if (explicit)
934
34.0k
        requested = 0;
935
936
322k
    if (requested & ASS_OVERRIDE_BIT_STYLE)
937
0
        requested |= ASS_OVERRIDE_BIT_FONT_NAME |
938
0
                     ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS |
939
0
                     ASS_OVERRIDE_BIT_COLORS |
940
0
                     ASS_OVERRIDE_BIT_BORDER |
941
0
                     ASS_OVERRIDE_BIT_ATTRIBUTES;
942
943
    // Copies fields even not covered by any of the other bits.
944
322k
    if (requested & ASS_OVERRIDE_FULL_STYLE)
945
0
        *new = *user;
946
947
    // The user style is supposed to be independent of the script resolution.
948
    // Treat the user style's values as if they were specified for a script with
949
    // PlayResY=288, and rescale the values to the current script.
950
322k
    scale = render_priv->track->PlayResY / 288.0;
951
952
322k
    if (requested & ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS) {
953
0
        new->FontSize = user->FontSize * scale;
954
0
        new->Spacing = user->Spacing * scale;
955
0
        new->ScaleX = user->ScaleX;
956
0
        new->ScaleY = user->ScaleY;
957
0
    }
958
959
322k
    if (requested & ASS_OVERRIDE_BIT_FONT_NAME) {
960
0
        new->FontName = user->FontName;
961
0
        new->treat_fontname_as_pattern = user->treat_fontname_as_pattern;
962
0
    }
963
964
322k
    if (requested & ASS_OVERRIDE_BIT_COLORS) {
965
0
        new->PrimaryColour = user->PrimaryColour;
966
0
        new->SecondaryColour = user->SecondaryColour;
967
0
        new->OutlineColour = user->OutlineColour;
968
0
        new->BackColour = user->BackColour;
969
0
    }
970
971
322k
    if (requested & ASS_OVERRIDE_BIT_ATTRIBUTES) {
972
0
        new->Bold = user->Bold;
973
0
        new->Italic = user->Italic;
974
0
        new->Underline = user->Underline;
975
0
        new->StrikeOut = user->StrikeOut;
976
0
    }
977
978
322k
    if (requested & ASS_OVERRIDE_BIT_BORDER) {
979
0
        new->BorderStyle = user->BorderStyle;
980
0
        new->Outline = user->Outline * scale;
981
0
        new->Shadow = user->Shadow * scale;
982
0
    }
983
984
322k
    if (requested & ASS_OVERRIDE_BIT_BLUR)
985
0
        new->Blur = user->Blur * scale;
986
987
322k
    if (requested & ASS_OVERRIDE_BIT_ALIGNMENT)
988
0
        new->Alignment = user->Alignment;
989
990
322k
    if (requested & ASS_OVERRIDE_BIT_JUSTIFY)
991
0
        new->Justify = user->Justify;
992
993
322k
    if (requested & ASS_OVERRIDE_BIT_MARGINS) {
994
0
        new->MarginL = user->MarginL;
995
0
        new->MarginR = user->MarginR;
996
0
        new->MarginV = user->MarginV;
997
0
    }
998
999
322k
    if (!new->FontName)
1000
0
        new->FontName = rstyle->FontName;
1001
1002
322k
    state->style = new;
1003
322k
    state->overrides = requested;
1004
1005
322k
    return new;
1006
322k
}
1007
1008
ASS_Vector ass_layout_res(ASS_Renderer *render_priv)
1009
485k
{
1010
485k
    ASS_Track *track = render_priv->track;
1011
485k
    if (track->LayoutResX > 0 && track->LayoutResY > 0)
1012
12.1k
        return (ASS_Vector) { track->LayoutResX, track->LayoutResY };
1013
1014
473k
    ASS_Settings *settings = &render_priv->settings;
1015
473k
    if (settings->storage_width > 0 && settings->storage_height > 0)
1016
473k
        return (ASS_Vector) { settings->storage_width, settings->storage_height };
1017
1018
0
    if (settings->par <= 0 || settings->par == 1 ||
1019
0
            !render_priv->frame_content_width || !render_priv->frame_content_height)
1020
0
        return (ASS_Vector) { track->PlayResX, track->PlayResY };
1021
0
    if (settings->par > 1)
1022
0
        return (ASS_Vector) {
1023
0
            FFMAX(1, lround(track->PlayResY * render_priv->frame_content_width
1024
0
                    / render_priv->frame_content_height / settings->par)),
1025
0
            track->PlayResY
1026
0
        };
1027
0
    else
1028
0
        return (ASS_Vector) {
1029
0
            track->PlayResX,
1030
0
            FFMAX(1, lround(track->PlayResX * render_priv->frame_content_height
1031
0
                    / render_priv->frame_content_width * settings->par))
1032
0
        };
1033
0
}
1034
1035
static void init_font_scale(RenderContext *state)
1036
322k
{
1037
322k
    ASS_Renderer *render_priv = state->renderer;
1038
322k
    ASS_Settings *settings_priv = &render_priv->settings;
1039
1040
322k
    double font_scr_w = render_priv->frame_content_width;
1041
322k
    double font_scr_h = render_priv->frame_content_height;
1042
322k
    if (!state->explicit && render_priv->settings.use_margins) {
1043
0
        font_scr_w = render_priv->fit_width;
1044
0
        font_scr_h = render_priv->fit_height;
1045
0
    }
1046
1047
322k
    state->screen_scale_x = font_scr_w / render_priv->track->PlayResX;
1048
322k
    state->screen_scale_y = font_scr_h / render_priv->track->PlayResY;
1049
1050
322k
    ASS_Vector layout_res = ass_layout_res(render_priv);
1051
322k
    state->blur_scale_x = font_scr_w / layout_res.x;
1052
322k
    state->blur_scale_y = font_scr_h / layout_res.y;
1053
322k
    if (render_priv->track->ScaledBorderAndShadow) {
1054
2.68k
        state->border_scale_x = state->screen_scale_x;
1055
2.68k
        state->border_scale_y = state->screen_scale_y;
1056
319k
    } else {
1057
319k
        state->border_scale_x = state->blur_scale_x;
1058
319k
        state->border_scale_y = state->blur_scale_y;
1059
319k
    }
1060
1061
322k
    if (state->apply_font_scale) {
1062
288k
        state->screen_scale_x *= settings_priv->font_size_coeff;
1063
288k
        state->screen_scale_y *= settings_priv->font_size_coeff;
1064
288k
        state->border_scale_x *= settings_priv->font_size_coeff;
1065
288k
        state->border_scale_y *= settings_priv->font_size_coeff;
1066
288k
        state->blur_scale_x *= settings_priv->font_size_coeff;
1067
288k
        state->blur_scale_y *= settings_priv->font_size_coeff;
1068
288k
    }
1069
322k
}
1070
1071
/**
1072
 * \brief partially reset render_context to style values
1073
 * Works like {\r}: resets some style overrides
1074
 */
1075
void ass_reset_render_context(RenderContext *state, ASS_Style *style)
1076
322k
{
1077
322k
    style = handle_selective_style_overrides(state, style);
1078
1079
322k
    init_font_scale(state);
1080
1081
322k
    state->c[0] = style->PrimaryColour;
1082
322k
    state->c[1] = style->SecondaryColour;
1083
322k
    state->c[2] = style->OutlineColour;
1084
322k
    state->c[3] = style->BackColour;
1085
322k
    state->flags =
1086
322k
        (style->Underline ? DECO_UNDERLINE : 0) |
1087
322k
        (style->StrikeOut ? DECO_STRIKETHROUGH : 0);
1088
322k
    state->font_size = style->FontSize;
1089
1090
322k
    state->family.str = style->FontName;
1091
322k
    state->family.len = strlen(style->FontName);
1092
322k
    state->treat_family_as_pattern = style->treat_fontname_as_pattern;
1093
322k
    state->bold = style->Bold;
1094
322k
    state->italic = style->Italic;
1095
322k
    ass_update_font(state);
1096
1097
322k
    state->border_style = style->BorderStyle;
1098
322k
    state->border_x = style->Outline;
1099
322k
    state->border_y = style->Outline;
1100
322k
    state->scale_x = style->ScaleX;
1101
322k
    state->scale_y = style->ScaleY;
1102
322k
    state->hspacing = style->Spacing;
1103
322k
    state->be = 0;
1104
322k
    state->blur = style->Blur;
1105
322k
    state->shadow_x = style->Shadow;
1106
322k
    state->shadow_y = style->Shadow;
1107
322k
    state->frx = state->fry = 0.;
1108
322k
    state->frz = style->Angle;
1109
322k
    state->fax = state->fay = 0.;
1110
322k
    state->font_encoding = style->Encoding;
1111
322k
}
1112
1113
/**
1114
 * \brief Start new event. Reset state.
1115
 */
1116
static void
1117
init_render_context(RenderContext *state, ASS_Event *event)
1118
313k
{
1119
313k
    ASS_Renderer *render_priv = state->renderer;
1120
1121
313k
    state->event = event;
1122
313k
    state->parsed_tags = 0;
1123
313k
    state->evt_type = EVENT_NORMAL;
1124
1125
313k
    state->wrap_style = render_priv->track->WrapStyle;
1126
1127
313k
    state->pos_x = 0;
1128
313k
    state->pos_y = 0;
1129
313k
    state->org_x = 0;
1130
313k
    state->org_y = 0;
1131
313k
    state->have_origin = 0;
1132
313k
    state->clip_x0 = 0;
1133
313k
    state->clip_y0 = 0;
1134
313k
    state->clip_x1 = render_priv->track->PlayResX;
1135
313k
    state->clip_y1 = render_priv->track->PlayResY;
1136
313k
    state->clip_mode = 0;
1137
313k
    state->detect_collisions = 1;
1138
313k
    state->fade = 0;
1139
313k
    state->drawing_scale = 0;
1140
313k
    state->pbo = 0;
1141
313k
    state->effect_type = EF_NONE;
1142
313k
    state->effect_timing = 0;
1143
313k
    state->effect_skip_timing = 0;
1144
313k
    state->reset_effect = false;
1145
1146
313k
    ass_apply_transition_effects(state);
1147
313k
    state->explicit = state->evt_type != EVENT_NORMAL ||
1148
306k
                      ass_event_has_hard_overrides(event->Text);
1149
1150
313k
    ass_reset_render_context(state, NULL);
1151
313k
    state->alignment = state->style->Alignment;
1152
313k
    state->justify = state->style->Justify;
1153
313k
}
1154
1155
static void free_render_context(RenderContext *state)
1156
626k
{
1157
626k
    state->font = NULL;
1158
626k
    state->family.str = NULL;
1159
626k
    state->family.len = 0;
1160
626k
    state->clip_drawing_text.str = NULL;
1161
626k
    state->clip_drawing_text.len = 0;
1162
626k
    state->text_info.length = 0;
1163
626k
}
1164
1165
/**
1166
 * \brief Get normal and outline (border) glyphs
1167
 * \param info out: struct filled with extracted data
1168
 * Tries to get both glyphs from cache.
1169
 * If they can't be found, gets a glyph from font face, generates outline,
1170
 * and add them to cache.
1171
 */
1172
static void
1173
get_outline_glyph(RenderContext *state, GlyphInfo *info)
1174
573k
{
1175
573k
    ASS_Renderer *priv = state->renderer;
1176
573k
    OutlineHashValue *val;
1177
573k
    ASS_DVector scale, offset = {0};
1178
1179
573k
    int32_t asc, desc;
1180
573k
    OutlineHashKey key;
1181
573k
    if (info->drawing_text.str) {
1182
3.19k
        key.type = OUTLINE_DRAWING;
1183
3.19k
        key.u.drawing.text = info->drawing_text;
1184
3.19k
        val = ass_cache_get(priv->cache.outline_cache, &key, priv);
1185
3.19k
        if (!val || !val->valid)
1186
0
            return;
1187
1188
3.19k
        int32_t scale_base = lshiftwrapi(1, info->drawing_scale - 1);
1189
3.19k
        double w = scale_base > 0 ? (1.0 / scale_base) : 0;
1190
3.19k
        scale.x = info->scale_x * w * state->screen_scale_x / priv->par_scale_x;
1191
3.19k
        scale.y = info->scale_y * w * state->screen_scale_y;
1192
3.19k
        desc = 64 * info->drawing_pbo;
1193
3.19k
        asc = val->asc - desc;
1194
1195
3.19k
        offset.y = -asc * scale.y;
1196
569k
    } else {
1197
569k
        key.type = OUTLINE_GLYPH;
1198
569k
        GlyphHashKey *k = &key.u.glyph;
1199
569k
        k->font = info->font;
1200
569k
        k->size = info->font_size;
1201
569k
        k->face_index = info->face_index;
1202
569k
        k->glyph_index = info->glyph_index;
1203
569k
        k->bold = info->bold;
1204
569k
        k->italic = info->italic;
1205
569k
        k->flags = info->flags;
1206
1207
569k
        val = ass_cache_get(priv->cache.outline_cache, &key, priv);
1208
569k
        if (!val || !val->valid)
1209
64.0k
            return;
1210
1211
505k
        scale.x = info->scale_x;
1212
505k
        scale.y = info->scale_y;
1213
505k
        asc  = val->asc;
1214
505k
        desc = val->desc;
1215
505k
    }
1216
1217
509k
    info->outline = val;
1218
509k
    info->transform.scale = scale;
1219
509k
    info->transform.offset = offset;
1220
1221
509k
    info->bbox.x_min = ass_lrint(val->cbox.x_min * scale.x + offset.x);
1222
509k
    info->bbox.y_min = ass_lrint(val->cbox.y_min * scale.y + offset.y);
1223
509k
    info->bbox.x_max = ass_lrint(val->cbox.x_max * scale.x + offset.x);
1224
509k
    info->bbox.y_max = ass_lrint(val->cbox.y_max * scale.y + offset.y);
1225
1226
509k
    if (info->drawing_text.str || priv->settings.shaper == ASS_SHAPING_SIMPLE) {
1227
3.19k
        info->cluster_advance.x = info->advance.x = ass_lrint(val->advance * scale.x);
1228
3.19k
        info->cluster_advance.y = info->advance.y = 0;
1229
3.19k
    }
1230
509k
    info->asc  = ass_lrint(asc  * scale.y);
1231
509k
    info->desc = ass_lrint(desc * scale.y);
1232
509k
}
1233
1234
size_t ass_outline_construct(void *key, void *value, void *priv)
1235
11.6k
{
1236
11.6k
    ASS_Renderer *render_priv = priv;
1237
11.6k
    OutlineHashKey *outline_key = key;
1238
11.6k
    OutlineHashValue *v = value;
1239
11.6k
    memset(v, 0, sizeof(*v));
1240
1241
11.6k
    switch (outline_key->type) {
1242
5.81k
    case OUTLINE_GLYPH:
1243
5.81k
        {
1244
5.81k
            GlyphHashKey *k = &outline_key->u.glyph;
1245
5.81k
            ass_face_set_size(k->font->faces[k->face_index], k->size);
1246
5.81k
            if (!ass_font_get_glyph(k->font, k->face_index, k->glyph_index,
1247
5.81k
                                    render_priv->settings.hinting))
1248
80
                return 1;
1249
5.73k
            if (!ass_get_glyph_outline(&v->outline[0], &v->advance,
1250
5.73k
                                       k->font->faces[k->face_index],
1251
5.73k
                                       k->flags))
1252
0
                return 1;
1253
5.73k
            ass_font_get_asc_desc(k->font, k->face_index,
1254
5.73k
                                  &v->asc, &v->desc);
1255
5.73k
            break;
1256
5.73k
        }
1257
127
    case OUTLINE_DRAWING:
1258
127
        {
1259
127
            ASS_Rect bbox;
1260
127
            const char *text = outline_key->u.drawing.text.str;  // always zero-terminated
1261
127
            if (!ass_drawing_parse(&v->outline[0], &bbox, text, render_priv->library))
1262
0
                return 1;
1263
1264
127
            v->advance = bbox.x_max - bbox.x_min;
1265
127
            v->asc = bbox.y_max - bbox.y_min;
1266
127
            v->desc = 0;
1267
127
            break;
1268
127
        }
1269
5.69k
    case OUTLINE_BORDER:
1270
5.69k
        {
1271
5.69k
            BorderHashKey *k = &outline_key->u.border;
1272
5.69k
            if (!k->border.x && !k->border.y)
1273
0
                break;
1274
5.69k
            if (!k->outline->outline[0].n_points)
1275
194
                break;
1276
1277
5.50k
            ASS_Outline src;
1278
5.50k
            if (!ass_outline_scale_pow2(&src, &k->outline->outline[0],
1279
5.50k
                                        k->scale_ord_x, k->scale_ord_y))
1280
0
                return 1;
1281
5.50k
            if (!ass_outline_stroke(&v->outline[0], &v->outline[1], &src,
1282
5.50k
                                    k->border.x * STROKER_PRECISION,
1283
5.50k
                                    k->border.y * STROKER_PRECISION,
1284
5.50k
                                    STROKER_PRECISION)) {
1285
0
                ass_msg(render_priv->library, MSGL_WARN, "Cannot stroke outline");
1286
0
                ass_outline_free(&v->outline[0]);
1287
0
                ass_outline_free(&v->outline[1]);
1288
0
                ass_outline_free(&src);
1289
0
                return 1;
1290
0
            }
1291
5.50k
            ass_outline_free(&src);
1292
5.50k
            break;
1293
5.50k
        }
1294
0
    case OUTLINE_BOX:
1295
0
        {
1296
0
            ASS_Outline *ol = &v->outline[0];
1297
0
            if (!ass_outline_alloc(ol, 4, 4))
1298
0
                return 1;
1299
0
            ol->points[0].x = ol->points[3].x = 0;
1300
0
            ol->points[1].x = ol->points[2].x = 64;
1301
0
            ol->points[0].y = ol->points[1].y = 0;
1302
0
            ol->points[2].y = ol->points[3].y = 64;
1303
0
            ol->segments[0] = OUTLINE_LINE_SEGMENT;
1304
0
            ol->segments[1] = OUTLINE_LINE_SEGMENT;
1305
0
            ol->segments[2] = OUTLINE_LINE_SEGMENT;
1306
0
            ol->segments[3] = OUTLINE_LINE_SEGMENT | OUTLINE_CONTOUR_END;
1307
0
            ol->n_points = ol->n_segments = 4;
1308
0
            break;
1309
0
        }
1310
0
    default:
1311
0
        return 1;
1312
11.6k
    }
1313
1314
11.5k
    rectangle_reset(&v->cbox);
1315
11.5k
    ass_outline_update_cbox(&v->outline[0], &v->cbox);
1316
11.5k
    ass_outline_update_cbox(&v->outline[1], &v->cbox);
1317
11.5k
    if (v->cbox.x_min > v->cbox.x_max || v->cbox.y_min > v->cbox.y_max)
1318
442
        v->cbox.x_min = v->cbox.y_min = v->cbox.x_max = v->cbox.y_max = 0;
1319
11.5k
    v->valid = true;
1320
11.5k
    return 1;
1321
11.6k
}
1322
1323
/**
1324
 * \brief Calculate outline transformation matrix
1325
 */
1326
static void calc_transform_matrix(RenderContext *state,
1327
                                  GlyphInfo *info, double m[3][3])
1328
496k
{
1329
496k
    ASS_Renderer *render_priv = state->renderer;
1330
1331
496k
    double frx = ASS_PI / 180 * info->frx;
1332
496k
    double fry = ASS_PI / 180 * info->fry;
1333
496k
    double frz = ASS_PI / 180 * info->frz;
1334
1335
496k
    double sx = -sin(frx), cx = cos(frx);
1336
496k
    double sy =  sin(fry), cy = cos(fry);
1337
496k
    double sz = -sin(frz), cz = cos(frz);
1338
1339
496k
    double fax = info->fax * info->scale_x / info->scale_y;
1340
496k
    double fay = info->fay * info->scale_y / info->scale_x;
1341
496k
    double x1[3] = { 1, fax, info->shift.x + info->asc * fax };
1342
496k
    double y1[3] = { fay, 1, info->shift.y };
1343
1344
496k
    double x2[3], y2[3];
1345
1.98M
    for (int i = 0; i < 3; i++) {
1346
1.48M
        x2[i] = x1[i] * cz - y1[i] * sz;
1347
1.48M
        y2[i] = x1[i] * sz + y1[i] * cz;
1348
1.48M
    }
1349
1350
496k
    double y3[3], z3[3];
1351
1.98M
    for (int i = 0; i < 3; i++) {
1352
1.48M
        y3[i] = y2[i] * cx;
1353
1.48M
        z3[i] = y2[i] * sx;
1354
1.48M
    }
1355
1356
496k
    double x4[3], z4[3];
1357
1.98M
    for (int i = 0; i < 3; i++) {
1358
1.48M
        x4[i] = x2[i] * cy - z3[i] * sy;
1359
1.48M
        z4[i] = x2[i] * sy + z3[i] * cy;
1360
1.48M
    }
1361
1362
496k
    double dist = 20000 * state->blur_scale_y;
1363
496k
    z4[2] += dist;
1364
1365
496k
    double scale_x = dist * render_priv->par_scale_x;
1366
496k
    double offs_x = info->pos.x - info->shift.x * render_priv->par_scale_x;
1367
496k
    double offs_y = info->pos.y - info->shift.y;
1368
1.98M
    for (int i = 0; i < 3; i++) {
1369
1.48M
        m[0][i] = z4[i] * offs_x + x4[i] * scale_x;
1370
1.48M
        m[1][i] = z4[i] * offs_y + y3[i] * dist;
1371
1.48M
        m[2][i] = z4[i];
1372
1.48M
    }
1373
496k
}
1374
1375
/**
1376
 * \brief Get bitmaps for a glyph
1377
 * \param info glyph info
1378
 * Tries to get glyph bitmaps from bitmap cache.
1379
 * If they can't be found, they are generated by rotating and rendering the glyph.
1380
 * After that, bitmaps are added to the cache.
1381
 * They are returned in info->bm (glyph), info->bm_o (outline).
1382
 */
1383
static void
1384
get_bitmap_glyph(RenderContext *state, GlyphInfo *info,
1385
                 int32_t *leftmost_x,
1386
                 ASS_Vector *pos, ASS_Vector *pos_o,
1387
                 ASS_DVector *offset, bool first, int flags)
1388
560k
{
1389
560k
    ASS_Renderer *render_priv = state->renderer;
1390
1391
560k
    if (!info->outline || info->symbol == '\n' || info->symbol == 0 || info->skip)
1392
63.9k
        return;
1393
1394
496k
    double m1[3][3], m2[3][3], m[3][3];
1395
496k
    const ASS_Transform *tr = &info->transform;
1396
496k
    calc_transform_matrix(state, info, m1);
1397
1.98M
    for (int i = 0; i < 3; i++) {
1398
1.48M
        m2[i][0] = m1[i][0] * tr->scale.x;
1399
1.48M
        m2[i][1] = m1[i][1] * tr->scale.y;
1400
1.48M
        m2[i][2] = m1[i][0] * tr->offset.x + m1[i][1] * tr->offset.y + m1[i][2];
1401
1.48M
    }
1402
496k
    memcpy(m, m2, sizeof(m));
1403
1404
496k
    if (info->effect_type == EF_KARAOKE_KF)
1405
8.96k
        ass_outline_update_min_transformed_x(&info->outline->outline[0], m, leftmost_x);
1406
1407
496k
    BitmapHashKey key;
1408
496k
    key.outline = info->outline;
1409
496k
    if (!quantize_transform(m, pos, offset, first, &key))
1410
1.65k
        return;
1411
1412
494k
    info->bm = ass_cache_get(render_priv->cache.bitmap_cache, &key, state);
1413
494k
    if (!info->bm || !info->bm->buffer)
1414
16.9k
        info->bm = NULL;
1415
1416
494k
    *pos_o = *pos;
1417
1418
494k
    OutlineHashKey ol_key;
1419
494k
    if (flags & FILTER_BORDER_STYLE_3) {
1420
0
        if (!(flags & (FILTER_NONZERO_BORDER | FILTER_NONZERO_SHADOW)))
1421
0
            return;
1422
1423
0
        ol_key.type = OUTLINE_BOX;
1424
1425
0
        ASS_DVector bord = {
1426
0
            64 * info->border_x * state->border_scale_x /
1427
0
                render_priv->par_scale_x,
1428
0
            64 * info->border_y * state->border_scale_y,
1429
0
        };
1430
0
        double width = info->hspacing_scaled + info->advance.x;
1431
0
        double height = info->asc + info->desc;
1432
1433
0
        ASS_DVector orig_scale;
1434
0
        orig_scale.x = info->scale_x * info->scale_fix;
1435
0
        orig_scale.y = info->scale_y * info->scale_fix;
1436
1437
        // Emulate the WTFish behavior of VSFilter, i.e. double-scale
1438
        // the sizes of the opaque box.
1439
0
        bord.x *= orig_scale.x;
1440
0
        bord.y *= orig_scale.y;
1441
0
        width  *= orig_scale.x;
1442
0
        height *= orig_scale.y;
1443
1444
        // to avoid gaps
1445
0
        bord.x = FFMAX(64, bord.x);
1446
0
        bord.y = FFMAX(64, bord.y);
1447
1448
0
        ASS_DVector scale = {
1449
0
            (width  + 2 * bord.x) / 64,
1450
0
            (height + 2 * bord.y) / 64,
1451
0
        };
1452
0
        ASS_DVector offset = { -bord.x, -bord.y - info->asc };
1453
0
        for (int i = 0; i < 3; i++) {
1454
0
            m[i][0] = m1[i][0] * scale.x;
1455
0
            m[i][1] = m1[i][1] * scale.y;
1456
0
            m[i][2] = m1[i][0] * offset.x + m1[i][1] * offset.y + m1[i][2];
1457
0
        }
1458
494k
    } else {
1459
494k
        if (!(flags & FILTER_NONZERO_BORDER))
1460
1.43k
            return;
1461
1462
493k
        ol_key.type = OUTLINE_BORDER;
1463
493k
        BorderHashKey *k = &ol_key.u.border;
1464
493k
        k->outline = info->outline;
1465
1466
493k
        double bord_x =
1467
493k
            64 * state->border_scale_x * info->border_x / tr->scale.x /
1468
493k
                render_priv->par_scale_x;
1469
493k
        double bord_y =
1470
493k
            64 * state->border_scale_y * info->border_y / tr->scale.y;
1471
1472
493k
        const ASS_Rect *bbox = &info->outline->cbox;
1473
        // Estimate bounding box half size after stroking
1474
493k
        double dx = (bbox->x_max - bbox->x_min) / 2.0 + (bord_x + 64);
1475
493k
        double dy = (bbox->y_max - bbox->y_min) / 2.0 + (bord_y + 64);
1476
1477
        // Matrix after quantize_transform() has
1478
        // input and output origin at bounding box center.
1479
493k
        double mxx = fabs(m[0][0]), mxy = fabs(m[0][1]);
1480
493k
        double myx = fabs(m[1][0]), myy = fabs(m[1][1]);
1481
493k
        double mzx = fabs(m[2][0]), mzy = fabs(m[2][1]);
1482
1483
493k
        double z0 = m[2][2] - mzx * dx - mzy * dy;
1484
493k
        double w = 1 / FFMAX(z0, m[2][2] / MAX_PERSP_SCALE);
1485
1486
        // Notation from quantize_transform().
1487
        // Note that goal here is to estimate acceptable error for stroking, i. e. D(x) and D(y).
1488
        // Matrix coefficients are constants now, so D(m_ij) = 0 for all i, j from {x, y, z}.
1489
1490
        // D(z) <= |m_zx| * D(x) + |m_zy| * D(y),
1491
        // D(x_out) = D((m_xx * x + m_xy * y) / z)
1492
        //  <= (|m_xx| * D(x) + |m_xy| * D(y)) / z0 + x_lim * D(z) / z0^2
1493
        //  <= (|m_xx| / z0 + |m_zx| * x_lim / z0^2) * D(x)
1494
        //   + (|m_xy| / z0 + |m_zy| * x_lim / z0^2) * D(y),
1495
        // D(y_out) = D((m_yx * x + m_yy * y) / z)
1496
        //  <= (|m_yx| * D(x) + |m_yy| * D(y)) / z0 + y_lim * D(z) / z0^2
1497
        //  <= (|m_yx| / z0 + |m_zx| * y_lim / z0^2) * D(x)
1498
        //   + (|m_yy| / z0 + |m_zy| * y_lim / z0^2) * D(y).
1499
1500
        // Quantization steps (pick: ACCURACY = POSITION_PRECISION):
1501
        // STROKER_PRECISION / 2^scale_ord_x ~ D(x) ~ POSITION_PRECISION /
1502
        //   (max(|m_xx|, |m_yx|) / z0 + |m_zx| * max(x_lim, y_lim) / z0^2),
1503
        // STROKER_PRECISION / 2^scale_ord_y ~ D(y) ~ POSITION_PRECISION /
1504
        //   (max(|m_xy|, |m_yy|) / z0 + |m_zy| * max(x_lim, y_lim) / z0^2).
1505
1506
493k
        double x_lim = mxx * dx + mxy * dy;
1507
493k
        double y_lim = myx * dx + myy * dy;
1508
493k
        double rz = FFMAX(x_lim, y_lim) * w;
1509
1510
493k
        w *= STROKER_PRECISION / POSITION_PRECISION;
1511
493k
        frexp(w * (FFMAX(mxx, myx) + mzx * rz), &k->scale_ord_x);
1512
493k
        frexp(w * (FFMAX(mxy, myy) + mzy * rz), &k->scale_ord_y);
1513
493k
        bord_x = ldexp(bord_x, k->scale_ord_x);
1514
493k
        bord_y = ldexp(bord_y, k->scale_ord_y);
1515
493k
        if (!(bord_x < OUTLINE_MAX && bord_y < OUTLINE_MAX))
1516
0
            return;
1517
493k
        k->border.x = ass_lrint(bord_x / STROKER_PRECISION);
1518
493k
        k->border.y = ass_lrint(bord_y / STROKER_PRECISION);
1519
493k
        if (!k->border.x && !k->border.y) {
1520
0
            info->bm_o = info->bm;
1521
0
            return;
1522
0
        }
1523
1524
1.97M
        for (int i = 0; i < 3; i++) {
1525
1.47M
            m[i][0] = ldexp(m2[i][0], -k->scale_ord_x);
1526
1.47M
            m[i][1] = ldexp(m2[i][1], -k->scale_ord_y);
1527
1.47M
            m[i][2] = m2[i][2];
1528
1.47M
        }
1529
493k
    }
1530
1531
493k
    key.outline = ass_cache_get(render_priv->cache.outline_cache, &ol_key, render_priv);
1532
493k
    if (!key.outline || !key.outline->valid ||
1533
493k
            !quantize_transform(m, pos_o, offset, false, &key))
1534
0
        return;
1535
1536
493k
    info->bm_o = ass_cache_get(render_priv->cache.bitmap_cache, &key, state);
1537
493k
    if (!info->bm_o || !info->bm_o->buffer) {
1538
16.9k
        info->bm_o = NULL;
1539
16.9k
        *pos_o = *pos;
1540
476k
    } else if (!info->bm)
1541
0
        *pos = *pos_o;
1542
493k
}
1543
1544
static inline size_t outline_size(const ASS_Outline* outline)
1545
35.0k
{
1546
35.0k
    return sizeof(ASS_Vector) * outline->n_points + outline->n_segments;
1547
35.0k
}
1548
1549
size_t ass_bitmap_construct(void *key, void *value, void *priv)
1550
17.5k
{
1551
17.5k
    RenderContext *state = priv;
1552
17.5k
    BitmapHashKey *k = key;
1553
17.5k
    Bitmap *bm = value;
1554
1555
17.5k
    double m[3][3];
1556
17.5k
    restore_transform(m, k);
1557
1558
17.5k
    ASS_Outline outline[2];
1559
17.5k
    if (k->matrix_z.x || k->matrix_z.y) {
1560
2
        ass_outline_transform_3d(&outline[0], &k->outline->outline[0], m);
1561
2
        ass_outline_transform_3d(&outline[1], &k->outline->outline[1], m);
1562
17.4k
    } else {
1563
17.4k
        ass_outline_transform_2d(&outline[0], &k->outline->outline[0], m);
1564
17.4k
        ass_outline_transform_2d(&outline[1], &k->outline->outline[1], m);
1565
17.4k
    }
1566
1567
17.5k
    if (!ass_outline_to_bitmap(state, bm, &outline[0], &outline[1]))
1568
478
        memset(bm, 0, sizeof(*bm));
1569
17.5k
    ass_outline_free(&outline[0]);
1570
17.5k
    ass_outline_free(&outline[1]);
1571
1572
17.5k
    return sizeof(BitmapHashKey) + sizeof(Bitmap) + bitmap_size(bm) +
1573
17.5k
           sizeof(OutlineHashValue) + outline_size(&k->outline->outline[0]) + outline_size(&k->outline->outline[1]);
1574
17.5k
}
1575
1576
static void measure_text_on_eol(RenderContext *state, double scale, int cur_line,
1577
                                int max_asc, int max_desc,
1578
                                double max_border_x, double max_border_y)
1579
86.6k
{
1580
86.6k
    TextInfo *text_info = &state->text_info;
1581
86.6k
    text_info->lines[cur_line].asc  = scale * max_asc;
1582
86.6k
    text_info->lines[cur_line].desc = scale * max_desc;
1583
86.6k
    text_info->height += scale * max_asc + scale * max_desc;
1584
    // For *VSFilter compatibility do biased rounding on max_border*
1585
    // https://github.com/Cyberbeing/xy-VSFilter/blob/xy_sub_filter_rc4@%7B2020-05-17%7D/src/subtitles/RTS.cpp#L1465
1586
86.6k
    text_info->border_bottom = (int) (state->border_scale_y * max_border_y + 0.5);
1587
86.6k
    if (cur_line == 0)
1588
85.9k
        text_info->border_top = text_info->border_bottom;
1589
    // VSFilter takes max \bordx into account for collision, even if far from edge
1590
86.6k
    text_info->border_x = FFMAX(text_info->border_x,
1591
86.6k
            (int) (state->border_scale_x * max_border_x + 0.5));
1592
86.6k
}
1593
1594
1595
/**
1596
 * This function goes through text_info and calculates text parameters.
1597
 * The following text_info fields are filled:
1598
 *   height
1599
 *   border_top
1600
 *   border_bottom
1601
 *   border_x
1602
 *   lines[].asc
1603
 *   lines[].desc
1604
 */
1605
static void measure_text(RenderContext *state)
1606
85.9k
{
1607
85.9k
    ASS_Renderer *render_priv = state->renderer;
1608
85.9k
    TextInfo *text_info = &state->text_info;
1609
85.9k
    text_info->height = 0;
1610
85.9k
    text_info->border_x = 0;
1611
1612
85.9k
    int cur_line = 0;
1613
85.9k
    double scale = 0.5 / 64;
1614
85.9k
    int max_asc = 0, max_desc = 0;
1615
85.9k
    double max_border_y = 0, max_border_x = 0;
1616
85.9k
    bool empty_trimmed_line = true;
1617
659k
    for (int i = 0; i < text_info->length; i++) {
1618
573k
        if (text_info->glyphs[i].linebreak) {
1619
653
            measure_text_on_eol(state, scale, cur_line,
1620
653
                    max_asc, max_desc, max_border_x, max_border_y);
1621
653
            empty_trimmed_line = true;
1622
653
            max_asc = max_desc = 0;
1623
653
            max_border_y = max_border_x = 0;
1624
653
            scale = 0.5 / 64;
1625
653
            cur_line++;
1626
653
        }
1627
573k
        GlyphInfo *cur = text_info->glyphs + i;
1628
        // VSFilter ignores metrics of line-leading/trailing (trimmed)
1629
        // whitespace, except when the line becomes empty after trimming
1630
573k
        if (empty_trimmed_line && !cur->is_trimmed_whitespace) {
1631
84.4k
            empty_trimmed_line = false;
1632
            // Forget metrics of line-leading whitespace
1633
84.4k
            max_asc = max_desc = 0;
1634
84.4k
            max_border_y = max_border_x = 0;
1635
488k
        } else if (!empty_trimmed_line && cur->is_trimmed_whitespace) {
1636
            // Ignore metrics of line-trailing whitespace
1637
3
            continue;
1638
3
        }
1639
573k
        max_asc  = FFMAX(max_asc,  cur->asc);
1640
573k
        max_desc = FFMAX(max_desc, cur->desc);
1641
573k
        max_border_y = FFMAX(max_border_y, cur->border_y);
1642
573k
        max_border_x = FFMAX(max_border_x, cur->border_x);
1643
573k
        if (cur->symbol != '\n')
1644
571k
            scale = 1.0 / 64;
1645
573k
    }
1646
85.9k
    assert(cur_line == text_info->n_lines - 1);
1647
85.9k
    measure_text_on_eol(state, scale, cur_line,
1648
85.9k
            max_asc, max_desc, max_border_x, max_border_y);
1649
85.9k
    text_info->height += cur_line * render_priv->settings.line_spacing;
1650
85.9k
}
1651
1652
/**
1653
 * Mark extra whitespace for later removal.
1654
 */
1655
145k
#define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \
1656
145k
                          && !x->linebreak)
1657
static void trim_whitespace(RenderContext *state)
1658
85.9k
{
1659
85.9k
    int i, j;
1660
85.9k
    GlyphInfo *cur;
1661
85.9k
    TextInfo *ti = &state->text_info;
1662
1663
    // Mark trailing spaces
1664
85.9k
    i = ti->length - 1;
1665
85.9k
    cur = ti->glyphs + i;
1666
85.9k
    while (i && IS_WHITESPACE(cur)) {
1667
0
        cur->skip = true;
1668
0
        cur->is_trimmed_whitespace = true;
1669
0
        cur = ti->glyphs + --i;
1670
0
    }
1671
1672
    // Mark leading whitespace
1673
85.9k
    i = 0;
1674
85.9k
    cur = ti->glyphs;
1675
89.8k
    while (i < ti->length && IS_WHITESPACE(cur)) {
1676
3.85k
        cur->skip = true;
1677
3.85k
        cur->is_trimmed_whitespace = true;
1678
3.85k
        cur = ti->glyphs + ++i;
1679
3.85k
    }
1680
85.9k
    if (i < ti->length)
1681
84.4k
        cur->starts_new_run = true;
1682
1683
    // Mark all extraneous whitespace inbetween
1684
659k
    for (i = 0; i < ti->length; ++i) {
1685
573k
        cur = ti->glyphs + i;
1686
573k
        if (cur->linebreak) {
1687
            // Mark whitespace before
1688
653
            j = i - 1;
1689
653
            cur = ti->glyphs + j;
1690
656
            while (j && IS_WHITESPACE(cur)) {
1691
3
                cur->skip = true;
1692
3
                cur->is_trimmed_whitespace = true;
1693
3
                cur = ti->glyphs + --j;
1694
3
            }
1695
            // A break itself can contain a whitespace, too
1696
653
            cur = ti->glyphs + i;
1697
653
            if (cur->symbol == ' ' || cur->symbol == '\n') {
1698
0
                cur->skip = true;
1699
0
                cur->is_trimmed_whitespace = true;
1700
                // Mark whitespace after
1701
0
                j = i + 1;
1702
0
                cur = ti->glyphs + j;
1703
0
                while (j < ti->length && IS_WHITESPACE(cur)) {
1704
0
                    cur->skip = true;
1705
0
                    cur->is_trimmed_whitespace = true;
1706
0
                    cur = ti->glyphs + ++j;
1707
0
                }
1708
0
                i = j - 1;
1709
0
            }
1710
653
            if (cur < ti->glyphs + ti->length)
1711
653
                cur->starts_new_run = true;
1712
653
        }
1713
573k
    }
1714
85.9k
}
1715
#undef IS_WHITESPACE
1716
1717
#ifdef CONFIG_UNIBREAK
1718
    #define ALLOWBREAK(glyph, index) (unibrks ? unibrks[index] == LINEBREAK_ALLOWBREAK : glyph == ' ')
1719
    #define FORCEBREAK(glyph, index) (unibrks ? unibrks[index] == LINEBREAK_MUSTBREAK  : glyph == '\n')
1720
#else
1721
573k
    #define ALLOWBREAK(glyph, index) (glyph == ' ')
1722
573k
    #define FORCEBREAK(glyph, index) (glyph == '\n')
1723
#endif
1724
1725
/*
1726
 * Starts a new line on the first breakable character after overflow
1727
 */
1728
static void
1729
wrap_lines_naive(RenderContext *state, double max_text_width, char *unibrks)
1730
85.9k
{
1731
85.9k
    ASS_Renderer *render_priv = state->renderer;
1732
85.9k
    TextInfo *text_info = &state->text_info;
1733
85.9k
    GlyphInfo *s1  = text_info->glyphs; // current line start
1734
85.9k
    int last_breakable = -1;
1735
85.9k
    int break_type = 0;
1736
1737
85.9k
    text_info->n_lines = 1;
1738
659k
    for (int i = 0; i < text_info->length; ++i) {
1739
573k
        GlyphInfo *cur = text_info->glyphs + i;
1740
573k
        int break_at = -1;
1741
573k
        double s_offset = d6_to_double(s1->bbox.x_min + s1->pos.x);
1742
573k
        double len = d6_to_double(cur->bbox.x_max + cur->pos.x) - s_offset;
1743
1744
573k
        if (FORCEBREAK(cur->symbol, i)) {
1745
1.44k
            break_type = 2;
1746
1.44k
            break_at = i;
1747
1.44k
            ass_msg(render_priv->library, MSGL_DBG2,
1748
1.44k
                    "forced line break at %d", break_at);
1749
571k
        } else if (len >= max_text_width &&
1750
2.78k
                   cur->symbol != ' ' /* get trimmed */ &&
1751
2.78k
                   (state->wrap_style != 2)) {
1752
2.78k
            break_type = 1;
1753
2.78k
            break_at = last_breakable;
1754
2.78k
            if (break_at >= 0)
1755
3
                ass_msg(render_priv->library, MSGL_DBG2, "line break at %d",
1756
3
                        break_at);
1757
2.78k
        }
1758
573k
        if (ALLOWBREAK(cur->symbol, i)) {
1759
16.0k
            last_breakable = i;
1760
16.0k
        }
1761
1762
573k
        if (break_at != -1) {
1763
            // need to use one more line
1764
            // marking break_at+1 as start of a new line
1765
1.44k
            int lead = break_at + 1;    // the first symbol of the new line
1766
1.44k
            if (text_info->n_lines >= text_info->max_lines) {
1767
                // Try to raise the maximum number of lines
1768
0
                bool success = false;
1769
0
                if (text_info->max_lines <= INT_MAX / 2) {
1770
0
                    text_info->max_lines *= 2;
1771
0
                    success = ASS_REALLOC_ARRAY(text_info->lines, text_info->max_lines);
1772
0
                }
1773
                // If realloc fails it's screwed and due to error-info not propagating (FIXME),
1774
                // the best we can do is to avoid UB by discarding the previous break
1775
0
                if (!success) {
1776
0
                    s1->linebreak = 0;
1777
0
                    text_info->n_lines--;
1778
0
                }
1779
0
            }
1780
1.44k
            if (lead < text_info->length) {
1781
653
                text_info->glyphs[lead].linebreak = break_type;
1782
653
                last_breakable = -1;
1783
653
                s1 = text_info->glyphs + lead;
1784
653
                text_info->n_lines++;
1785
653
            }
1786
1.44k
        }
1787
573k
    }
1788
85.9k
}
1789
1790
/*
1791
 * Rewind from a linestart position back to the first non-whitespace (0x20)
1792
 * character. Trailing ASCII whitespace gets trimmed in rendering.
1793
 * Assumes both arguments are part of the same array.
1794
 */
1795
static inline GlyphInfo *rewind_trailing_spaces(GlyphInfo *start1, GlyphInfo* start2)
1796
3
{
1797
3
    GlyphInfo *g = start2;
1798
6
    do {
1799
6
        --g;
1800
6
    } while ((g > start1) && (g->symbol == ' '));
1801
3
    return g;
1802
3
}
1803
1804
/*
1805
 * Shift soft linebreaks to balance out line lengths
1806
 * Does not change the linebreak count
1807
 * FIXME: implement style 0 and 3 correctly
1808
 */
1809
static void
1810
wrap_lines_rebalance(RenderContext *state, double max_text_width, char *unibrks)
1811
85.9k
{
1812
85.9k
    TextInfo *text_info = &state->text_info;
1813
85.9k
    int exit = 0;
1814
1815
85.9k
#define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
1816
171k
    while (!exit && state->wrap_style != 1) {
1817
85.9k
        exit = 1;
1818
85.9k
        GlyphInfo  *s1, *s2, *s3;
1819
85.9k
        s3 = text_info->glyphs;
1820
85.9k
        s1 = s2 = 0;
1821
659k
        for (int i = 0; i <= text_info->length; ++i) {
1822
659k
            GlyphInfo *cur = text_info->glyphs + i;
1823
659k
            if ((i == text_info->length) || cur->linebreak) {
1824
86.6k
                s1 = s2;
1825
86.6k
                s2 = s3;
1826
86.6k
                s3 = cur;
1827
86.6k
                if (s1 && (s2->linebreak == 1)) {       // have at least 2 lines, and linebreak is 'soft'
1828
3
                    double l1, l2, l1_new, l2_new;
1829
1830
                    // Find last word of line and trim surrounding whitespace before measuring
1831
                    // (whitespace ' ' will also get trimmed in rendering)
1832
3
                    GlyphInfo *w = rewind_trailing_spaces(s1, s2);
1833
3
                    GlyphInfo *e1_old = w;
1834
756
                    while ((w > s1) && (!ALLOWBREAK(w->symbol, w - text_info->glyphs))) {
1835
753
                        --w;
1836
753
                    }
1837
3
                    GlyphInfo *e1 = w;
1838
3
                    while ((e1 > s1) && (e1->symbol == ' ')) {
1839
0
                        --e1;
1840
0
                    }
1841
3
                    if (w->symbol == ' ')
1842
0
                        ++w;
1843
3
                    if (w == s1)
1844
3
                        continue; // Merging linebreaks is never beneficial
1845
1846
0
                    GlyphInfo *e2 = rewind_trailing_spaces(s2, s3);
1847
1848
0
                    l1 = d6_to_double(
1849
0
                        (e1_old->bbox.x_max + e1_old->pos.x) -
1850
0
                        (s1->bbox.x_min + s1->pos.x));
1851
0
                    l2 = d6_to_double(
1852
0
                        (e2->bbox.x_max + e2->pos.x) -
1853
0
                        (s2->bbox.x_min + s2->pos.x));
1854
0
                    l1_new = d6_to_double(
1855
0
                        (e1->bbox.x_max + e1->pos.x) -
1856
0
                        (s1->bbox.x_min + s1->pos.x));
1857
0
                    l2_new = d6_to_double(
1858
0
                        (e2->bbox.x_max + e2->pos.x) -
1859
0
                        (w->bbox.x_min + w->pos.x));
1860
1861
0
                    if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) {
1862
0
                        w->linebreak = 1;
1863
0
                        s2->linebreak = 0;
1864
0
                        s2 = w;
1865
0
                        exit = 0;
1866
0
                    }
1867
0
                }
1868
86.6k
            }
1869
659k
            if (i == text_info->length)
1870
85.9k
                break;
1871
659k
        }
1872
1873
85.9k
    }
1874
85.9k
    assert(text_info->n_lines >= 1);
1875
85.9k
#undef DIFF
1876
85.9k
}
1877
1878
static void
1879
wrap_lines_measure(RenderContext *state, char *unibrks)
1880
85.9k
{
1881
85.9k
    TextInfo *text_info = &state->text_info;
1882
85.9k
    int cur_line = 1;
1883
85.9k
    int i = 0;
1884
1885
89.8k
    while (i < text_info->length && text_info->glyphs[i].skip)
1886
3.85k
        ++i;
1887
85.9k
    double pen_shift_x = d6_to_double(-text_info->glyphs[i].pos.x);
1888
85.9k
    double pen_shift_y = 0.;
1889
1890
659k
    for (i = 0; i < text_info->length; ++i) {
1891
573k
        GlyphInfo *cur = text_info->glyphs + i;
1892
573k
        if (cur->linebreak) {
1893
653
            while (i < text_info->length && cur->skip && !FORCEBREAK(cur->symbol, i))
1894
0
                cur = text_info->glyphs + ++i;
1895
653
            double height =
1896
653
                text_info->lines[cur_line - 1].desc +
1897
653
                text_info->lines[cur_line].asc;
1898
653
            text_info->lines[cur_line - 1].len = i -
1899
653
                text_info->lines[cur_line - 1].offset;
1900
653
            text_info->lines[cur_line].offset = i;
1901
653
            cur_line++;
1902
653
            pen_shift_x = d6_to_double(-cur->pos.x);
1903
653
            pen_shift_y += height + state->renderer->settings.line_spacing;
1904
653
        }
1905
573k
        cur->pos.x += double_to_d6(pen_shift_x);
1906
573k
        cur->pos.y += double_to_d6(pen_shift_y);
1907
573k
    }
1908
85.9k
    text_info->lines[cur_line - 1].len =
1909
85.9k
        text_info->length - text_info->lines[cur_line - 1].offset;
1910
85.9k
}
1911
1912
#undef ALLOWBREAK
1913
#undef FORCEBREAK
1914
1915
/**
1916
 * \brief rearrange text between lines
1917
 * \param max_text_width maximal text line width in pixels
1918
 * The algo is similar to the one in libvo/sub.c:
1919
 * 1. Place text, wrapping it when current line is full
1920
 * 2. Try moving words from the end of a line to the beginning of the next one while it reduces
1921
 * the difference in lengths between this two lines.
1922
 * The result may not be optimal, but usually is good enough.
1923
 *
1924
 * FIXME: implement style 0 and 3 correctly
1925
 */
1926
static void
1927
wrap_lines_smart(RenderContext *state, double max_text_width)
1928
85.9k
{
1929
85.9k
    char *unibrks = NULL;
1930
1931
#ifdef CONFIG_UNIBREAK
1932
    ASS_Renderer *render_priv = state->renderer;
1933
    TextInfo *text_info = &state->text_info;
1934
    if (render_priv->track->parser_priv->feature_flags & FEATURE_MASK(ASS_FEATURE_WRAP_UNICODE)) {
1935
        unibrks = text_info->breaks;
1936
        set_linebreaks_utf32(
1937
            text_info->event_text, text_info->length,
1938
            render_priv->track->Language, unibrks);
1939
#if UNIBREAK_VERSION < 0x0500UL
1940
        // Prior to 5.0 libunibreaks always ended text with LINE_BREAKMUSTBREAK, matching
1941
        // Unicode spec, but messing with our text-overflow detection.
1942
        // Thus reevaluate the last char in a different context.
1943
        // (Later versions set either MUSTBREAK or the newly added INDETERMINATE)
1944
        unibrks[text_info->length - 1] = is_line_breakable(
1945
            text_info->event_text[text_info->length - 1],
1946
            ' ',
1947
            render_priv->track->Language
1948
        );
1949
#endif
1950
    }
1951
#endif
1952
1953
85.9k
    wrap_lines_naive(state, max_text_width, unibrks);
1954
85.9k
    wrap_lines_rebalance(state, max_text_width, unibrks);
1955
1956
85.9k
    trim_whitespace(state);
1957
85.9k
    measure_text(state);
1958
85.9k
    wrap_lines_measure(state, unibrks);
1959
85.9k
}
1960
1961
/**
1962
 * \brief Calculate base point for positioning and rotation
1963
 * \param bbox text bbox
1964
 * \param alignment alignment
1965
 * \param bx, by out: base point coordinates
1966
 */
1967
static void get_base_point(ASS_DRect *bbox, int alignment, double *bx, double *by)
1968
87.7k
{
1969
87.7k
    const int halign = alignment & 3;
1970
87.7k
    const int valign = alignment & 12;
1971
87.7k
    if (bx)
1972
87.7k
        switch (halign) {
1973
1.17k
        case HALIGN_LEFT:
1974
1.17k
            *bx = bbox->x_min;
1975
1.17k
            break;
1976
84.3k
        case HALIGN_CENTER:
1977
84.3k
            *bx = (bbox->x_max + bbox->x_min) / 2.0;
1978
84.3k
            break;
1979
139
        case HALIGN_RIGHT:
1980
139
            *bx = bbox->x_max;
1981
139
            break;
1982
87.7k
        }
1983
87.7k
    if (by)
1984
87.7k
        switch (valign) {
1985
393
        case VALIGN_TOP:
1986
393
            *by = bbox->y_min;
1987
393
            break;
1988
684
        case VALIGN_CENTER:
1989
684
            *by = (bbox->y_max + bbox->y_min) / 2.0;
1990
684
            break;
1991
86.6k
        case VALIGN_SUB:
1992
86.6k
            *by = bbox->y_max;
1993
86.6k
            break;
1994
87.7k
        }
1995
87.7k
}
1996
1997
/**
1998
 * \brief Adjust the glyph's font size and scale factors to ensure smooth
1999
 *  scaling and handle pathological font sizes. The main problem here is
2000
 *  freetype's grid fitting, which destroys animations by font size, or will
2001
 *  result in incorrect final text size if font sizes are very small and
2002
 *  scale factors very large. See Google Code issue #46.
2003
 * \param priv guess what
2004
 * \param glyph the glyph to be modified
2005
 */
2006
static void
2007
fix_glyph_scaling(ASS_Renderer *priv, GlyphInfo *glyph)
2008
569k
{
2009
569k
    double ft_size;
2010
569k
    if (priv->settings.hinting == ASS_HINTING_NONE) {
2011
        // arbitrary, not too small to prevent grid fitting rounding effects
2012
        // XXX: this is a rather crude hack
2013
569k
        ft_size = 256.0;
2014
569k
    } else {
2015
        // If hinting is enabled, we want to pass the real font size
2016
        // to freetype. Normalize scale_y to 1.0.
2017
0
        ft_size = glyph->scale_y * glyph->font_size;
2018
0
    }
2019
2020
569k
    if (!ft_size || !glyph->font_size)
2021
64.0k
        return;
2022
2023
505k
    double mul = glyph->font_size / ft_size;
2024
505k
    glyph->scale_fix = 1 / mul;
2025
505k
    glyph->scale_x *= mul;
2026
505k
    glyph->scale_y *= mul;
2027
505k
    glyph->font_size = ft_size;
2028
505k
}
2029
2030
// Initial run splitting based purely on the characters' styles
2031
static void split_style_runs(RenderContext *state)
2032
85.9k
{
2033
85.9k
    TextInfo *text_info = &state->text_info;
2034
85.9k
    Effect last_effect_type = text_info->glyphs[0].effect_type;
2035
85.9k
    text_info->glyphs[0].starts_new_run = true;
2036
573k
    for (int i = 1; i < text_info->length; i++) {
2037
487k
        GlyphInfo *info = text_info->glyphs + i;
2038
487k
        GlyphInfo *last = text_info->glyphs + (i - 1);
2039
487k
        Effect effect_type = info->effect_type;
2040
487k
        info->starts_new_run =
2041
487k
            info->effect_timing ||  // but ignore effect_skip_timing
2042
487k
            (effect_type != EF_NONE && effect_type != last_effect_type) ||
2043
487k
            info->drawing_text.str ||
2044
486k
            last->drawing_text.str ||
2045
486k
            !ass_string_equal(last->font->desc.family, info->font->desc.family) ||
2046
486k
            last->font->desc.vertical != info->font->desc.vertical ||
2047
486k
            last->font_size != info->font_size ||
2048
486k
            last->c[0] != info->c[0] ||
2049
486k
            last->c[1] != info->c[1] ||
2050
486k
            last->c[2] != info->c[2] ||
2051
486k
            last->c[3] != info->c[3] ||
2052
486k
            last->be != info->be ||
2053
486k
            last->blur != info->blur ||
2054
486k
            last->shadow_x != info->shadow_x ||
2055
486k
            last->shadow_y != info->shadow_y ||
2056
486k
            last->frx != info->frx ||
2057
486k
            last->fry != info->fry ||
2058
486k
            last->frz != info->frz ||
2059
486k
            last->fax != info->fax ||
2060
486k
            last->fay != info->fay ||
2061
486k
            last->scale_x != info->scale_x ||
2062
486k
            last->scale_y != info->scale_y ||
2063
486k
            last->border_style != info->border_style ||
2064
486k
            last->border_x != info->border_x ||
2065
486k
            last->border_y != info->border_y ||
2066
486k
            last->hspacing != info->hspacing ||
2067
486k
            last->italic != info->italic ||
2068
486k
            last->bold != info->bold ||
2069
486k
            ((last->flags ^ info->flags) & ~DECO_ROTATE);
2070
487k
        if (effect_type != EF_NONE)
2071
0
            last_effect_type = effect_type;
2072
487k
    }
2073
85.9k
}
2074
2075
// Parse event text.
2076
// Fill render_priv->text_info.
2077
static bool parse_events(RenderContext *state, ASS_Event *event)
2078
313k
{
2079
313k
    TextInfo *text_info = &state->text_info;
2080
313k
    ASS_Renderer *render_priv = state->renderer;
2081
2082
313k
    char *p = event->Text, *q;
2083
2084
    // Event parsing.
2085
886k
    while (true) {
2086
886k
        ASS_StringView drawing_text = {NULL, 0};
2087
2088
        // get next char, executing style override
2089
        // this affects render_context
2090
886k
        unsigned code = 0;
2091
1.02M
        while (*p) {
2092
714k
            if ((*p == '{') && (q = strchr(p, '}'))) {
2093
140k
                p = ass_parse_tags(state, p, q, 1., false);
2094
140k
                assert(*p == '}');
2095
140k
                p++;
2096
573k
            } else if (state->drawing_scale) {
2097
3.19k
                q = p;
2098
3.19k
                if (*p == '{')
2099
1.46k
                    q++;
2100
12.2k
                while ((*q != '{') && (*q != 0))
2101
9.05k
                    q++;
2102
3.19k
                drawing_text.str = p;
2103
3.19k
                drawing_text.len = q - p;
2104
3.19k
                code = 0xfffc; // object replacement character
2105
3.19k
                p = q;
2106
3.19k
                break;
2107
569k
            } else {
2108
569k
                code = ass_get_next_char(state, &p);
2109
569k
                break;
2110
569k
            }
2111
714k
        }
2112
2113
886k
        if (code == 0)
2114
313k
            break;
2115
2116
        // face could have been changed in get_next_char
2117
573k
        if (!state->font)
2118
0
            goto fail;
2119
2120
573k
        if (text_info->length >= text_info->max_glyphs) {
2121
            // Raise maximum number of glyphs
2122
0
            int new_max = 2 * FFMIN(FFMAX(text_info->max_glyphs, text_info->length / 2 + 1),
2123
0
                                    INT_MAX / 2);
2124
0
            if (text_info->length >= new_max)
2125
0
                goto fail;
2126
0
            if (!ASS_REALLOC_ARRAY(text_info->glyphs, new_max) ||
2127
0
                    !ASS_REALLOC_ARRAY(text_info->event_text, new_max) ||
2128
0
                    !ASS_REALLOC_ARRAY(text_info->breaks, new_max))
2129
0
                goto fail;
2130
0
            text_info->max_glyphs = new_max;
2131
0
        }
2132
2133
573k
        GlyphInfo *info = &text_info->glyphs[text_info->length];
2134
2135
        // Clear current GlyphInfo
2136
573k
        memset(info, 0, sizeof(GlyphInfo));
2137
2138
        // Parse drawing
2139
573k
        if (drawing_text.str) {
2140
3.19k
            info->drawing_text = drawing_text;
2141
3.19k
            info->drawing_scale = state->drawing_scale;
2142
3.19k
            info->drawing_pbo = state->pbo;
2143
3.19k
        }
2144
2145
        // Fill glyph information
2146
573k
        info->symbol = code;
2147
573k
        info->font = state->font;
2148
2.86M
        for (int i = 0; i < 4; i++)
2149
2.29M
            info->c[i] = state->c[i];
2150
2151
573k
        info->effect_type = state->effect_type;
2152
573k
        info->effect_timing = state->effect_timing;
2153
573k
        info->effect_skip_timing = state->effect_skip_timing;
2154
573k
        info->reset_effect = state->reset_effect;
2155
        // VSFilter compatibility: font glyphs use PlayResY scaling in both dimensions
2156
573k
        info->font_size =
2157
573k
            fabs(state->font_size * state->screen_scale_y);
2158
573k
        info->be = state->be;
2159
573k
        info->blur = state->blur;
2160
573k
        info->shadow_x = state->shadow_x;
2161
573k
        info->shadow_y = state->shadow_y;
2162
573k
        info->scale_x = state->scale_x;
2163
573k
        info->scale_y = state->scale_y;
2164
573k
        info->border_style = state->border_style;
2165
573k
        info->border_x = state->border_x;
2166
573k
        info->border_y = state->border_y;
2167
573k
        info->hspacing = state->hspacing;
2168
573k
        info->bold = state->bold;
2169
573k
        info->italic = state->italic;
2170
573k
        info->flags = state->flags;
2171
573k
        if (info->font->desc.vertical && code >= VERTICAL_LOWER_BOUND)
2172
0
            info->flags |= DECO_ROTATE;
2173
573k
        info->frx = state->frx;
2174
573k
        info->fry = state->fry;
2175
573k
        info->frz = state->frz;
2176
573k
        info->fax = state->fax;
2177
573k
        info->fay = state->fay;
2178
573k
        info->fade = state->fade;
2179
2180
573k
        info->hspacing_scaled = 0;
2181
573k
        info->scale_fix = 1;
2182
2183
573k
        if (!drawing_text.str) {
2184
569k
            info->hspacing_scaled = double_to_d6(info->hspacing *
2185
569k
                    state->screen_scale_x / render_priv->par_scale_x *
2186
569k
                    info->scale_x);
2187
569k
            fix_glyph_scaling(render_priv, info);
2188
569k
        }
2189
2190
573k
        text_info->length++;
2191
2192
573k
        state->effect_type = EF_NONE;
2193
573k
        state->effect_timing = 0;
2194
573k
        state->effect_skip_timing = 0;
2195
573k
        state->reset_effect = false;
2196
573k
    }
2197
2198
313k
    return true;
2199
2200
0
fail:
2201
0
    free_render_context(state);
2202
0
    return false;
2203
313k
}
2204
2205
// Process render_priv->text_info and load glyph outlines.
2206
static void retrieve_glyphs(RenderContext *state)
2207
85.9k
{
2208
85.9k
    GlyphInfo *glyphs = state->text_info.glyphs;
2209
85.9k
    int i;
2210
2211
659k
    for (i = 0; i < state->text_info.length; i++) {
2212
573k
        GlyphInfo *info = glyphs + i;
2213
573k
        do {
2214
573k
            get_outline_glyph(state, info);
2215
573k
            info = info->next;
2216
573k
        } while (info);
2217
573k
        info = glyphs + i;
2218
2219
        // Add additional space after italic to non-italic style changes
2220
573k
        if (i && glyphs[i - 1].italic && !info->italic) {
2221
0
            int back = i - 1;
2222
0
            GlyphInfo *og = &glyphs[back];
2223
0
            while (back && og->bbox.x_max - og->bbox.x_min == 0
2224
0
                    && og->italic)
2225
0
                og = &glyphs[--back];
2226
0
            if (og->bbox.x_max > og->cluster_advance.x)
2227
0
                og->cluster_advance.x = og->bbox.x_max;
2228
0
        }
2229
2230
        // add horizontal letter spacing
2231
573k
        info->cluster_advance.x += info->hspacing_scaled;
2232
573k
    }
2233
85.9k
}
2234
2235
// Preliminary layout (for line wrapping)
2236
static void preliminary_layout(RenderContext *state)
2237
85.9k
{
2238
85.9k
    ASS_Vector pen = { 0, 0 };
2239
659k
    for (int i = 0; i < state->text_info.length; i++) {
2240
573k
        GlyphInfo *info = state->text_info.glyphs + i;
2241
573k
        ASS_Vector cluster_pen = pen;
2242
573k
        do {
2243
573k
            info->pos.x = cluster_pen.x;
2244
573k
            info->pos.y = cluster_pen.y;
2245
2246
573k
            cluster_pen.x += info->advance.x;
2247
573k
            cluster_pen.y += info->advance.y;
2248
2249
573k
            info = info->next;
2250
573k
        } while (info);
2251
573k
        info = state->text_info.glyphs + i;
2252
573k
        pen.x += info->cluster_advance.x;
2253
573k
        pen.y += info->cluster_advance.y;
2254
573k
    }
2255
85.9k
}
2256
2257
// Reorder text into visual order
2258
static void reorder_text(RenderContext *state)
2259
85.9k
{
2260
85.9k
    ASS_Renderer *render_priv = state->renderer;
2261
85.9k
    TextInfo *text_info = &state->text_info;
2262
85.9k
    FriBidiStrIndex *cmap = ass_shaper_reorder(state->shaper, text_info);
2263
85.9k
    if (!cmap) {
2264
0
        ass_msg(render_priv->library, MSGL_ERR, "Failed to reorder text");
2265
0
        ass_shaper_cleanup(state->shaper, text_info);
2266
0
        free_render_context(state);
2267
0
        return;
2268
0
    }
2269
2270
    // Reposition according to the map
2271
85.9k
    ASS_Vector pen = { 0, 0 };
2272
85.9k
    int lineno = 1;
2273
659k
    for (int i = 0; i < text_info->length; i++) {
2274
573k
        GlyphInfo *info = text_info->glyphs + cmap[i];
2275
573k
        if (text_info->glyphs[i].linebreak) {
2276
653
            pen.x = 0;
2277
653
            pen.y += double_to_d6(text_info->lines[lineno-1].desc);
2278
653
            pen.y += double_to_d6(text_info->lines[lineno].asc);
2279
653
            pen.y += double_to_d6(render_priv->settings.line_spacing);
2280
653
            lineno++;
2281
653
        }
2282
573k
        if (info->skip)
2283
3.85k
            continue;
2284
569k
        ASS_Vector cluster_pen = pen;
2285
569k
        pen.x += info->cluster_advance.x;
2286
569k
        pen.y += info->cluster_advance.y;
2287
1.13M
        while (info) {
2288
569k
            info->pos.x = info->offset.x + cluster_pen.x;
2289
569k
            info->pos.y = info->offset.y + cluster_pen.y;
2290
569k
            cluster_pen.x += info->advance.x;
2291
569k
            cluster_pen.y += info->advance.y;
2292
569k
            info = info->next;
2293
569k
        }
2294
569k
    }
2295
85.9k
}
2296
2297
static void apply_baseline_shear(RenderContext *state)
2298
85.9k
{
2299
85.9k
    ASS_Renderer *render_priv = state->renderer;
2300
85.9k
    TextInfo *text_info = &state->text_info;
2301
85.9k
    FriBidiStrIndex *cmap = ass_shaper_get_reorder_map(state->shaper);
2302
85.9k
    int32_t shear = 0;
2303
85.9k
    bool whole_text_layout =
2304
85.9k
        render_priv->track->parser_priv->feature_flags &
2305
85.9k
        FEATURE_MASK(ASS_FEATURE_WHOLE_TEXT_LAYOUT);
2306
659k
    for (int i = 0; i < text_info->length; i++) {
2307
573k
        GlyphInfo *info = text_info->glyphs + cmap[i];
2308
573k
        if (text_info->glyphs[i].linebreak ||
2309
572k
            (!whole_text_layout && text_info->glyphs[i].starts_new_run))
2310
88.9k
            shear = 0;
2311
573k
        if (!info->scale_x || !info->scale_y)
2312
9.14k
            info->skip = true;
2313
573k
        if (info->skip)
2314
13.0k
            continue;
2315
560k
        double fay = info->fay / info->scale_x * info->scale_y;
2316
1.12M
        for (GlyphInfo *cur = info; cur; cur = cur->next) {
2317
560k
            cur->pos.y += shear + fay * cur->offset.x;
2318
560k
            shear += fay * cur->advance.x;
2319
560k
        }
2320
560k
    }
2321
85.9k
}
2322
2323
static void align_lines(RenderContext *state, double max_text_width)
2324
85.9k
{
2325
85.9k
    TextInfo *text_info = &state->text_info;
2326
85.9k
    GlyphInfo *glyphs = text_info->glyphs;
2327
85.9k
    int i, j;
2328
85.9k
    double width = 0;
2329
85.9k
    int last_break = -1;
2330
85.9k
    int halign = state->alignment & 3;
2331
85.9k
    int justify = state->justify;
2332
85.9k
    double max_width = 0;
2333
2334
85.9k
    if (state->evt_type & EVENT_HSCROLL) {
2335
252
        justify = halign;
2336
252
        halign = HALIGN_LEFT;
2337
252
    }
2338
2339
745k
    for (i = 0; i <= text_info->length; ++i) {   // (text_info->length + 1) is the end of the last line
2340
659k
        if ((i == text_info->length) || glyphs[i].linebreak) {
2341
86.6k
            max_width = FFMAX(max_width,width);
2342
86.6k
            width = 0;
2343
86.6k
        }
2344
659k
        if (i < text_info->length && !glyphs[i].skip &&
2345
569k
                glyphs[i].symbol != '\n' && glyphs[i].symbol != 0) {
2346
569k
            width += d6_to_double(glyphs[i].cluster_advance.x);
2347
569k
        }
2348
659k
    }
2349
745k
    for (i = 0; i <= text_info->length; ++i) {   // (text_info->length + 1) is the end of the last line
2350
659k
        if ((i == text_info->length) || glyphs[i].linebreak) {
2351
86.6k
            double shift = 0;
2352
86.6k
            if (halign == HALIGN_LEFT) {    // left aligned, no action
2353
1.42k
                if (justify == ASS_JUSTIFY_RIGHT) {
2354
0
                    shift = max_width - width;
2355
1.42k
                } else if (justify == ASS_JUSTIFY_CENTER) {
2356
252
                    shift = (max_width - width) / 2.0;
2357
1.17k
                } else {
2358
1.17k
                    shift = 0;
2359
1.17k
                }
2360
85.2k
            } else if (halign == HALIGN_RIGHT) {    // right aligned
2361
139
                if (justify == ASS_JUSTIFY_LEFT) {
2362
0
                    shift = max_text_width - max_width;
2363
139
                } else if (justify == ASS_JUSTIFY_CENTER) {
2364
0
                    shift = max_text_width - max_width + (max_width - width) / 2.0;
2365
139
                } else {
2366
139
                    shift = max_text_width - width;
2367
139
                }
2368
85.0k
            } else if (halign == HALIGN_CENTER) {   // centered
2369
83.3k
                if (justify == ASS_JUSTIFY_LEFT) {
2370
0
                    shift = (max_text_width - max_width) / 2.0;
2371
83.3k
                } else if (justify == ASS_JUSTIFY_RIGHT) {
2372
0
                    shift = (max_text_width - max_width) / 2.0 + max_width - width;
2373
83.3k
                } else {
2374
83.3k
                    shift = (max_text_width - width) / 2.0;
2375
83.3k
                }
2376
83.3k
            }
2377
659k
            for (j = last_break + 1; j < i; ++j) {
2378
573k
                GlyphInfo *info = glyphs + j;
2379
1.14M
                while (info) {
2380
573k
                    info->pos.x += double_to_d6(shift);
2381
573k
                    info = info->next;
2382
573k
                }
2383
573k
            }
2384
86.6k
            last_break = i - 1;
2385
86.6k
            width = 0;
2386
86.6k
        }
2387
659k
        if (i < text_info->length && !glyphs[i].skip &&
2388
569k
                glyphs[i].symbol != '\n' && glyphs[i].symbol != 0) {
2389
569k
            width += d6_to_double(glyphs[i].cluster_advance.x);
2390
569k
        }
2391
659k
    }
2392
85.9k
}
2393
2394
static void calculate_rotation_params(RenderContext *state, ASS_DRect *bbox,
2395
                                      double device_x, double device_y)
2396
85.9k
{
2397
85.9k
    ASS_Renderer *render_priv = state->renderer;
2398
85.9k
    ASS_DVector center;
2399
85.9k
    if (state->have_origin) {
2400
33
        center.x = x2scr_pos(render_priv, state->org_x);
2401
33
        center.y = y2scr_pos(render_priv, state->org_y);
2402
85.9k
    } else {
2403
85.9k
        double bx = 0., by = 0.;
2404
85.9k
        get_base_point(bbox, state->alignment, &bx, &by);
2405
85.9k
        center.x = device_x + bx;
2406
85.9k
        center.y = device_y + by;
2407
85.9k
    }
2408
2409
85.9k
    TextInfo *text_info = &state->text_info;
2410
659k
    for (int i = 0; i < text_info->length; i++) {
2411
573k
        GlyphInfo *info = text_info->glyphs + i;
2412
1.14M
        while (info) {
2413
573k
            info->shift.x = info->pos.x + double_to_d6(device_x - center.x +
2414
573k
                    info->shadow_x * state->border_scale_x /
2415
573k
                    render_priv->par_scale_x);
2416
573k
            info->shift.y = info->pos.y + double_to_d6(device_y - center.y +
2417
573k
                    info->shadow_y * state->border_scale_y);
2418
573k
            info = info->next;
2419
573k
        }
2420
573k
    }
2421
85.9k
}
2422
2423
2424
static int quantize_blur(double radius, int32_t *shadow_mask)
2425
167k
{
2426
    // Gaussian filter kernel (1D):
2427
    // G(x, r2) = exp(-x^2 / (2 * r2)) / sqrt(2 * pi * r2),
2428
    // position unit is 1/64th of pixel, r = 64 * radius, r2 = r^2.
2429
2430
    // Difference between kernels with different but near r2:
2431
    // G(x, r2 + dr2) - G(x, r2) ~= dr2 * G(x, r2) * (x^2 - r2) / (2 * r2^2).
2432
    // Maximal possible error relative to full pixel value is half of
2433
    // integral (from -inf to +inf) of absolute value of that difference.
2434
    // E_max ~= dr2 / 2 * integral(G(x, r2) * |x^2 - r2| / (2 * r2^2), x)
2435
    //  = dr2 / (4 * r2) * integral(G(y, 1) * |y^2 - 1|, y)
2436
    //  = dr2 / (4 * r2) * 4 / sqrt(2 * pi * e)
2437
    //  ~ dr2 / (4 * r2) ~= dr / (2 * r).
2438
    // E_max ~ BLUR_PRECISION / 2 as we have 2 dimensions.
2439
2440
    // To get discretized blur radius solve the following
2441
    // differential equation (n--quantization index):
2442
    // dr(n) / dn = BLUR_PRECISION * r + POSITION_PRECISION, r(0) = 0,
2443
    // r(n) = (exp(BLUR_PRECISION * n) - 1) * POSITION_PRECISION / BLUR_PRECISION,
2444
    // n = log(1 + r * BLUR_PRECISION / POSITION_PRECISION) / BLUR_PRECISION.
2445
2446
    // To get shadow offset quantization estimate difference of
2447
    // G(x + dx, r2) - G(x, r2) ~= dx * G(x, r2) * (-x / r2).
2448
    // E_max ~= dx / 2 * integral(G(x, r2) * |x| / r2, x)
2449
    //  = dx / sqrt(2 * pi * r2) ~ dx / (2 * r).
2450
    // 2^ord ~ dx ~ BLUR_PRECISION * r + POSITION_PRECISION.
2451
2452
167k
    const double scale = 64 * BLUR_PRECISION / POSITION_PRECISION;
2453
167k
    radius *= scale;
2454
2455
167k
    int ord;
2456
    // ord = floor(log2(BLUR_PRECISION * r + POSITION_PRECISION))
2457
    //     = floor(log2(64 * radius * BLUR_PRECISION + POSITION_PRECISION))
2458
    //     = floor(log2((radius * scale + 1) * POSITION_PRECISION)),
2459
    // floor(log2(x)) = frexp(x) - 1 = frexp(x / 2).
2460
167k
    frexp((1 + radius) * (POSITION_PRECISION / 2), &ord);
2461
167k
    *shadow_mask = ((uint32_t) 1 << ord) - 1;
2462
167k
    return ass_lrint(log1p(radius) / BLUR_PRECISION);
2463
167k
}
2464
2465
static double restore_blur(int qblur)
2466
4.89k
{
2467
4.89k
    const double scale = 64 * BLUR_PRECISION / POSITION_PRECISION;
2468
4.89k
    double sigma = expm1(BLUR_PRECISION * qblur) / scale;
2469
4.89k
    return sigma * sigma;
2470
4.89k
}
2471
2472
// Convert glyphs to bitmaps, combine them, apply blur, generate shadows.
2473
static void render_and_combine_glyphs(RenderContext *state,
2474
                                      double device_x, double device_y)
2475
85.9k
{
2476
85.9k
    ASS_Renderer *render_priv = state->renderer;
2477
85.9k
    TextInfo *text_info = &state->text_info;
2478
85.9k
    int left = render_priv->settings.left_margin;
2479
85.9k
    device_x = (device_x - left) * render_priv->par_scale_x + left;
2480
85.9k
    unsigned nb_bitmaps = 0;
2481
85.9k
    bool new_run = true;
2482
85.9k
    CombinedBitmapInfo *combined_info = text_info->combined_bitmaps;
2483
85.9k
    CombinedBitmapInfo *current_info = NULL;
2484
85.9k
    ASS_DVector offset;
2485
659k
    for (int i = 0; i < text_info->length; i++) {
2486
573k
        GlyphInfo *info = text_info->glyphs + i;
2487
573k
        if (info->starts_new_run) new_run = true;
2488
573k
        if (info->skip)
2489
13.0k
            continue;
2490
2491
1.12M
        for (; info; info = info->next) {
2492
560k
            int flags = 0;
2493
560k
            if (info->border_style == 3)
2494
0
                flags |= FILTER_BORDER_STYLE_3;
2495
560k
            if (info->border_x || info->border_y)
2496
494k
                flags |= FILTER_NONZERO_BORDER;
2497
560k
            if (info->shadow_x || info->shadow_y)
2498
495k
                flags |= FILTER_NONZERO_SHADOW;
2499
560k
            if (flags & FILTER_NONZERO_SHADOW &&
2500
495k
                (info->effect_type == EF_KARAOKE_KF ||
2501
486k
                 info->effect_type == EF_KARAOKE_KO ||
2502
485k
                 _a(info->c[0]) != 0xFF ||
2503
1.72k
                 info->border_style == 3))
2504
493k
                flags |= FILTER_FILL_IN_SHADOW;
2505
560k
            if (!(flags & FILTER_NONZERO_BORDER) &&
2506
65.3k
                !(flags & FILTER_FILL_IN_SHADOW))
2507
63.9k
                flags &= ~FILTER_NONZERO_SHADOW;
2508
560k
            if ((flags & FILTER_NONZERO_BORDER &&
2509
494k
                 _a(info->c[0]) == 0 &&
2510
492k
                 _a(info->c[1]) == 0 &&
2511
492k
                 info->fade == 0) ||
2512
68.5k
                info->border_style == 3)
2513
491k
                flags |= FILTER_FILL_IN_BORDER;
2514
2515
560k
            if (new_run) {
2516
83.8k
                if (nb_bitmaps >= text_info->max_bitmaps) {
2517
1
                    size_t new_size = 2 * text_info->max_bitmaps;
2518
1
                    if (!ASS_REALLOC_ARRAY(text_info->combined_bitmaps, new_size))
2519
0
                        continue;
2520
2521
1
                    text_info->max_bitmaps = new_size;
2522
1
                    combined_info = text_info->combined_bitmaps;
2523
1
                }
2524
83.8k
                current_info = &combined_info[nb_bitmaps];
2525
2526
83.8k
                memcpy(&current_info->c, &info->c, sizeof(info->c));
2527
419k
                for (int i = 0; i < 4; i++)
2528
335k
                    ass_apply_fade(&current_info->c[i], info->fade);
2529
2530
83.8k
                current_info->effect_type = info->effect_type;
2531
83.8k
                current_info->effect_timing = info->effect_timing;
2532
83.8k
                current_info->leftmost_x = OUTLINE_MAX;
2533
2534
83.8k
                FilterDesc *filter = &current_info->filter;
2535
83.8k
                filter->flags = flags;
2536
83.8k
                filter->be = info->be;
2537
2538
83.8k
                int32_t shadow_mask_x, shadow_mask_y;
2539
83.8k
                double blur_radius_scale = 2 / sqrt(log(256));
2540
83.8k
                double blur_scale_x = state->blur_scale_x * blur_radius_scale;
2541
83.8k
                double blur_scale_y = state->blur_scale_y * blur_radius_scale;
2542
83.8k
                filter->blur_x = quantize_blur(info->blur * blur_scale_x, &shadow_mask_x);
2543
83.8k
                filter->blur_y = quantize_blur(info->blur * blur_scale_y, &shadow_mask_y);
2544
83.8k
                if (flags & FILTER_NONZERO_SHADOW) {
2545
82.0k
                    int32_t x = double_to_d6(info->shadow_x * state->border_scale_x);
2546
82.0k
                    int32_t y = double_to_d6(info->shadow_y * state->border_scale_y);
2547
82.0k
                    filter->shadow.x = (x + (shadow_mask_x >> 1)) & ~shadow_mask_x;
2548
82.0k
                    filter->shadow.y = (y + (shadow_mask_y >> 1)) & ~shadow_mask_y;
2549
82.0k
                } else
2550
1.81k
                    filter->shadow.x = filter->shadow.y = 0;
2551
2552
83.8k
                current_info->x = current_info->y = INT_MAX;
2553
83.8k
                current_info->bm = current_info->bm_o = current_info->bm_s = NULL;
2554
83.8k
                current_info->image = NULL;
2555
2556
83.8k
                current_info->bitmap_count = current_info->max_bitmap_count = 0;
2557
83.8k
                current_info->bitmaps = malloc(MAX_SUB_BITMAPS_INITIAL * sizeof(BitmapRef));
2558
83.8k
                if (!current_info->bitmaps)
2559
0
                    continue;
2560
2561
83.8k
                current_info->max_bitmap_count = MAX_SUB_BITMAPS_INITIAL;
2562
2563
83.8k
                nb_bitmaps++;
2564
83.8k
                new_run = false;
2565
83.8k
            }
2566
560k
            assert(current_info);
2567
2568
560k
            ASS_Vector pos, pos_o;
2569
560k
            info->pos.x = double_to_d6(device_x + d6_to_double(info->pos.x) * render_priv->par_scale_x);
2570
560k
            info->pos.y = double_to_d6(device_y) + info->pos.y;
2571
560k
            get_bitmap_glyph(state, info, &current_info->leftmost_x, &pos, &pos_o,
2572
560k
                             &offset, !current_info->bitmap_count, flags);
2573
2574
560k
            if (!info->bm && !info->bm_o)
2575
82.5k
                continue;
2576
2577
477k
            if (current_info->bitmap_count >= current_info->max_bitmap_count) {
2578
420
                size_t new_size = 2 * current_info->max_bitmap_count;
2579
420
                if (!ASS_REALLOC_ARRAY(current_info->bitmaps, new_size))
2580
0
                    continue;
2581
2582
420
                current_info->max_bitmap_count = new_size;
2583
420
            }
2584
477k
            current_info->bitmaps[current_info->bitmap_count].bm   = info->bm;
2585
477k
            current_info->bitmaps[current_info->bitmap_count].bm_o = info->bm_o;
2586
477k
            current_info->bitmaps[current_info->bitmap_count].pos   = pos;
2587
477k
            current_info->bitmaps[current_info->bitmap_count].pos_o = pos_o;
2588
477k
            current_info->bitmap_count++;
2589
2590
477k
            current_info->x = FFMIN(current_info->x, pos.x);
2591
477k
            current_info->y = FFMIN(current_info->y, pos.y);
2592
477k
        }
2593
560k
    }
2594
2595
169k
    for (int i = 0; i < nb_bitmaps; i++) {
2596
83.8k
        CombinedBitmapInfo *info = &combined_info[i];
2597
83.8k
        if (!info->bitmap_count) {
2598
5.80k
            free(info->bitmaps);
2599
5.80k
            continue;
2600
5.80k
        }
2601
2602
78.0k
        if (info->effect_type == EF_KARAOKE_KF)
2603
1.49k
            info->effect_timing = lround(d6_to_double(info->leftmost_x) +
2604
1.49k
                d6_to_double(info->effect_timing) * render_priv->par_scale_x);
2605
2606
555k
        for (int j = 0; j < info->bitmap_count; j++) {
2607
477k
            info->bitmaps[j].pos.x -= info->x;
2608
477k
            info->bitmaps[j].pos.y -= info->y;
2609
477k
            info->bitmaps[j].pos_o.x -= info->x;
2610
477k
            info->bitmaps[j].pos_o.y -= info->y;
2611
477k
        }
2612
2613
78.0k
        CompositeHashKey key;
2614
78.0k
        key.filter = info->filter;
2615
78.0k
        key.bitmap_count = info->bitmap_count;
2616
78.0k
        key.bitmaps = info->bitmaps;
2617
78.0k
        CompositeHashValue *val = ass_cache_get(render_priv->cache.composite_cache, &key, render_priv);
2618
78.0k
        if (!val)
2619
0
            continue;
2620
2621
78.0k
        if (val->bm.buffer)
2622
78.0k
            info->bm = &val->bm;
2623
78.0k
        if (val->bm_o.buffer)
2624
77.5k
            info->bm_o = &val->bm_o;
2625
78.0k
        if (val->bm_s.buffer)
2626
77.9k
            info->bm_s = &val->bm_s;
2627
78.0k
        info->image = val;
2628
78.0k
        continue;
2629
78.0k
    }
2630
2631
85.9k
    text_info->n_bitmaps = nb_bitmaps;
2632
85.9k
}
2633
2634
static inline void rectangle_combine(ASS_Rect *rect, const Bitmap *bm, ASS_Vector pos)
2635
20.1k
{
2636
20.1k
    pos.x += bm->left;
2637
20.1k
    pos.y += bm->top;
2638
20.1k
    rectangle_update(rect, pos.x, pos.y, pos.x + bm->w, pos.y + bm->h);
2639
20.1k
}
2640
2641
/*
2642
 * To find these values, simulate blur on the border between two
2643
 * half-planes, one zero-filled (background) and the other filled
2644
 * with the maximum supported value (foreground). Keep incrementing
2645
 * the \be argument. The necessary padding is the distance by which
2646
 * the blurred foreground image extends beyond the original border
2647
 * and into the background. Initially it increases along with \be,
2648
 * but very soon it grinds to a halt. At some point, the blurred
2649
 * image actually reaches a stationary point and stays unchanged
2650
 * forever after, simply _shifting_ by one pixel for each \be
2651
 * step--moving in the direction of the non-zero half-plane and
2652
 * thus decreasing the necessary padding (although the large
2653
 * padding is still needed for intermediate results). In practice,
2654
 * images are finite rather than infinite like half-planes, but
2655
 * this can only decrease the required padding. Half-planes filled
2656
 * with extreme values are the theoretical limit of the worst case.
2657
 * Make sure to use the right pixel value range in the simulation!
2658
 */
2659
int ass_be_padding(int be)
2660
2.44k
{
2661
2.44k
    if (be <= 3)
2662
2.43k
        return be;
2663
10
    if (be <= 7)
2664
0
        return 4;
2665
10
    return 5;
2666
10
}
2667
2668
2669
size_t ass_composite_construct(void *key, void *value, void *priv)
2670
2.44k
{
2671
2.44k
    ASS_Renderer *render_priv = priv;
2672
2.44k
    CompositeHashKey *k = key;
2673
2.44k
    CompositeHashValue *v = value;
2674
2.44k
    memset(v, 0, sizeof(*v));
2675
2676
2.44k
    ASS_Rect rect, rect_o;
2677
2.44k
    rectangle_reset(&rect);
2678
2.44k
    rectangle_reset(&rect_o);
2679
2680
2.44k
    size_t n_bm = 0, n_bm_o = 0;
2681
2.44k
    BitmapRef *last = NULL, *last_o = NULL;
2682
12.5k
    for (int i = 0; i < k->bitmap_count; i++) {
2683
10.0k
        BitmapRef *ref = &k->bitmaps[i];
2684
10.0k
        if (ref->bm) {
2685
10.0k
            rectangle_combine(&rect, ref->bm, ref->pos);
2686
10.0k
            last = ref;
2687
10.0k
            n_bm++;
2688
10.0k
        }
2689
10.0k
        if (ref->bm_o) {
2690
10.0k
            rectangle_combine(&rect_o, ref->bm_o, ref->pos_o);
2691
10.0k
            last_o = ref;
2692
10.0k
            n_bm_o++;
2693
10.0k
        }
2694
10.0k
    }
2695
2696
2.44k
    int bord = ass_be_padding(k->filter.be);
2697
2.44k
    if (!bord && n_bm == 1) {
2698
962
        ass_copy_bitmap(&render_priv->engine, &v->bm, last->bm);
2699
962
        v->bm.left += last->pos.x;
2700
962
        v->bm.top  += last->pos.y;
2701
1.48k
    } else if (n_bm && ass_alloc_bitmap(&render_priv->engine, &v->bm,
2702
1.48k
                                        rect.x_max - rect.x_min + 2 * bord,
2703
1.48k
                                        rect.y_max - rect.y_min + 2 * bord,
2704
1.48k
                                        true)) {
2705
1.48k
        Bitmap *dst = &v->bm;
2706
1.48k
        dst->left = rect.x_min - bord;
2707
1.48k
        dst->top  = rect.y_min - bord;
2708
10.5k
        for (int i = 0; i < k->bitmap_count; i++) {
2709
9.10k
            Bitmap *src = k->bitmaps[i].bm;
2710
9.10k
            if (!src)
2711
0
                continue;
2712
9.10k
            int x = k->bitmaps[i].pos.x + src->left - dst->left;
2713
9.10k
            int y = k->bitmaps[i].pos.y + src->top  - dst->top;
2714
9.10k
            assert(x >= 0 && x + src->w <= dst->w);
2715
9.10k
            assert(y >= 0 && y + src->h <= dst->h);
2716
9.10k
            unsigned char *buf = dst->buffer + y * dst->stride + x;
2717
9.10k
            render_priv->engine.add_bitmaps(buf, dst->stride,
2718
9.10k
                                            src->buffer, src->stride,
2719
9.10k
                                            src->w, src->h);
2720
9.10k
        }
2721
1.48k
    }
2722
2.44k
    if (!bord && n_bm_o == 1) {
2723
956
        ass_copy_bitmap(&render_priv->engine, &v->bm_o, last_o->bm_o);
2724
956
        v->bm_o.left += last_o->pos_o.x;
2725
956
        v->bm_o.top  += last_o->pos_o.y;
2726
1.48k
    } else if (n_bm_o && ass_alloc_bitmap(&render_priv->engine, &v->bm_o,
2727
1.47k
                                          rect_o.x_max - rect_o.x_min + 2 * bord,
2728
1.47k
                                          rect_o.y_max - rect_o.y_min + 2 * bord,
2729
1.47k
                                          true)) {
2730
1.47k
        Bitmap *dst = &v->bm_o;
2731
1.47k
        dst->left = rect_o.x_min - bord;
2732
1.47k
        dst->top  = rect_o.y_min - bord;
2733
10.5k
        for (int i = 0; i < k->bitmap_count; i++) {
2734
9.08k
            Bitmap *src = k->bitmaps[i].bm_o;
2735
9.08k
            if (!src)
2736
0
                continue;
2737
9.08k
            int x = k->bitmaps[i].pos_o.x + src->left - dst->left;
2738
9.08k
            int y = k->bitmaps[i].pos_o.y + src->top  - dst->top;
2739
9.08k
            assert(x >= 0 && x + src->w <= dst->w);
2740
9.08k
            assert(y >= 0 && y + src->h <= dst->h);
2741
9.08k
            unsigned char *buf = dst->buffer + y * dst->stride + x;
2742
9.08k
            render_priv->engine.add_bitmaps(buf, dst->stride,
2743
9.08k
                                            src->buffer, src->stride,
2744
9.08k
                                            src->w, src->h);
2745
9.08k
        }
2746
1.47k
    }
2747
2748
2.44k
    int flags = k->filter.flags;
2749
2.44k
    double r2x = restore_blur(k->filter.blur_x);
2750
2.44k
    double r2y = restore_blur(k->filter.blur_y);
2751
2.44k
    if (!(flags & FILTER_NONZERO_BORDER) || (flags & FILTER_BORDER_STYLE_3))
2752
11
        ass_synth_blur(&render_priv->engine, &v->bm, k->filter.be, r2x, r2y);
2753
2.44k
    ass_synth_blur(&render_priv->engine, &v->bm_o, k->filter.be, r2x, r2y);
2754
2755
2.44k
    if (!(flags & FILTER_FILL_IN_BORDER) && !(flags & FILTER_FILL_IN_SHADOW))
2756
14
        ass_fix_outline(&v->bm, &v->bm_o);
2757
2758
2.44k
    if (flags & FILTER_NONZERO_SHADOW) {
2759
2.42k
        if (flags & FILTER_NONZERO_BORDER) {
2760
2.41k
            ass_copy_bitmap(&render_priv->engine, &v->bm_s, &v->bm_o);
2761
2.41k
            if ((flags & FILTER_FILL_IN_BORDER) && !(flags & FILTER_FILL_IN_SHADOW))
2762
0
                ass_fix_outline(&v->bm, &v->bm_s);
2763
2.41k
        } else if (flags & FILTER_BORDER_STYLE_3) {
2764
0
            v->bm_s = v->bm_o;
2765
0
            memset(&v->bm_o, 0, sizeof(v->bm_o));
2766
11
        } else {
2767
11
            ass_copy_bitmap(&render_priv->engine, &v->bm_s, &v->bm);
2768
11
        }
2769
2770
        // Works right even for negative offsets
2771
        // '>>' rounds toward negative infinity, '&' returns correct remainder
2772
2.42k
        v->bm_s.left += k->filter.shadow.x >> 6;
2773
2.42k
        v->bm_s.top  += k->filter.shadow.y >> 6;
2774
2.42k
        ass_shift_bitmap(&v->bm_s, k->filter.shadow.x & SUBPIXEL_MASK, k->filter.shadow.y & SUBPIXEL_MASK);
2775
2.42k
    }
2776
2777
2.44k
    if ((flags & FILTER_FILL_IN_SHADOW) && !(flags & FILTER_FILL_IN_BORDER))
2778
44
        ass_fix_outline(&v->bm, &v->bm_o);
2779
2780
2.44k
    return sizeof(CompositeHashKey) + sizeof(CompositeHashValue) +
2781
2.44k
        k->bitmap_count * sizeof(BitmapRef) +
2782
2.44k
        bitmap_size(&v->bm) + bitmap_size(&v->bm_o) + bitmap_size(&v->bm_s);
2783
2.44k
}
2784
2785
static void add_background(RenderContext *state, EventImages *event_images)
2786
0
{
2787
0
    ASS_Renderer *render_priv = state->renderer;
2788
0
    int size_x = state->shadow_x > 0 ?
2789
0
        lround(state->shadow_x * state->border_scale_x) : 0;
2790
0
    int size_y = state->shadow_y > 0 ?
2791
0
        lround(state->shadow_y * state->border_scale_y) : 0;
2792
0
    int left    = event_images->left - size_x;
2793
0
    int top     = event_images->top  - size_y;
2794
0
    int right   = event_images->left + event_images->width  + size_x;
2795
0
    int bottom  = event_images->top  + event_images->height + size_y;
2796
0
    left        = FFMINMAX(left,   0, render_priv->width);
2797
0
    top         = FFMINMAX(top,    0, render_priv->height);
2798
0
    right       = FFMINMAX(right,  0, render_priv->width);
2799
0
    bottom      = FFMINMAX(bottom, 0, render_priv->height);
2800
0
    int w = right - left;
2801
0
    int h = bottom - top;
2802
0
    if (w < 1 || h < 1)
2803
0
        return;
2804
0
    void *nbuffer = ass_aligned_alloc(1, w * h, false);
2805
0
    if (!nbuffer)
2806
0
        return;
2807
0
    memset(nbuffer, 0xFF, w * h);
2808
0
    uint32_t clr = state->c[3];
2809
0
    ass_apply_fade(&clr, state->fade);
2810
0
    ASS_Image *img = my_draw_bitmap(nbuffer, w, h, w, left, top,
2811
0
                                    clr, NULL);
2812
0
    if (img) {
2813
0
        img->next = event_images->imgs;
2814
0
        event_images->imgs = img;
2815
0
    }
2816
0
}
2817
2818
/**
2819
 * \brief Main ass rendering function, glues everything together
2820
 * \param event event to render
2821
 * \param event_images struct containing resulting images, will also be initialized
2822
 * Process event, appending resulting ASS_Image's to images_root.
2823
 */
2824
static bool
2825
ass_render_event(RenderContext *state, ASS_Event *event,
2826
                 EventImages *event_images)
2827
313k
{
2828
313k
    ASS_Renderer *render_priv = state->renderer;
2829
313k
    if (event->Style >= render_priv->track->n_styles) {
2830
0
        ass_msg(render_priv->library, MSGL_WARN, "No style found");
2831
0
        return false;
2832
0
    }
2833
313k
    if (!event->Text) {
2834
0
        ass_msg(render_priv->library, MSGL_WARN, "Empty event");
2835
0
        return false;
2836
0
    }
2837
2838
313k
    free_render_context(state);
2839
313k
    init_render_context(state, event);
2840
2841
313k
    if (!parse_events(state, event))
2842
0
        return false;
2843
2844
313k
    TextInfo *text_info = &state->text_info;
2845
313k
    if (text_info->length == 0) {
2846
        // no valid symbols in the event; this can be smth like {comment}
2847
227k
        free_render_context(state);
2848
227k
        return false;
2849
227k
    }
2850
2851
85.9k
    split_style_runs(state);
2852
2853
    // Find shape runs and shape text
2854
85.9k
    ass_shaper_set_base_direction(state->shaper,
2855
85.9k
            ass_resolve_base_direction(state->font_encoding));
2856
85.9k
    ass_shaper_find_runs(state->shaper, render_priv, text_info->glyphs,
2857
85.9k
            text_info->length);
2858
85.9k
    if (!ass_shaper_shape(state->shaper, text_info)) {
2859
0
        ass_msg(render_priv->library, MSGL_ERR, "Failed to shape text");
2860
0
        free_render_context(state);
2861
0
        return false;
2862
0
    }
2863
2864
85.9k
    retrieve_glyphs(state);
2865
2866
85.9k
    preliminary_layout(state);
2867
2868
85.9k
    int valign = state->alignment & 12;
2869
2870
85.9k
    int MarginL =
2871
85.9k
        (event->MarginL) ? event->MarginL : state->style->MarginL;
2872
85.9k
    int MarginR =
2873
85.9k
        (event->MarginR) ? event->MarginR : state->style->MarginR;
2874
85.9k
    int MarginV =
2875
85.9k
        (event->MarginV) ? event->MarginV : state->style->MarginV;
2876
2877
    // calculate max length of a line
2878
85.9k
    double max_text_width =
2879
85.9k
        x2scr_right(state, render_priv->track->PlayResX - MarginR) -
2880
85.9k
        x2scr_left(state, MarginL);
2881
2882
    // wrap lines
2883
85.9k
    wrap_lines_smart(state, max_text_width);
2884
2885
    // depends on glyph x coordinates being monotonous within runs, so it should be done before reorder
2886
85.9k
    ass_process_karaoke_effects(state);
2887
2888
85.9k
    reorder_text(state);
2889
2890
85.9k
    align_lines(state, max_text_width);
2891
2892
    // determine text bounding box
2893
85.9k
    ASS_DRect bbox;
2894
85.9k
    compute_string_bbox(text_info, &bbox);
2895
2896
85.9k
    apply_baseline_shear(state);
2897
2898
    // determine device coordinates for text
2899
85.9k
    double device_x = 0;
2900
85.9k
    double device_y = 0;
2901
2902
    // handle positioned events first: an event can be both positioned and
2903
    // scrolling, and the scrolling effect overrides the position on one axis
2904
85.9k
    if (state->evt_type & EVENT_POSITIONED) {
2905
1.78k
        double base_x = 0;
2906
1.78k
        double base_y = 0;
2907
1.78k
        get_base_point(&bbox, state->alignment, &base_x, &base_y);
2908
1.78k
        device_x =
2909
1.78k
            x2scr_pos(render_priv, state->pos_x) - base_x;
2910
1.78k
        device_y =
2911
1.78k
            y2scr_pos(render_priv, state->pos_y) - base_y;
2912
1.78k
    }
2913
2914
    // x coordinate
2915
85.9k
    if (state->evt_type & EVENT_HSCROLL) {
2916
252
        if (state->scroll_direction == SCROLL_RL)
2917
102
            device_x =
2918
102
                x2scr_pos(render_priv,
2919
102
                      render_priv->track->PlayResX -
2920
102
                      state->scroll_shift);
2921
150
        else if (state->scroll_direction == SCROLL_LR)
2922
150
            device_x =
2923
150
                x2scr_pos(render_priv, state->scroll_shift) -
2924
150
                (bbox.x_max - bbox.x_min);
2925
85.7k
    } else if (!(state->evt_type & EVENT_POSITIONED)) {
2926
83.9k
        device_x = x2scr_left(state, MarginL);
2927
83.9k
    }
2928
2929
    // y coordinate
2930
85.9k
    if (state->evt_type & EVENT_VSCROLL) {
2931
26
        if (state->scroll_direction == SCROLL_TB)
2932
0
            device_y =
2933
0
                y2scr(state,
2934
0
                      state->scroll_y0 +
2935
0
                      state->scroll_shift) -
2936
0
                bbox.y_max;
2937
26
        else if (state->scroll_direction == SCROLL_BT)
2938
26
            device_y =
2939
26
                y2scr(state,
2940
26
                      state->scroll_y1 -
2941
26
                      state->scroll_shift) -
2942
26
                bbox.y_min;
2943
85.9k
    } else if (!(state->evt_type & EVENT_POSITIONED)) {
2944
84.1k
        if (valign == VALIGN_TOP) {     // toptitle
2945
393
            device_y =
2946
393
                y2scr_top(state,
2947
393
                          MarginV) + text_info->lines[0].asc;
2948
83.7k
        } else if (valign == VALIGN_CENTER) {   // midtitle
2949
684
            double scr_y =
2950
684
                y2scr(state, render_priv->track->PlayResY / 2.0);
2951
684
            device_y = scr_y - (bbox.y_max + bbox.y_min) / 2.0;
2952
83.1k
        } else {                // subtitle
2953
83.1k
            double line_pos = state->explicit ?
2954
72.7k
                0 : render_priv->settings.line_position;
2955
83.1k
            double scr_top, scr_bottom, scr_y0;
2956
83.1k
            if (valign != VALIGN_SUB)
2957
0
                ass_msg(render_priv->library, MSGL_V,
2958
0
                       "Invalid valign, assuming 0 (subtitle)");
2959
83.1k
            scr_bottom =
2960
83.1k
                y2scr_sub(state,
2961
83.1k
                          render_priv->track->PlayResY - MarginV);
2962
83.1k
            scr_top = y2scr_top(state, 0); //xxx not always 0?
2963
83.1k
            device_y = scr_bottom + (scr_top - scr_bottom) * line_pos / 100.0;
2964
83.1k
            device_y -= text_info->height;
2965
83.1k
            device_y += text_info->lines[0].asc;
2966
            // clip to top to avoid confusion if line_position is very high,
2967
            // turning the subtitle into a toptitle
2968
            // also, don't change behavior if line_position is not used
2969
83.1k
            scr_y0 = scr_top + text_info->lines[0].asc;
2970
83.1k
            if (device_y < scr_y0 && line_pos > 0) {
2971
0
                device_y = scr_y0;
2972
0
            }
2973
83.1k
        }
2974
84.1k
    }
2975
2976
    // fix clip coordinates
2977
85.9k
    if (state->explicit || !render_priv->settings.use_margins) {
2978
85.9k
        state->clip_x0 =
2979
85.9k
            lround(x2scr_pos_scaled(render_priv, state->clip_x0));
2980
85.9k
        state->clip_x1 =
2981
85.9k
            lround(x2scr_pos_scaled(render_priv, state->clip_x1));
2982
85.9k
        state->clip_y0 =
2983
85.9k
            lround(y2scr_pos(render_priv, state->clip_y0));
2984
85.9k
        state->clip_y1 =
2985
85.9k
            lround(y2scr_pos(render_priv, state->clip_y1));
2986
2987
85.9k
        if (state->explicit) {
2988
            // we still need to clip against screen boundaries
2989
12.2k
            int zx = render_priv->settings.left_margin;
2990
12.2k
            int zy = render_priv->settings.top_margin;
2991
12.2k
            int sx = zx + render_priv->frame_content_width;
2992
12.2k
            int sy = zy + render_priv->frame_content_height;
2993
2994
12.2k
            state->clip_x0 = FFMAX(state->clip_x0, zx);
2995
12.2k
            state->clip_y0 = FFMAX(state->clip_y0, zy);
2996
12.2k
            state->clip_x1 = FFMIN(state->clip_x1, sx);
2997
12.2k
            state->clip_y1 = FFMIN(state->clip_y1, sy);
2998
12.2k
        }
2999
85.9k
    } else {
3000
        // no \clip (explicit==0) and use_margins => only clip to screen with margins
3001
0
        state->clip_x0 = 0;
3002
0
        state->clip_y0 = 0;
3003
0
        state->clip_x1 = render_priv->settings.frame_width;
3004
0
        state->clip_y1 = render_priv->settings.frame_height;
3005
0
    }
3006
3007
85.9k
    if (state->evt_type & EVENT_VSCROLL) {
3008
26
        int y0 = lround(y2scr_pos(render_priv, state->scroll_y0));
3009
26
        int y1 = lround(y2scr_pos(render_priv, state->scroll_y1));
3010
3011
26
        state->clip_y0 = FFMAX(state->clip_y0, y0);
3012
26
        state->clip_y1 = FFMIN(state->clip_y1, y1);
3013
26
    }
3014
3015
85.9k
    calculate_rotation_params(state, &bbox, device_x, device_y);
3016
3017
85.9k
    render_and_combine_glyphs(state, device_x, device_y);
3018
3019
85.9k
    memset(event_images, 0, sizeof(*event_images));
3020
    // VSFilter does *not* shift lines with a border > margin to be within the
3021
    // frame, so negative values for top and left may occur
3022
85.9k
    event_images->top = device_y - text_info->lines[0].asc - text_info->border_top;
3023
85.9k
    event_images->height =
3024
85.9k
        text_info->height + text_info->border_bottom + text_info->border_top;
3025
85.9k
    event_images->left =
3026
85.9k
        (device_x + bbox.x_min) * render_priv->par_scale_x - text_info->border_x + 0.5;
3027
85.9k
    event_images->width =
3028
85.9k
        (bbox.x_max - bbox.x_min) * render_priv->par_scale_x
3029
85.9k
        + 2 * text_info->border_x + 0.5;
3030
85.9k
    event_images->detect_collisions = state->detect_collisions;
3031
85.9k
    event_images->shift_direction = (valign == VALIGN_SUB) ? -1 : 1;
3032
85.9k
    event_images->event = event;
3033
85.9k
    event_images->imgs = render_text(state);
3034
3035
85.9k
    if (state->border_style == 4)
3036
0
        add_background(state, event_images);
3037
3038
85.9k
    ass_shaper_cleanup(state->shaper, text_info);
3039
85.9k
    free_render_context(state);
3040
3041
85.9k
    return true;
3042
85.9k
}
3043
3044
/**
3045
 * \brief Check cache limits and reset cache if they are exceeded
3046
 */
3047
static void check_cache_limits(ASS_Renderer *priv, CacheStore *cache)
3048
46.0k
{
3049
46.0k
    ass_cache_cut(cache->composite_cache, cache->composite_max_size);
3050
46.0k
    ass_cache_cut(cache->bitmap_cache, cache->bitmap_max_size);
3051
46.0k
    ass_cache_cut(cache->outline_cache, cache->glyph_max);
3052
46.0k
}
3053
3054
static void setup_shaper(ASS_Shaper *shaper, ASS_Renderer *render_priv)
3055
46.0k
{
3056
46.0k
    ASS_Track *track = render_priv->track;
3057
3058
46.0k
    ass_shaper_set_kerning(shaper, track->Kerning);
3059
46.0k
    ass_shaper_set_language(shaper, track->Language);
3060
46.0k
    ass_shaper_set_level(shaper, render_priv->settings.shaper);
3061
46.0k
#ifdef USE_FRIBIDI_EX_API
3062
46.0k
    ass_shaper_set_bidi_brackets(shaper,
3063
46.0k
            track->parser_priv->feature_flags & FEATURE_MASK(ASS_FEATURE_BIDI_BRACKETS));
3064
46.0k
#endif
3065
46.0k
    ass_shaper_set_whole_text_layout(shaper,
3066
46.0k
            track->parser_priv->feature_flags & FEATURE_MASK(ASS_FEATURE_WHOLE_TEXT_LAYOUT));
3067
46.0k
}
3068
3069
/**
3070
 * \brief Start a new frame
3071
 */
3072
static bool
3073
ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track,
3074
                long long now)
3075
46.0k
{
3076
46.0k
    if (!render_priv->settings.frame_width
3077
0
        && !render_priv->settings.frame_height)
3078
0
        return false;               // library not initialized
3079
3080
46.0k
    if (!render_priv->fontselect)
3081
0
        return false;
3082
3083
46.0k
    if (render_priv->library != track->library)
3084
0
        return false;
3085
3086
46.0k
    if (track->n_events == 0)
3087
0
        return false;               // nothing to do
3088
3089
46.0k
    render_priv->track = track;
3090
46.0k
    render_priv->time = now;
3091
3092
46.0k
    ass_lazy_track_init(render_priv->library, render_priv->track);
3093
3094
46.0k
    if (render_priv->library->num_fontdata != render_priv->num_emfonts) {
3095
0
        assert(render_priv->library->num_fontdata > render_priv->num_emfonts);
3096
0
        render_priv->num_emfonts = ass_update_embedded_fonts(
3097
0
            render_priv->fontselect, render_priv->num_emfonts);
3098
0
    }
3099
3100
46.0k
    setup_shaper(render_priv->state.shaper, render_priv);
3101
3102
    // PAR correction
3103
46.0k
    double par = render_priv->settings.par;
3104
46.0k
    bool lr_track = track->LayoutResX > 0 && track->LayoutResY > 0;
3105
46.0k
    if (par == 0. || lr_track) {
3106
46.0k
        if (render_priv->frame_content_width && render_priv->frame_content_height && (lr_track ||
3107
46.0k
                (render_priv->settings.storage_width && render_priv->settings.storage_height))) {
3108
46.0k
            double dar = ((double) render_priv->frame_content_width) /
3109
46.0k
                         render_priv->frame_content_height;
3110
46.0k
            ASS_Vector layout_res = ass_layout_res(render_priv);
3111
46.0k
            double sar = ((double) layout_res.x) / layout_res.y;
3112
46.0k
            par = dar / sar;
3113
46.0k
        } else
3114
0
            par = 1.0;
3115
46.0k
    }
3116
46.0k
    render_priv->par_scale_x = par;
3117
3118
46.0k
    render_priv->prev_images_root = render_priv->images_root;
3119
46.0k
    render_priv->images_root = NULL;
3120
3121
46.0k
    check_cache_limits(render_priv, &render_priv->cache);
3122
3123
46.0k
    return true;
3124
46.0k
}
3125
3126
static int cmp_event_layer(const void *p1, const void *p2)
3127
110k
{
3128
110k
    ASS_Event *e1 = ((EventImages *) p1)->event;
3129
110k
    ASS_Event *e2 = ((EventImages *) p2)->event;
3130
110k
    if (e1->Layer < e2->Layer)
3131
0
        return -1;
3132
110k
    if (e1->Layer > e2->Layer)
3133
0
        return 1;
3134
110k
    if (e1->ReadOrder < e2->ReadOrder)
3135
110k
        return -1;
3136
0
    if (e1->ReadOrder > e2->ReadOrder)
3137
0
        return 1;
3138
0
    return 0;
3139
0
}
3140
3141
static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv,
3142
                                       ASS_Event *event)
3143
159k
{
3144
159k
    if (!event->render_priv) {
3145
3.44k
        event->render_priv = calloc(1, sizeof(ASS_RenderPriv));
3146
3.44k
        if (!event->render_priv)
3147
0
            return NULL;
3148
3.44k
    }
3149
159k
    if (render_priv->render_id != event->render_priv->render_id) {
3150
3.44k
        memset(event->render_priv, 0, sizeof(ASS_RenderPriv));
3151
3.44k
        event->render_priv->render_id = render_priv->render_id;
3152
3.44k
    }
3153
3154
159k
    return event->render_priv;
3155
159k
}
3156
3157
static int overlap(Rect *s1, Rect *s2)
3158
396k
{
3159
396k
    if (s1->y0 >= s2->y1 || s2->y0 >= s1->y1 ||
3160
5.45k
        s1->x0 >= s2->x1 || s2->x0 >= s1->x1)
3161
396k
        return 0;
3162
0
    return 1;
3163
396k
}
3164
3165
static int cmp_rect_y0(const void *p1, const void *p2)
3166
135k
{
3167
135k
    return ((Rect *) p1)->y0 - ((Rect *) p2)->y0;
3168
135k
}
3169
3170
static void
3171
shift_event(ASS_Renderer *render_priv, EventImages *ei, int shift)
3172
78.2k
{
3173
78.2k
    ASS_Image *cur = ei->imgs;
3174
292k
    while (cur) {
3175
213k
        cur->dst_y += shift;
3176
        // clip top and bottom
3177
213k
        if (cur->dst_y < 0) {
3178
29.1k
            int clip = -cur->dst_y;
3179
29.1k
            cur->h -= clip;
3180
29.1k
            cur->bitmap += clip * cur->stride;
3181
29.1k
            cur->dst_y = 0;
3182
29.1k
        }
3183
213k
        if (cur->dst_y + cur->h >= render_priv->height) {
3184
463
            int clip = cur->dst_y + cur->h - render_priv->height;
3185
463
            cur->h -= clip;
3186
463
        }
3187
213k
        if (cur->h <= 0) {
3188
27.7k
            cur->h = 0;
3189
27.7k
            cur->dst_y = 0;
3190
27.7k
        }
3191
213k
        cur = cur->next;
3192
213k
    }
3193
78.2k
    ei->top += shift;
3194
78.2k
}
3195
3196
// dir: 1 - move down
3197
//      -1 - move up
3198
static int fit_rect(Rect *s, Rect *fixed, int *cnt, int dir)
3199
3.44k
{
3200
3.44k
    int i;
3201
3.44k
    int shift = 0;
3202
3203
3.44k
    if (dir == 1)               // move down
3204
141
        for (i = 0; i < *cnt; ++i) {
3205
95
            if (s->y1 + shift <= fixed[i].y0 || s->y0 + shift >= fixed[i].y1 ||
3206
82
                s->x1 <= fixed[i].x0 || s->x0 >= fixed[i].x1)
3207
14
                continue;
3208
81
            shift = fixed[i].y1 - s->y0;
3209
81
    } else                      // dir == -1, move up
3210
11.4k
        for (i = *cnt - 1; i >= 0; --i) {
3211
8.01k
            if (s->y1 + shift <= fixed[i].y0 || s->y0 + shift >= fixed[i].y1 ||
3212
7.85k
                s->x1 <= fixed[i].x0 || s->x0 >= fixed[i].x1)
3213
418
                continue;
3214
7.59k
            shift = fixed[i].y0 - s->y1;
3215
7.59k
        }
3216
3217
3.44k
    fixed[*cnt].y0 = s->y0 + shift;
3218
3.44k
    fixed[*cnt].y1 = s->y1 + shift;
3219
3.44k
    fixed[*cnt].x0 = s->x0;
3220
3.44k
    fixed[*cnt].x1 = s->x1;
3221
3.44k
    (*cnt)++;
3222
3.44k
    qsort(fixed, *cnt, sizeof(*fixed), cmp_rect_y0);
3223
3224
3.44k
    return shift;
3225
3.44k
}
3226
3227
static void
3228
fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt)
3229
20.4k
{
3230
20.4k
    Rect *used = ass_realloc_array(NULL, cnt, sizeof(*used));
3231
20.4k
    int cnt_used = 0;
3232
20.4k
    int i, j;
3233
3234
20.4k
    if (!used)
3235
0
        return;
3236
3237
    // fill used[] with fixed events
3238
106k
    for (i = 0; i < cnt; ++i) {
3239
85.9k
        ASS_RenderPriv *priv;
3240
        // VSFilter considers events colliding if their intersections area is non-zero,
3241
        // zero-area events are therefore effectively fixed as well
3242
85.9k
        if (!imgs[i].detect_collisions || !imgs[i].height  || !imgs[i].width)
3243
6.26k
            continue;
3244
79.7k
        priv = get_render_priv(render_priv, imgs[i].event);
3245
79.7k
        if (priv && priv->height > 0) { // it's a fixed event
3246
76.2k
            Rect s;
3247
76.2k
            s.y0 = priv->top;
3248
76.2k
            s.y1 = priv->top + priv->height;
3249
76.2k
            s.x0 = priv->left;
3250
76.2k
            s.x1 = priv->left + priv->width;
3251
76.2k
            if (priv->height != imgs[i].height) {       // no, it's not
3252
0
                ass_msg(render_priv->library, MSGL_WARN,
3253
0
                        "Event height has changed");
3254
0
                priv->top = 0;
3255
0
                priv->height = 0;
3256
0
                priv->left = 0;
3257
0
                priv->width = 0;
3258
0
            }
3259
472k
            for (j = 0; j < cnt_used; ++j)
3260
396k
                if (overlap(&s, used + j)) {    // no, it's not
3261
0
                    priv->top = 0;
3262
0
                    priv->height = 0;
3263
0
                    priv->left = 0;
3264
0
                    priv->width = 0;
3265
0
                }
3266
76.2k
            if (priv->height > 0) {     // still a fixed event
3267
76.2k
                used[cnt_used].y0 = priv->top;
3268
76.2k
                used[cnt_used].y1 = priv->top + priv->height;
3269
76.2k
                used[cnt_used].x0 = priv->left;
3270
76.2k
                used[cnt_used].x1 = priv->left + priv->width;
3271
76.2k
                cnt_used++;
3272
76.2k
                shift_event(render_priv, imgs + i, priv->top - imgs[i].top);
3273
76.2k
            }
3274
76.2k
        }
3275
79.7k
    }
3276
20.4k
    qsort(used, cnt_used, sizeof(*used), cmp_rect_y0);
3277
3278
    // try to fit other events in free spaces
3279
106k
    for (i = 0; i < cnt; ++i) {
3280
85.9k
        ASS_RenderPriv *priv;
3281
85.9k
        if (!imgs[i].detect_collisions || !imgs[i].height  || !imgs[i].width)
3282
6.26k
            continue;
3283
79.7k
        priv = get_render_priv(render_priv, imgs[i].event);
3284
79.7k
        if (priv && priv->height == 0) {        // not a fixed event
3285
3.44k
            int shift;
3286
3.44k
            Rect s;
3287
3.44k
            s.y0 = imgs[i].top;
3288
3.44k
            s.y1 = imgs[i].top + imgs[i].height;
3289
3.44k
            s.x0 = imgs[i].left;
3290
3.44k
            s.x1 = imgs[i].left + imgs[i].width;
3291
3.44k
            shift = fit_rect(&s, used, &cnt_used, imgs[i].shift_direction);
3292
3.44k
            if (shift)
3293
1.98k
                shift_event(render_priv, imgs + i, shift);
3294
            // make it fixed
3295
3.44k
            priv->top = imgs[i].top;
3296
3.44k
            priv->height = imgs[i].height;
3297
3.44k
            priv->left = imgs[i].left;
3298
3.44k
            priv->width = imgs[i].width;
3299
3.44k
        }
3300
3301
79.7k
    }
3302
3303
20.4k
    free(used);
3304
20.4k
}
3305
3306
/**
3307
 * \brief compare two images
3308
 * \param i1 first image
3309
 * \param i2 second image
3310
 * \return 0 if identical, 1 if different positions, 2 if different content
3311
 */
3312
static int ass_image_compare(ASS_Image *i1, ASS_Image *i2)
3313
148k
{
3314
148k
    if (i1->w != i2->w)
3315
2.77k
        return 2;
3316
146k
    if (i1->h != i2->h)
3317
46
        return 2;
3318
145k
    if (i1->stride != i2->stride)
3319
0
        return 2;
3320
145k
    if (i1->color != i2->color)
3321
217
        return 2;
3322
145k
    if (i1->bitmap != i2->bitmap)
3323
271
        return 2;
3324
145k
    if (i1->dst_x != i2->dst_x)
3325
0
        return 1;
3326
145k
    if (i1->dst_y != i2->dst_y)
3327
4.89k
        return 1;
3328
140k
    return 0;
3329
145k
}
3330
3331
/**
3332
 * \brief compare current and previous image list
3333
 * \param priv library handle
3334
 * \return 0 if identical, 1 if different positions, 2 if different content
3335
 */
3336
static int ass_detect_change(ASS_Renderer *priv)
3337
46.0k
{
3338
46.0k
    ASS_Image *img, *img2;
3339
46.0k
    int diff;
3340
3341
46.0k
    img = priv->prev_images_root;
3342
46.0k
    img2 = priv->images_root;
3343
46.0k
    diff = 0;
3344
194k
    while (img && diff < 2) {
3345
150k
        ASS_Image *next, *next2;
3346
150k
        next = img->next;
3347
150k
        if (img2) {
3348
148k
            int d = ass_image_compare(img, img2);
3349
148k
            if (d > diff)
3350
4.11k
                diff = d;
3351
148k
            next2 = img2->next;
3352
148k
        } else {
3353
            // previous list is shorter
3354
1.73k
            diff = 2;
3355
1.73k
            break;
3356
1.73k
        }
3357
148k
        img = next;
3358
148k
        img2 = next2;
3359
148k
    }
3360
3361
    // is the previous list longer?
3362
46.0k
    if (img2)
3363
6.00k
        diff = 2;
3364
3365
46.0k
    return diff;
3366
46.0k
}
3367
3368
/**
3369
 * \brief render a frame
3370
 * \param priv library handle
3371
 * \param track track
3372
 * \param now current video timestamp (ms)
3373
 * \param detect_change a value describing how the new images differ from the previous ones will be written here:
3374
 *        0 if identical, 1 if different positions, 2 if different content.
3375
 *        Can be NULL, in that case no detection is performed.
3376
 */
3377
ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
3378
                            long long now, int *detect_change)
3379
46.0k
{
3380
    // init frame
3381
46.0k
    if (!ass_start_frame(priv, track, now)) {
3382
0
        if (detect_change)
3383
0
            *detect_change = 2;
3384
0
        return NULL;
3385
0
    }
3386
3387
    // render events separately
3388
46.0k
    int cnt = 0;
3389
733k
    for (int i = 0; i < track->n_events; i++) {
3390
687k
        ASS_Event *event = track->events + i;
3391
687k
        if ((event->Start <= now)
3392
686k
            && (now < (event->Start + event->Duration))) {
3393
313k
            if (cnt >= priv->eimg_size) {
3394
6.71k
                priv->eimg_size += 100;
3395
6.71k
                priv->eimg =
3396
6.71k
                    realloc(priv->eimg,
3397
6.71k
                            priv->eimg_size * sizeof(EventImages));
3398
6.71k
            }
3399
313k
            if (ass_render_event(&priv->state, event, priv->eimg + cnt))
3400
85.9k
                cnt++;
3401
313k
        }
3402
687k
    }
3403
3404
    // sort by layer
3405
46.0k
    if (cnt > 0)
3406
20.4k
        qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer);
3407
3408
    // call fix_collisions for each group of events with the same layer
3409
46.0k
    EventImages *last = priv->eimg;
3410
111k
    for (int i = 1; i < cnt; i++)
3411
65.5k
        if (last->event->Layer != priv->eimg[i].event->Layer) {
3412
0
            fix_collisions(priv, last, priv->eimg + i - last);
3413
0
            last = priv->eimg + i;
3414
0
        }
3415
46.0k
    if (cnt > 0)
3416
20.4k
        fix_collisions(priv, last, priv->eimg + cnt - last);
3417
3418
    // concat lists
3419
46.0k
    ASS_Image **tail = &priv->images_root;
3420
132k
    for (int i = 0; i < cnt; i++) {
3421
85.9k
        ASS_Image *cur = priv->eimg[i].imgs;
3422
313k
        while (cur) {
3423
227k
            *tail = cur;
3424
227k
            tail = &cur->next;
3425
227k
            cur = cur->next;
3426
227k
        }
3427
85.9k
    }
3428
46.0k
    ass_frame_ref(priv->images_root);
3429
3430
46.0k
    if (detect_change)
3431
46.0k
        *detect_change = ass_detect_change(priv);
3432
3433
    // free the previous image list
3434
46.0k
    ass_frame_unref(priv->prev_images_root);
3435
46.0k
    priv->prev_images_root = NULL;
3436
3437
46.0k
    if (track->parser_priv->prune_delay >= 0)
3438
0
        ass_prune_events(track, now - track->parser_priv->prune_delay);
3439
3440
46.0k
    return priv->images_root;
3441
46.0k
}
3442
3443
/**
3444
 * \brief Add reference to a frame image list.
3445
 * \param image_list image list returned by ass_render_frame()
3446
 */
3447
void ass_frame_ref(ASS_Image *img)
3448
46.0k
{
3449
46.0k
    if (!img)
3450
26.9k
        return;
3451
19.0k
    ((ASS_ImagePriv *) img)->ref_count++;
3452
19.0k
}
3453
3454
/**
3455
 * \brief Release reference to a frame image list.
3456
 * \param image_list image list returned by ass_render_frame()
3457
 */
3458
void ass_frame_unref(ASS_Image *img)
3459
69.9k
{
3460
69.9k
    if (!img || --((ASS_ImagePriv *) img)->ref_count)
3461
50.8k
        return;
3462
227k
    do {
3463
227k
        ASS_ImagePriv *priv = (ASS_ImagePriv *) img;
3464
227k
        img = img->next;
3465
227k
        ass_cache_dec_ref(priv->source);
3466
227k
        ass_aligned_free(priv->buffer);
3467
227k
        free(priv);
3468
227k
    } while (img);
3469
19.0k
}