~ubuntu-branches/ubuntu/lucid/avidemux/lucid

« back to all changes in this revision

Viewing changes to plugins/ADM_videoFilters/Ass/ADM_libAss/ass_render.c

  • Committer: Bazaar Package Importer
  • Author(s): Alessio Treglia
  • Date: 2009-08-20 08:42:44 UTC
  • mfrom: (1.1.14 upstream)
  • Revision ID: james.westby@ubuntu.com-20090820084244-bhh15xxd7x2vbcuh
Tags: 1:2.5.1+repack-0ubuntu1
* New upstream bugfix release (LP: #416066):
  - Re-enabled several video and audio encoders (regression introduced
    in 2.5.0)
  - Updated the FFmpeg libraries
  - More video encoders are now plugins
  - DV video encoder now supports more profiles
  - Fixed loading and saving issues with LAME, x264 and Xvid options
    (regression introduced in 2.5.0)
  - Fraps video decoding support
  - Lowpass-5 mode added to libavcodec deinterlacer filter plugin
  - Fixed formatting of parameters for various filters on 64-bit platforms
  - Updated libass
  - Fixed sizing of the bitrate control on various video encoder configure
    windows (regression introduced in 2.5.0)
  - Improved filter dialog for GTK+ interface
  - New navigation icons for GTK+ interface
  - Fixed the behaviour of several GTK+ open/save dialogs (regression
    introduced in 2.5.0)
  - asharp filter's Block Adaptive mode can now be disabled using the Qt
    interface
  - Re-enabled the colour chooser dialog using the Qt interface (regression
    introduced in 2.5.0)
  - GCC 4.4 support
  - Fixed issues with CMake build scripts when using multiple make jobs
    (regression introduced in 2.5.0)
* Remove debian/patches dir and drop all patches, now applied by upstream.
* Drop quilt support.
* debian/libavidemux0.install: Also install missing libraries.
* Move debian/install to debian/avidemux.install.
* debian/rules:
  - Build the internal ffmpeg version properly (thanks to Christian Marillat).
  - A bit of cleanup.
* debian/control:
  - Bump Standards-Version.
  - Update Homepage field.
  - Adjust libavidemux0 short description.
  - gtk -> GTK, qt -> QT.
  - Set myself as Maintainer.
* Repack the tarball to remove the debian/ dir provided by upstream:
  - Create debian/README.source.
  - Update debian/watch.
  - Add get-orig-source target to debian/rules.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
 
3
 *
 
4
 * This file is part of libass.
 
5
 *
 
6
 * libass is free software; you can redistribute it and/or modify
 
7
 * it under the terms of the GNU General Public License as published by
 
8
 * the Free Software Foundation; either version 2 of the License, or
 
9
 * (at your option) any later version.
 
10
 *
 
11
 * libass is distributed in the hope that it will be useful,
 
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
 * GNU General Public License for more details.
 
15
 *
 
16
 * You should have received a copy of the GNU General Public License along
 
17
 * with libass; if not, write to the Free Software Foundation, Inc.,
 
18
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
19
 */
 
20
 
 
21
#include "config.h"
 
22
 
 
23
#include <assert.h>
 
24
#include <math.h>
 
25
#include <inttypes.h>
 
26
#include <ft2build.h>
 
27
#include FT_FREETYPE_H
 
28
#include FT_STROKER_H
 
29
#include FT_GLYPH_H
 
30
#include FT_SYNTHESIS_H
 
31
 
 
32
#include "ass.h"
 
33
#include "ass_font.h"
 
34
#include "ass_bitmap.h"
 
35
#include "ass_cache.h"
 
36
#include "ass_utils.h"
 
37
#include "ass_fontconfig.h"
 
38
#include "ass_library.h"
 
39
#include "ass_drawing.h"
 
40
 
 
41
#define MAX_GLYPHS_INITIAL 1024
 
42
#define MAX_LINES_INITIAL 64
 
43
#define BLUR_MAX_RADIUS 100.0
 
44
#define MAX_BE 127
 
45
#define SUBPIXEL_MASK 63
 
46
#define SUBPIXEL_ACCURACY 7    // d6 mask for subpixel accuracy adjustment
 
47
#define GLYPH_CACHE_MAX 1000
 
48
#define BITMAP_CACHE_MAX_SIZE 50 * 1048576;
 
49
 
 
50
typedef struct {
 
51
    double xMin;
 
52
    double xMax;
 
53
    double yMin;
 
54
    double yMax;
 
55
} double_bbox_t;
 
56
 
 
57
typedef struct {
 
58
    double x;
 
59
    double y;
 
60
} double_vector_t;
 
61
 
 
62
typedef struct free_list {
 
63
    void *object;
 
64
    struct free_list *next;
 
65
} free_list_t;
 
66
 
 
67
typedef struct {
 
68
    int frame_width;
 
69
    int frame_height;
 
70
    double font_size_coeff;     // font size multiplier
 
71
    double line_spacing;        // additional line spacing (in frame pixels)
 
72
    int top_margin;             // height of top margin. Everything except toptitles is shifted down by top_margin.
 
73
    int bottom_margin;          // height of bottom margin. (frame_height - top_margin - bottom_margin) is original video height.
 
74
    int left_margin;
 
75
    int right_margin;
 
76
    int use_margins;            // 0 - place all subtitles inside original frame
 
77
    // 1 - use margins for placing toptitles and subtitles
 
78
    double aspect;              // frame aspect ratio, d_width / d_height.
 
79
    double pixel_ratio;         // pixel ratio of the source image
 
80
    ass_hinting_t hinting;
 
81
 
 
82
    char *default_font;
 
83
    char *default_family;
 
84
} ass_settings_t;
 
85
 
 
86
// a rendered event
 
87
typedef struct {
 
88
    ass_image_t *imgs;
 
89
    int top, height;
 
90
    int detect_collisions;
 
91
    int shift_direction;
 
92
    ass_event_t *event;
 
93
} event_images_t;
 
94
 
 
95
typedef enum { EF_NONE = 0, EF_KARAOKE, EF_KARAOKE_KF, EF_KARAOKE_KO
 
96
} effect_t;
 
97
 
 
98
// describes a glyph
 
99
// glyph_info_t and text_info_t are used for text centering and word-wrapping operations
 
100
typedef struct {
 
101
    unsigned symbol;
 
102
    FT_Glyph glyph;
 
103
    FT_Glyph outline_glyph;
 
104
    bitmap_t *bm;               // glyph bitmap
 
105
    bitmap_t *bm_o;             // outline bitmap
 
106
    bitmap_t *bm_s;             // shadow bitmap
 
107
    FT_BBox bbox;
 
108
    FT_Vector pos;
 
109
    char linebreak;             // the first (leading) glyph of some line ?
 
110
    uint32_t c[4];              // colors
 
111
    FT_Vector advance;          // 26.6
 
112
    effect_t effect_type;
 
113
    int effect_timing;          // time duration of current karaoke word
 
114
    // after process_karaoke_effects: distance in pixels from the glyph origin.
 
115
    // part of the glyph to the left of it is displayed in a different color.
 
116
    int effect_skip_timing;     // delay after the end of last karaoke word
 
117
    int asc, desc;              // font max ascender and descender
 
118
//      int height;
 
119
    int be;                     // blur edges
 
120
    double blur;                // gaussian blur
 
121
    double shadow_x;
 
122
    double shadow_y;
 
123
    double frx, fry, frz;       // rotation
 
124
    double fax, fay;            // text shearing
 
125
 
 
126
    bitmap_hash_key_t hash_key;
 
127
} glyph_info_t;
 
128
 
 
129
typedef struct {
 
130
    double asc, desc;
 
131
} line_info_t;
 
132
 
 
133
typedef struct {
 
134
    glyph_info_t *glyphs;
 
135
    int length;
 
136
    line_info_t *lines;
 
137
    int n_lines;
 
138
    double height;
 
139
    int max_glyphs;
 
140
    int max_lines;
 
141
} text_info_t;
 
142
 
 
143
 
 
144
// Renderer state.
 
145
// Values like current font face, color, screen position, clipping and so on are stored here.
 
146
typedef struct {
 
147
    ass_event_t *event;
 
148
    ass_style_t *style;
 
149
 
 
150
    ass_font_t *font;
 
151
    char *font_path;
 
152
    double font_size;
 
153
    int flags;                  // decoration flags (underline/strike-through)
 
154
 
 
155
    FT_Stroker stroker;
 
156
    int alignment;              // alignment overrides go here; if zero, style value will be used
 
157
    double frx, fry, frz;
 
158
    double fax, fay;            // text shearing
 
159
    enum { EVENT_NORMAL,        // "normal" top-, sub- or mid- title
 
160
        EVENT_POSITIONED,       // happens after pos(,), margins are ignored
 
161
        EVENT_HSCROLL,          // "Banner" transition effect, text_width is unlimited
 
162
        EVENT_VSCROLL           // "Scroll up", "Scroll down" transition effects
 
163
    } evt_type;
 
164
    double pos_x, pos_y;        // position
 
165
    double org_x, org_y;        // origin
 
166
    char have_origin;           // origin is explicitly defined; if 0, get_base_point() is used
 
167
    double scale_x, scale_y;
 
168
    double hspacing;            // distance between letters, in pixels
 
169
    double border_x;              // outline width
 
170
    double border_y;
 
171
    uint32_t c[4];              // colors(Primary, Secondary, so on) in RGBA
 
172
    int clip_x0, clip_y0, clip_x1, clip_y1;
 
173
    char clip_mode;             // 1 = iclip
 
174
    char detect_collisions;
 
175
    uint32_t fade;              // alpha from \fad
 
176
    char be;                    // blur edges
 
177
    double blur;                // gaussian blur
 
178
    double shadow_x;
 
179
    double shadow_y;
 
180
    int drawing_mode;           // not implemented; when != 0 text is discarded, except for style override tags
 
181
    ass_drawing_t *drawing;     // current drawing
 
182
    ass_drawing_t *clip_drawing;// clip vector
 
183
    int clip_drawing_mode;      // 0 = regular clip, 1 = inverse clip
 
184
 
 
185
    effect_t effect_type;
 
186
    int effect_timing;
 
187
    int effect_skip_timing;
 
188
 
 
189
    enum { SCROLL_LR,           // left-to-right
 
190
        SCROLL_RL,
 
191
        SCROLL_TB,              // top-to-bottom
 
192
        SCROLL_BT
 
193
    } scroll_direction;         // for EVENT_HSCROLL, EVENT_VSCROLL
 
194
    int scroll_shift;
 
195
 
 
196
    // face properties
 
197
    char *family;
 
198
    unsigned bold;
 
199
    unsigned italic;
 
200
    int treat_family_as_pattern;
 
201
 
 
202
} render_context_t;
 
203
 
 
204
typedef struct {
 
205
    hashmap_t *font_cache;
 
206
    hashmap_t *glyph_cache;
 
207
    hashmap_t *bitmap_cache;
 
208
    hashmap_t *composite_cache;
 
209
    size_t glyph_max;
 
210
    size_t bitmap_max_size;
 
211
} cache_store_t;
 
212
 
 
213
struct ass_renderer {
 
214
    ass_library_t *library;
 
215
    FT_Library ftlibrary;
 
216
    fc_instance_t *fontconfig_priv;
 
217
    ass_settings_t settings;
 
218
    int render_id;
 
219
    ass_synth_priv_t *synth_priv;
 
220
 
 
221
    ass_image_t *images_root;   // rendering result is stored here
 
222
    ass_image_t *prev_images_root;
 
223
 
 
224
    event_images_t *eimg;       // temporary buffer for sorting rendered events
 
225
    int eimg_size;              // allocated buffer size
 
226
 
 
227
    // frame-global data
 
228
    int width, height;          // screen dimensions
 
229
    int orig_height;            // frame height ( = screen height - margins )
 
230
    int orig_width;             // frame width ( = screen width - margins )
 
231
    int orig_height_nocrop;     // frame height ( = screen height - margins + cropheight)
 
232
    int orig_width_nocrop;      // frame width ( = screen width - margins + cropwidth)
 
233
    ass_track_t *track;
 
234
    long long time;             // frame's timestamp, ms
 
235
    double font_scale;
 
236
    double font_scale_x;        // x scale applied to all glyphs to preserve text aspect ratio
 
237
    double border_scale;
 
238
 
 
239
    render_context_t state;
 
240
    text_info_t text_info;
 
241
    cache_store_t cache;
 
242
 
 
243
    free_list_t *free_head;
 
244
    free_list_t *free_tail;
 
245
};
 
246
 
 
247
struct render_priv {
 
248
    int top, height;
 
249
    int render_id;
 
250
};
 
251
 
 
252
static void ass_lazy_track_init(ass_renderer_t *render_priv)
 
253
{
 
254
    ass_track_t *track = render_priv->track;
 
255
 
 
256
    if (track->PlayResX && track->PlayResY)
 
257
        return;
 
258
    if (!track->PlayResX && !track->PlayResY) {
 
259
        ass_msg(render_priv->library, MSGL_WARN,
 
260
               "Neither PlayResX nor PlayResY defined. Assuming 384x288");
 
261
        track->PlayResX = 384;
 
262
        track->PlayResY = 288;
 
263
    } else {
 
264
        if (!track->PlayResY && track->PlayResX == 1280) {
 
265
            track->PlayResY = 1024;
 
266
            ass_msg(render_priv->library, MSGL_WARN,
 
267
                   "PlayResY undefined, setting to %d", track->PlayResY);
 
268
        } else if (!track->PlayResY) {
 
269
            track->PlayResY = track->PlayResX * 3 / 4;
 
270
            ass_msg(render_priv->library, MSGL_WARN,
 
271
                   "PlayResY undefined, setting to %d", track->PlayResY);
 
272
        } else if (!track->PlayResX && track->PlayResY == 1024) {
 
273
            track->PlayResX = 1280;
 
274
            ass_msg(render_priv->library, MSGL_WARN,
 
275
                   "PlayResX undefined, setting to %d", track->PlayResX);
 
276
        } else if (!track->PlayResX) {
 
277
            track->PlayResX = track->PlayResY * 4 / 3;
 
278
            ass_msg(render_priv->library, MSGL_WARN,
 
279
                   "PlayResX undefined, setting to %d", track->PlayResX);
 
280
        }
 
281
    }
 
282
}
 
283
 
 
284
ass_renderer_t *ass_renderer_init(ass_library_t *library)
 
285
{
 
286
    int error;
 
287
    FT_Library ft;
 
288
    ass_renderer_t *priv = 0;
 
289
    int vmajor, vminor, vpatch;
 
290
 
 
291
    error = FT_Init_FreeType(&ft);
 
292
    if (error) {
 
293
        ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType");
 
294
        goto ass_init_exit;
 
295
    }
 
296
 
 
297
    FT_Library_Version(ft, &vmajor, &vminor, &vpatch);
 
298
    ass_msg(library, MSGL_V, "FreeType library version: %d.%d.%d",
 
299
           vmajor, vminor, vpatch);
 
300
    ass_msg(library, MSGL_V, "FreeType headers version: %d.%d.%d",
 
301
           FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
 
302
 
 
303
    priv = calloc(1, sizeof(ass_renderer_t));
 
304
    if (!priv) {
 
305
        FT_Done_FreeType(ft);
 
306
        goto ass_init_exit;
 
307
    }
 
308
 
 
309
    priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS);
 
310
 
 
311
    priv->library = library;
 
312
    priv->ftlibrary = ft;
 
313
    // images_root and related stuff is zero-filled in calloc
 
314
 
 
315
    priv->cache.font_cache = ass_font_cache_init(library);
 
316
    priv->cache.bitmap_cache = ass_bitmap_cache_init(library);
 
317
    priv->cache.composite_cache = ass_composite_cache_init(library);
 
318
    priv->cache.glyph_cache = ass_glyph_cache_init(library);
 
319
    priv->cache.glyph_max = GLYPH_CACHE_MAX;
 
320
    priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE;
 
321
 
 
322
    priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL;
 
323
    priv->text_info.max_lines = MAX_LINES_INITIAL;
 
324
    priv->text_info.glyphs =
 
325
        calloc(MAX_GLYPHS_INITIAL, sizeof(glyph_info_t));
 
326
    priv->text_info.lines = calloc(MAX_LINES_INITIAL, sizeof(line_info_t));
 
327
 
 
328
  ass_init_exit:
 
329
    if (priv)
 
330
        ass_msg(library, MSGL_INFO, "Init");
 
331
    else
 
332
        ass_msg(library, MSGL_ERR, "Init failed");
 
333
 
 
334
    return priv;
 
335
}
 
336
 
 
337
void ass_set_cache_limits(ass_renderer_t *render_priv, int glyph_max,
 
338
                          int bitmap_max)
 
339
{
 
340
    render_priv->cache.glyph_max = glyph_max ? glyph_max : GLYPH_CACHE_MAX;
 
341
    render_priv->cache.bitmap_max_size = bitmap_max ? 1048576 * bitmap_max :
 
342
                                         BITMAP_CACHE_MAX_SIZE;
 
343
}
 
344
 
 
345
static void free_list_clear(ass_renderer_t *render_priv)
 
346
{
 
347
    if (render_priv->free_head) {
 
348
        free_list_t *item = render_priv->free_head;
 
349
        while(item) {
 
350
            free_list_t *oi = item;
 
351
            free(item->object);
 
352
            item = item->next;
 
353
            free(oi);
 
354
        }
 
355
        render_priv->free_head = NULL;
 
356
    }
 
357
}
 
358
 
 
359
static void ass_free_images(ass_image_t *img);
 
360
 
 
361
void ass_renderer_done(ass_renderer_t *render_priv)
 
362
{
 
363
    ass_font_cache_done(render_priv->cache.font_cache);
 
364
    ass_bitmap_cache_done(render_priv->cache.bitmap_cache);
 
365
    ass_composite_cache_done(render_priv->cache.composite_cache);
 
366
    ass_glyph_cache_done(render_priv->cache.glyph_cache);
 
367
 
 
368
    ass_free_images(render_priv->images_root);
 
369
    ass_free_images(render_priv->prev_images_root);
 
370
 
 
371
    if (render_priv->state.stroker) {
 
372
        FT_Stroker_Done(render_priv->state.stroker);
 
373
        render_priv->state.stroker = 0;
 
374
    }
 
375
    if (render_priv && render_priv->ftlibrary)
 
376
        FT_Done_FreeType(render_priv->ftlibrary);
 
377
    if (render_priv && render_priv->fontconfig_priv)
 
378
        fontconfig_done(render_priv->fontconfig_priv);
 
379
    if (render_priv && render_priv->synth_priv)
 
380
        ass_synth_done(render_priv->synth_priv);
 
381
    if (render_priv && render_priv->eimg)
 
382
        free(render_priv->eimg);
 
383
    free(render_priv->text_info.glyphs);
 
384
    free(render_priv->text_info.lines);
 
385
 
 
386
    free(render_priv->settings.default_font);
 
387
    free(render_priv->settings.default_family);
 
388
 
 
389
    free_list_clear(render_priv);
 
390
    free(render_priv);
 
391
}
 
392
 
 
393
/**
 
394
 * \brief Create a new ass_image_t
 
395
 * Parameters are the same as ass_image_t fields.
 
396
 */
 
397
static ass_image_t *my_draw_bitmap(unsigned char *bitmap, int bitmap_w,
 
398
                                   int bitmap_h, int stride, int dst_x,
 
399
                                   int dst_y, uint32_t color)
 
400
{
 
401
    ass_image_t *img = calloc(1, sizeof(ass_image_t));
 
402
 
 
403
    img->w = bitmap_w;
 
404
    img->h = bitmap_h;
 
405
    img->stride = stride;
 
406
    img->bitmap = bitmap;
 
407
    img->color = color;
 
408
    img->dst_x = dst_x;
 
409
    img->dst_y = dst_y;
 
410
 
 
411
    return img;
 
412
}
 
413
 
 
414
static double x2scr_pos(ass_renderer_t *render_priv, double x);
 
415
static double y2scr_pos(ass_renderer_t *render_priv, double y);
 
416
 
 
417
typedef struct {
 
418
    int x0;
 
419
    int y0;
 
420
    int x1;
 
421
    int y1;
 
422
} rect_t;
 
423
 
 
424
/*
 
425
 * \brief Convert bitmap glyphs into ass_image_t list with inverse clipping
 
426
 *
 
427
 * Inverse clipping with the following strategy:
 
428
 * - find rectangle from (x0, y0) to (cx0, y1)
 
429
 * - find rectangle from (cx0, y0) to (cx1, cy0)
 
430
 * - find rectangle from (cx0, cy1) to (cx1, y1)
 
431
 * - find rectangle from (cx1, y0) to (x1, y1)
 
432
 * These rectangles can be invalid and in this case are discarded.
 
433
 * Afterwards, they are clipped against the screen coordinates.
 
434
 * In an additional pass, the rectangles need to be split up left/right for
 
435
 * karaoke effects.  This can result in a lot of bitmaps (6 to be exact).
 
436
 */
 
437
static ass_image_t **render_glyph_i(ass_renderer_t *render_priv,
 
438
                                    bitmap_t *bm, int dst_x, int dst_y,
 
439
                                    uint32_t color, uint32_t color2, int brk,
 
440
                                    ass_image_t **tail)
 
441
{
 
442
    int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
 
443
    rect_t r[4];
 
444
    ass_image_t *img;
 
445
 
 
446
    dst_x += bm->left;
 
447
    dst_y += bm->top;
 
448
 
 
449
    // we still need to clip against screen boundaries
 
450
    zx = x2scr_pos(render_priv, 0);
 
451
    zy = y2scr_pos(render_priv, 0);
 
452
    sx = x2scr_pos(render_priv, render_priv->track->PlayResX);
 
453
    sy = y2scr_pos(render_priv, render_priv->track->PlayResY);
 
454
 
 
455
    x0 = 0;
 
456
    y0 = 0;
 
457
    x1 = bm->w;
 
458
    y1 = bm->h;
 
459
    cx0 = render_priv->state.clip_x0 - dst_x;
 
460
    cy0 = render_priv->state.clip_y0 - dst_y;
 
461
    cx1 = render_priv->state.clip_x1 - dst_x;
 
462
    cy1 = render_priv->state.clip_y1 - dst_y;
 
463
 
 
464
    // calculate rectangles and discard invalid ones while we're at it.
 
465
    i = 0;
 
466
    r[i].x0 = x0;
 
467
    r[i].y0 = y0;
 
468
    r[i].x1 = (cx0 > x1) ? x1 : cx0;
 
469
    r[i].y1 = y1;
 
470
    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
 
471
    r[i].x0 = (cx0 < 0) ? x0 : cx0;
 
472
    r[i].y0 = y0;
 
473
    r[i].x1 = (cx1 > x1) ? x1 : cx1;
 
474
    r[i].y1 = (cy0 > y1) ? y1 : cy0;
 
475
    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
 
476
    r[i].x0 = (cx0 < 0) ? x0 : cx0;
 
477
    r[i].y0 = (cy1 < 0) ? y0 : cy1;
 
478
    r[i].x1 = (cx1 > x1) ? x1 : cx1;
 
479
    r[i].y1 = y1;
 
480
    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
 
481
    r[i].x0 = (cx1 < 0) ? x0 : cx1;
 
482
    r[i].y0 = y0;
 
483
    r[i].x1 = x1;
 
484
    r[i].y1 = y1;
 
485
    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
 
486
 
 
487
    // clip each rectangle to screen coordinates
 
488
    for (j = 0; j < i; j++) {
 
489
        r[j].x0 = (r[j].x0 + dst_x < zx) ? zx - dst_x : r[j].x0;
 
490
        r[j].y0 = (r[j].y0 + dst_y < zy) ? zy - dst_y : r[j].y0;
 
491
        r[j].x1 = (r[j].x1 + dst_x > sx) ? sx - dst_x : r[j].x1;
 
492
        r[j].y1 = (r[j].y1 + dst_y > sy) ? sy - dst_y : r[j].y1;
 
493
    }
 
494
 
 
495
    // draw the rectangles
 
496
    for (j = 0; j < i; j++) {
 
497
        int lbrk = brk;
 
498
        // kick out rectangles that are invalid now
 
499
        if (r[j].x1 <= r[j].x0 || r[j].y1 <= r[j].y0)
 
500
            continue;
 
501
        // split up into left and right for karaoke, if needed
 
502
        if (lbrk > r[j].x0) {
 
503
            if (lbrk > r[j].x1) lbrk = r[j].x1;
 
504
            img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + r[j].x0,
 
505
                lbrk - r[j].x0, r[j].y1 - r[j].y0,
 
506
                bm->w, dst_x + r[j].x0, dst_y + r[j].y0, color);
 
507
            *tail = img;
 
508
            tail = &img->next;
 
509
        }
 
510
        if (lbrk < r[j].x1) {
 
511
            if (lbrk < r[j].x0) lbrk = r[j].x0;
 
512
            img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + lbrk,
 
513
                r[j].x1 - lbrk, r[j].y1 - r[j].y0,
 
514
                bm->w, dst_x + lbrk, dst_y + r[j].y0, color2);
 
515
            *tail = img;
 
516
            tail = &img->next;
 
517
        }
 
518
    }
 
519
 
 
520
    return tail;
 
521
}
 
522
 
 
523
/**
 
524
 * \brief convert bitmap glyph into ass_image_t struct(s)
 
525
 * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY
 
526
 * \param dst_x bitmap x coordinate in video frame
 
527
 * \param dst_y bitmap y coordinate in video frame
 
528
 * \param color first color, RGBA
 
529
 * \param color2 second color, RGBA
 
530
 * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right
 
531
 * \param tail pointer to the last image's next field, head of the generated list should be stored here
 
532
 * \return pointer to the new list tail
 
533
 * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion.
 
534
 */
 
535
static ass_image_t **render_glyph(ass_renderer_t *render_priv,
 
536
                                  bitmap_t *bm, int dst_x, int dst_y,
 
537
                                  uint32_t color, uint32_t color2, int brk,
 
538
                                  ass_image_t **tail)
 
539
{
 
540
    // Inverse clipping in use?
 
541
    if (render_priv->state.clip_mode)
 
542
        return render_glyph_i(render_priv, bm, dst_x, dst_y, color, color2,
 
543
                              brk, tail);
 
544
 
 
545
    // brk is relative to dst_x
 
546
    // color = color left of brk
 
547
    // color2 = color right of brk
 
548
    int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap
 
549
    int clip_x0, clip_y0, clip_x1, clip_y1;
 
550
    int tmp;
 
551
    ass_image_t *img;
 
552
 
 
553
    dst_x += bm->left;
 
554
    dst_y += bm->top;
 
555
    brk -= bm->left;
 
556
 
 
557
    // clipping
 
558
    clip_x0 = FFMINMAX(render_priv->state.clip_x0, 0, render_priv->width);
 
559
    clip_y0 = FFMINMAX(render_priv->state.clip_y0, 0, render_priv->height);
 
560
    clip_x1 = FFMINMAX(render_priv->state.clip_x1, 0, render_priv->width);
 
561
    clip_y1 = FFMINMAX(render_priv->state.clip_y1, 0, render_priv->height);
 
562
    b_x0 = 0;
 
563
    b_y0 = 0;
 
564
    b_x1 = bm->w;
 
565
    b_y1 = bm->h;
 
566
 
 
567
    tmp = dst_x - clip_x0;
 
568
    if (tmp < 0) {
 
569
        ass_msg(render_priv->library, MSGL_DBG2, "clip left");
 
570
        b_x0 = -tmp;
 
571
    }
 
572
    tmp = dst_y - clip_y0;
 
573
    if (tmp < 0) {
 
574
        ass_msg(render_priv->library, MSGL_DBG2, "clip top");
 
575
        b_y0 = -tmp;
 
576
    }
 
577
    tmp = clip_x1 - dst_x - bm->w;
 
578
    if (tmp < 0) {
 
579
        ass_msg(render_priv->library, MSGL_DBG2, "clip right");
 
580
        b_x1 = bm->w + tmp;
 
581
    }
 
582
    tmp = clip_y1 - dst_y - bm->h;
 
583
    if (tmp < 0) {
 
584
        ass_msg(render_priv->library, MSGL_DBG2, "clip bottom");
 
585
        b_y1 = bm->h + tmp;
 
586
    }
 
587
 
 
588
    if ((b_y0 >= b_y1) || (b_x0 >= b_x1))
 
589
        return tail;
 
590
 
 
591
    if (brk > b_x0) {           // draw left part
 
592
        if (brk > b_x1)
 
593
            brk = b_x1;
 
594
        img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0,
 
595
                             brk - b_x0, b_y1 - b_y0, bm->w,
 
596
                             dst_x + b_x0, dst_y + b_y0, color);
 
597
        *tail = img;
 
598
        tail = &img->next;
 
599
    }
 
600
    if (brk < b_x1) {           // draw right part
 
601
        if (brk < b_x0)
 
602
            brk = b_x0;
 
603
        img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk,
 
604
                             b_x1 - brk, b_y1 - b_y0, bm->w,
 
605
                             dst_x + brk, dst_y + b_y0, color2);
 
606
        *tail = img;
 
607
        tail = &img->next;
 
608
    }
 
609
    return tail;
 
610
}
 
611
 
 
612
/**
 
613
 * \brief Replace the bitmap buffer in ass_image_t with a copy
 
614
 * \param img ass_image_t to operate on
 
615
 * \return pointer to old bitmap buffer
 
616
 */
 
617
static unsigned char *clone_bitmap_buffer(ass_image_t *img)
 
618
{
 
619
    unsigned char *old_bitmap = img->bitmap;
 
620
    int size = img->stride * (img->h - 1) + img->w;
 
621
    img->bitmap = malloc(size);
 
622
    memcpy(img->bitmap, old_bitmap, size);
 
623
    return old_bitmap;
 
624
}
 
625
 
 
626
/**
 
627
 * \brief Calculate overlapping area of two consecutive bitmaps and in case they
 
628
 * overlap, composite them together
 
629
 * Mainly useful for translucent glyphs and especially borders, to avoid the
 
630
 * luminance adding up where they overlap (which looks ugly)
 
631
 */
 
632
static void
 
633
render_overlap(ass_renderer_t *render_priv, ass_image_t **last_tail,
 
634
               ass_image_t **tail, bitmap_hash_key_t *last_hash,
 
635
               bitmap_hash_key_t *hash)
 
636
{
 
637
    int left, top, bottom, right;
 
638
    int old_left, old_top, w, h, cur_left, cur_top;
 
639
    int x, y, opos, cpos;
 
640
    char m;
 
641
    composite_hash_key_t hk;
 
642
    composite_hash_val_t *hv;
 
643
    composite_hash_val_t chv;
 
644
    int ax = (*last_tail)->dst_x;
 
645
    int ay = (*last_tail)->dst_y;
 
646
    int aw = (*last_tail)->w;
 
647
    int as = (*last_tail)->stride;
 
648
    int ah = (*last_tail)->h;
 
649
    int bx = (*tail)->dst_x;
 
650
    int by = (*tail)->dst_y;
 
651
    int bw = (*tail)->w;
 
652
    int bs = (*tail)->stride;
 
653
    int bh = (*tail)->h;
 
654
    unsigned char *a;
 
655
    unsigned char *b;
 
656
 
 
657
    if ((*last_tail)->bitmap == (*tail)->bitmap)
 
658
        return;
 
659
 
 
660
    if ((*last_tail)->color != (*tail)->color)
 
661
        return;
 
662
 
 
663
    // Calculate overlap coordinates
 
664
    left = (ax > bx) ? ax : bx;
 
665
    top = (ay > by) ? ay : by;
 
666
    right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw);
 
667
    bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh);
 
668
    if ((right <= left) || (bottom <= top))
 
669
        return;
 
670
    old_left = left - ax;
 
671
    old_top = top - ay;
 
672
    w = right - left;
 
673
    h = bottom - top;
 
674
    cur_left = left - bx;
 
675
    cur_top = top - by;
 
676
 
 
677
    // Query cache
 
678
    memset(&hk, 0, sizeof(hk));
 
679
    memcpy(&hk.a, last_hash, sizeof(*last_hash));
 
680
    memcpy(&hk.b, hash, sizeof(*hash));
 
681
    hk.aw = aw;
 
682
    hk.ah = ah;
 
683
    hk.bw = bw;
 
684
    hk.bh = bh;
 
685
    hk.ax = ax;
 
686
    hk.ay = ay;
 
687
    hk.bx = bx;
 
688
    hk.by = by;
 
689
    hv = cache_find_composite(render_priv->cache.composite_cache, &hk);
 
690
    if (hv) {
 
691
        (*last_tail)->bitmap = hv->a;
 
692
        (*tail)->bitmap = hv->b;
 
693
        return;
 
694
    }
 
695
    // Allocate new bitmaps and copy over data
 
696
    a = clone_bitmap_buffer(*last_tail);
 
697
    b = clone_bitmap_buffer(*tail);
 
698
 
 
699
    // Composite overlapping area
 
700
    for (y = 0; y < h; y++)
 
701
        for (x = 0; x < w; x++) {
 
702
            opos = (old_top + y) * (as) + (old_left + x);
 
703
            cpos = (cur_top + y) * (bs) + (cur_left + x);
 
704
            m = (a[opos] > b[cpos]) ? a[opos] : b[cpos];
 
705
            (*last_tail)->bitmap[opos] = 0;
 
706
            (*tail)->bitmap[cpos] = m;
 
707
        }
 
708
 
 
709
    // Insert bitmaps into the cache
 
710
    chv.a = (*last_tail)->bitmap;
 
711
    chv.b = (*tail)->bitmap;
 
712
    cache_add_composite(render_priv->cache.composite_cache, &hk, &chv);
 
713
}
 
714
 
 
715
static void free_list_add(ass_renderer_t *render_priv, void *object)
 
716
{
 
717
    if (!render_priv->free_head) {
 
718
        render_priv->free_head = calloc(1, sizeof(free_list_t));
 
719
        render_priv->free_head->object = object;
 
720
        render_priv->free_tail = render_priv->free_head;
 
721
    } else {
 
722
        free_list_t *l = calloc(1, sizeof(free_list_t));
 
723
        l->object = object;
 
724
        render_priv->free_tail->next = l;
 
725
        render_priv->free_tail = render_priv->free_tail->next;
 
726
    }
 
727
}
 
728
 
 
729
/**
 
730
 * Iterate through a list of bitmaps and blend with clip vector, if
 
731
 * applicable. The blended bitmaps are added to a free list which is freed
 
732
 * at the start of a new frame.
 
733
 */
 
734
static void blend_vector_clip(ass_renderer_t *render_priv,
 
735
                              ass_image_t *head)
 
736
{
 
737
    FT_Glyph glyph;
 
738
    FT_BitmapGlyph clip_bm;
 
739
    ass_image_t *cur;
 
740
    ass_drawing_t *drawing = render_priv->state.clip_drawing;
 
741
    int error;
 
742
 
 
743
    if (!drawing)
 
744
        return;
 
745
 
 
746
    // Rasterize it
 
747
    FT_Glyph_Copy((FT_Glyph) drawing->glyph, &glyph);
 
748
    error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
 
749
    if (error) {
 
750
        ass_msg(render_priv->library, MSGL_V,
 
751
            "Clip vector rasterization failed: %d. Skipping.", error);
 
752
        goto blend_vector_exit;
 
753
    }
 
754
    clip_bm = (FT_BitmapGlyph) glyph;
 
755
    clip_bm->top = -clip_bm->top;
 
756
 
 
757
    assert(clip_bm->bitmap.pitch >= 0);
 
758
 
 
759
    // Iterate through bitmaps and blend/clip them
 
760
    for (cur = head; cur; cur = cur->next) {
 
761
        int left, top, right, bottom, apos, bpos, y, x, w, h;
 
762
        int ax, ay, aw, ah, as;
 
763
        int bx, by, bw, bh, bs;
 
764
        int aleft, atop, bleft, btop;
 
765
        unsigned char *abuffer, *bbuffer, *nbuffer;
 
766
 
 
767
        abuffer = cur->bitmap;
 
768
        bbuffer = clip_bm->bitmap.buffer;
 
769
        ax = cur->dst_x;
 
770
        ay = cur->dst_y;
 
771
        aw = cur->w;
 
772
        ah = cur->h;
 
773
        as = cur->stride;
 
774
        bx = clip_bm->left;
 
775
        by = clip_bm->top;
 
776
        bw = clip_bm->bitmap.width;
 
777
        bh = clip_bm->bitmap.rows;
 
778
        bs = clip_bm->bitmap.pitch;
 
779
 
 
780
        // Calculate overlap coordinates
 
781
        left = (ax > bx) ? ax : bx;
 
782
        top = (ay > by) ? ay : by;
 
783
        right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw);
 
784
        bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh);
 
785
        aleft = left - ax;
 
786
        atop = top - ay;
 
787
        w = right - left;
 
788
        h = bottom - top;
 
789
        bleft = left - bx;
 
790
        btop = top - by;
 
791
 
 
792
        if (render_priv->state.clip_drawing_mode) {
 
793
            // Inverse clip
 
794
            if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
 
795
                ay > by + bh) {
 
796
                continue;
 
797
            }
 
798
 
 
799
            // Allocate new buffer and add to free list
 
800
            nbuffer = malloc(as * ah);
 
801
            free_list_add(render_priv, nbuffer);
 
802
 
 
803
            // Blend together
 
804
            memcpy(nbuffer, abuffer, as * ah);
 
805
            for (y = 0; y < h; y++)
 
806
                for (x = 0; x < w; x++) {
 
807
                    apos = (atop + y) * as + aleft + x;
 
808
                    bpos = (btop + y) * bs + bleft + x;
 
809
                    nbuffer[apos] = FFMAX(0, abuffer[apos] - bbuffer[bpos]);
 
810
                }
 
811
        } else {
 
812
            // Regular clip
 
813
            if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
 
814
                ay > by + bh) {
 
815
                cur->w = cur->h = 0;
 
816
                continue;
 
817
            }
 
818
 
 
819
            // Allocate new buffer and add to free list
 
820
            nbuffer = calloc(as, ah);
 
821
            free_list_add(render_priv, nbuffer);
 
822
 
 
823
            // Blend together
 
824
            for (y = 0; y < h; y++)
 
825
                for (x = 0; x < w; x++) {
 
826
                    apos = (atop + y) * as + aleft + x;
 
827
                    bpos = (btop + y) * bs + bleft + x;
 
828
                    nbuffer[apos] = (abuffer[apos] * bbuffer[bpos] + 255) >> 8;
 
829
                }
 
830
        }
 
831
        cur->bitmap = nbuffer;
 
832
    }
 
833
 
 
834
    // Free clip vector and its bitmap, we don't need it anymore
 
835
    FT_Done_Glyph(glyph);
 
836
blend_vector_exit:
 
837
    ass_drawing_free(render_priv->state.clip_drawing);
 
838
    render_priv->state.clip_drawing = 0;
 
839
}
 
840
 
 
841
/**
 
842
 * \brief Convert text_info_t struct to ass_image_t list
 
843
 * Splits glyphs in halves when needed (for \kf karaoke).
 
844
 */
 
845
static ass_image_t *render_text(ass_renderer_t *render_priv, int dst_x,
 
846
                                int dst_y)
 
847
{
 
848
    int pen_x, pen_y;
 
849
    int i;
 
850
    bitmap_t *bm;
 
851
    ass_image_t *head;
 
852
    ass_image_t **tail = &head;
 
853
    ass_image_t **last_tail = 0;
 
854
    ass_image_t **here_tail = 0;
 
855
    bitmap_hash_key_t *last_hash = 0;
 
856
    text_info_t *text_info = &render_priv->text_info;
 
857
 
 
858
    for (i = 0; i < text_info->length; ++i) {
 
859
        glyph_info_t *info = text_info->glyphs + i;
 
860
        if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s
 
861
            || (info->shadow_x == 0 && info->shadow_y == 0))
 
862
            continue;
 
863
 
 
864
        pen_x =
 
865
            dst_x + (info->pos.x >> 6) +
 
866
            (int) (info->shadow_x * render_priv->border_scale);
 
867
        pen_y =
 
868
            dst_y + (info->pos.y >> 6) +
 
869
            (int) (info->shadow_y * render_priv->border_scale);
 
870
        bm = info->bm_s;
 
871
 
 
872
        here_tail = tail;
 
873
        tail =
 
874
            render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0,
 
875
                         1000000, tail);
 
876
        if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0))
 
877
            render_overlap(render_priv, last_tail, here_tail, last_hash,
 
878
                           &info->hash_key);
 
879
        last_tail = here_tail;
 
880
        last_hash = &info->hash_key;
 
881
    }
 
882
 
 
883
    last_tail = 0;
 
884
    for (i = 0; i < text_info->length; ++i) {
 
885
        glyph_info_t *info = text_info->glyphs + i;
 
886
        if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o)
 
887
            continue;
 
888
 
 
889
        pen_x = dst_x + (info->pos.x >> 6);
 
890
        pen_y = dst_y + (info->pos.y >> 6);
 
891
        bm = info->bm_o;
 
892
 
 
893
        if ((info->effect_type == EF_KARAOKE_KO)
 
894
            && (info->effect_timing <= (info->bbox.xMax >> 6))) {
 
895
            // do nothing
 
896
        } else {
 
897
            here_tail = tail;
 
898
            tail =
 
899
                render_glyph(render_priv, bm, pen_x, pen_y, info->c[2],
 
900
                             0, 1000000, tail);
 
901
            if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0))
 
902
                render_overlap(render_priv, last_tail, here_tail,
 
903
                               last_hash, &info->hash_key);
 
904
            last_tail = here_tail;
 
905
            last_hash = &info->hash_key;
 
906
        }
 
907
    }
 
908
    for (i = 0; i < text_info->length; ++i) {
 
909
        glyph_info_t *info = text_info->glyphs + i;
 
910
        if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm)
 
911
            continue;
 
912
 
 
913
        pen_x = dst_x + (info->pos.x >> 6);
 
914
        pen_y = dst_y + (info->pos.y >> 6);
 
915
        bm = info->bm;
 
916
 
 
917
        if ((info->effect_type == EF_KARAOKE)
 
918
            || (info->effect_type == EF_KARAOKE_KO)) {
 
919
            if (info->effect_timing > (info->bbox.xMax >> 6))
 
920
                tail =
 
921
                    render_glyph(render_priv, bm, pen_x, pen_y,
 
922
                                 info->c[0], 0, 1000000, tail);
 
923
            else
 
924
                tail =
 
925
                    render_glyph(render_priv, bm, pen_x, pen_y,
 
926
                                 info->c[1], 0, 1000000, tail);
 
927
        } else if (info->effect_type == EF_KARAOKE_KF) {
 
928
            tail =
 
929
                render_glyph(render_priv, bm, pen_x, pen_y, info->c[0],
 
930
                             info->c[1], info->effect_timing, tail);
 
931
        } else
 
932
            tail =
 
933
                render_glyph(render_priv, bm, pen_x, pen_y, info->c[0],
 
934
                             0, 1000000, tail);
 
935
    }
 
936
 
 
937
    *tail = 0;
 
938
    blend_vector_clip(render_priv, head);
 
939
 
 
940
    return head;
 
941
}
 
942
 
 
943
/**
 
944
 * \brief Mapping between script and screen coordinates
 
945
 */
 
946
static double x2scr(ass_renderer_t *render_priv, double x)
 
947
{
 
948
    return x * render_priv->orig_width_nocrop /
 
949
        render_priv->track->PlayResX +
 
950
        FFMAX(render_priv->settings.left_margin, 0);
 
951
}
 
952
static double x2scr_pos(ass_renderer_t *render_priv, double x)
 
953
{
 
954
    return x * render_priv->orig_width / render_priv->track->PlayResX +
 
955
        render_priv->settings.left_margin;
 
956
}
 
957
 
 
958
/**
 
959
 * \brief Mapping between script and screen coordinates
 
960
 */
 
961
static double y2scr(ass_renderer_t *render_priv, double y)
 
962
{
 
963
    return y * render_priv->orig_height_nocrop /
 
964
        render_priv->track->PlayResY +
 
965
        FFMAX(render_priv->settings.top_margin, 0);
 
966
}
 
967
static double y2scr_pos(ass_renderer_t *render_priv, double y)
 
968
{
 
969
    return y * render_priv->orig_height / render_priv->track->PlayResY +
 
970
        render_priv->settings.top_margin;
 
971
}
 
972
 
 
973
// the same for toptitles
 
974
static double y2scr_top(ass_renderer_t *render_priv, double y)
 
975
{
 
976
    if (render_priv->settings.use_margins)
 
977
        return y * render_priv->orig_height_nocrop /
 
978
            render_priv->track->PlayResY;
 
979
    else
 
980
        return y * render_priv->orig_height_nocrop /
 
981
            render_priv->track->PlayResY +
 
982
            FFMAX(render_priv->settings.top_margin, 0);
 
983
}
 
984
 
 
985
// the same for subtitles
 
986
static double y2scr_sub(ass_renderer_t *render_priv, double y)
 
987
{
 
988
    if (render_priv->settings.use_margins)
 
989
        return y * render_priv->orig_height_nocrop /
 
990
            render_priv->track->PlayResY +
 
991
            FFMAX(render_priv->settings.top_margin,
 
992
                  0) + FFMAX(render_priv->settings.bottom_margin, 0);
 
993
    else
 
994
        return y * render_priv->orig_height_nocrop /
 
995
            render_priv->track->PlayResY +
 
996
            FFMAX(render_priv->settings.top_margin, 0);
 
997
}
 
998
 
 
999
static void compute_string_bbox(text_info_t *info, double_bbox_t *bbox)
 
1000
{
 
1001
    int i;
 
1002
 
 
1003
    if (info->length > 0) {
 
1004
        bbox->xMin = 32000;
 
1005
        bbox->xMax = -32000;
 
1006
        bbox->yMin = -1 * info->lines[0].asc + d6_to_double(info->glyphs[0].pos.y);
 
1007
        bbox->yMax = info->height - info->lines[0].asc +
 
1008
                     d6_to_double(info->glyphs[0].pos.y);
 
1009
 
 
1010
        for (i = 0; i < info->length; ++i) {
 
1011
            double s = d6_to_double(info->glyphs[i].pos.x);
 
1012
            double e = s + d6_to_double(info->glyphs[i].advance.x);
 
1013
            bbox->xMin = FFMIN(bbox->xMin, s);
 
1014
            bbox->xMax = FFMAX(bbox->xMax, e);
 
1015
        }
 
1016
    } else
 
1017
        bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.;
 
1018
}
 
1019
 
 
1020
 
 
1021
/**
 
1022
 * \brief Check if starting part of (*p) matches sample. If true, shift p to the first symbol after the matching part.
 
1023
 */
 
1024
static inline int mystrcmp(char **p, const char *sample)
 
1025
{
 
1026
    int len = strlen(sample);
 
1027
    if (strncmp(*p, sample, len) == 0) {
 
1028
        (*p) += len;
 
1029
        return 1;
 
1030
    } else
 
1031
        return 0;
 
1032
}
 
1033
 
 
1034
static void change_font_size(ass_renderer_t *render_priv, double sz)
 
1035
{
 
1036
    double size = sz * render_priv->font_scale;
 
1037
 
 
1038
    if (size < 1)
 
1039
        size = 1;
 
1040
    else if (size > render_priv->height * 2)
 
1041
        size = render_priv->height * 2;
 
1042
 
 
1043
    ass_font_set_size(render_priv->state.font, size);
 
1044
 
 
1045
    render_priv->state.font_size = sz;
 
1046
}
 
1047
 
 
1048
/**
 
1049
 * \brief Change current font, using setting from render_priv->state.
 
1050
 */
 
1051
static void update_font(ass_renderer_t *render_priv)
 
1052
{
 
1053
    unsigned val;
 
1054
    ass_font_desc_t desc;
 
1055
    desc.family = strdup(render_priv->state.family);
 
1056
    desc.treat_family_as_pattern =
 
1057
        render_priv->state.treat_family_as_pattern;
 
1058
 
 
1059
    val = render_priv->state.bold;
 
1060
    // 0 = normal, 1 = bold, >1 = exact weight
 
1061
    if (val == 1 || val == -1)
 
1062
        val = 200;              // bold
 
1063
    else if (val <= 0)
 
1064
        val = 80;               // normal
 
1065
    desc.bold = val;
 
1066
 
 
1067
    val = render_priv->state.italic;
 
1068
    if (val == 1 || val == -1)
 
1069
        val = 110;              // italic
 
1070
    else if (val <= 0)
 
1071
        val = 0;                // normal
 
1072
    desc.italic = val;
 
1073
 
 
1074
    render_priv->state.font =
 
1075
        ass_font_new(render_priv->cache.font_cache, render_priv->library,
 
1076
                     render_priv->ftlibrary, render_priv->fontconfig_priv,
 
1077
                     &desc);
 
1078
    free(desc.family);
 
1079
 
 
1080
    if (render_priv->state.font)
 
1081
        change_font_size(render_priv, render_priv->state.font_size);
 
1082
}
 
1083
 
 
1084
/**
 
1085
 * \brief Change border width
 
1086
 * negative value resets border to style value
 
1087
 */
 
1088
static void change_border(ass_renderer_t *render_priv, double border_x,
 
1089
                          double border_y)
 
1090
{
 
1091
    int bord;
 
1092
    if (!render_priv->state.font)
 
1093
        return;
 
1094
 
 
1095
    if (border_x < 0 && border_y < 0) {
 
1096
        if (render_priv->state.style->BorderStyle == 1)
 
1097
            border_x = border_y = render_priv->state.style->Outline;
 
1098
        else
 
1099
            border_x = border_y = 1.;
 
1100
    }
 
1101
 
 
1102
    render_priv->state.border_x = border_x;
 
1103
    render_priv->state.border_y = border_y;
 
1104
 
 
1105
    bord = 64 * border_x * render_priv->border_scale;
 
1106
    if (bord > 0 && border_x == border_y) {
 
1107
        if (!render_priv->state.stroker) {
 
1108
            int error;
 
1109
#if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1))
 
1110
            error =
 
1111
                FT_Stroker_New(render_priv->ftlibrary,
 
1112
                               &render_priv->state.stroker);
 
1113
#else                           // < 2.2
 
1114
            error =
 
1115
                FT_Stroker_New(render_priv->state.font->faces[0]->
 
1116
                               memory, &render_priv->state.stroker);
 
1117
#endif
 
1118
            if (error) {
 
1119
                ass_msg(render_priv->library, MSGL_V,
 
1120
                        "failed to get stroker");
 
1121
                render_priv->state.stroker = 0;
 
1122
            }
 
1123
        }
 
1124
        if (render_priv->state.stroker)
 
1125
            FT_Stroker_Set(render_priv->state.stroker, bord,
 
1126
                           FT_STROKER_LINECAP_ROUND,
 
1127
                           FT_STROKER_LINEJOIN_ROUND, 0);
 
1128
    } else {
 
1129
        FT_Stroker_Done(render_priv->state.stroker);
 
1130
        render_priv->state.stroker = 0;
 
1131
    }
 
1132
}
 
1133
 
 
1134
#define _r(c)  ((c)>>24)
 
1135
#define _g(c)  (((c)>>16)&0xFF)
 
1136
#define _b(c)  (((c)>>8)&0xFF)
 
1137
#define _a(c)  ((c)&0xFF)
 
1138
 
 
1139
/**
 
1140
 * \brief Calculate a weighted average of two colors
 
1141
 * calculates c1*(1-a) + c2*a, but separately for each component except alpha
 
1142
 */
 
1143
static void change_color(uint32_t *var, uint32_t new, double pwr)
 
1144
{
 
1145
    (*var) = ((uint32_t) (_r(*var) * (1 - pwr) + _r(new) * pwr) << 24) +
 
1146
        ((uint32_t) (_g(*var) * (1 - pwr) + _g(new) * pwr) << 16) +
 
1147
        ((uint32_t) (_b(*var) * (1 - pwr) + _b(new) * pwr) << 8) + _a(*var);
 
1148
}
 
1149
 
 
1150
// like change_color, but for alpha component only
 
1151
static void change_alpha(uint32_t *var, uint32_t new, double pwr)
 
1152
{
 
1153
    *var =
 
1154
        (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) +
 
1155
        (_a(*var) * (1 - pwr) + _a(new) * pwr);
 
1156
}
 
1157
 
 
1158
/**
 
1159
 * \brief Multiply two alpha values
 
1160
 * \param a first value
 
1161
 * \param b second value
 
1162
 * \return result of multiplication
 
1163
 * Parameters and result are limited by 0xFF.
 
1164
 */
 
1165
static uint32_t mult_alpha(uint32_t a, uint32_t b)
 
1166
{
 
1167
    return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF;
 
1168
}
 
1169
 
 
1170
/**
 
1171
 * \brief Calculate alpha value by piecewise linear function
 
1172
 * Used for \fad, \fade implementation.
 
1173
 */
 
1174
static unsigned
 
1175
interpolate_alpha(long long now,
 
1176
                  long long t1, long long t2, long long t3, long long t4,
 
1177
                  unsigned a1, unsigned a2, unsigned a3)
 
1178
{
 
1179
    unsigned a;
 
1180
    double cf;
 
1181
    if (now <= t1) {
 
1182
        a = a1;
 
1183
    } else if (now >= t4) {
 
1184
        a = a3;
 
1185
    } else if (now < t2) {      // and > t1
 
1186
        cf = ((double) (now - t1)) / (t2 - t1);
 
1187
        a = a1 * (1 - cf) + a2 * cf;
 
1188
    } else if (now > t3) {
 
1189
        cf = ((double) (now - t3)) / (t4 - t3);
 
1190
        a = a2 * (1 - cf) + a3 * cf;
 
1191
    } else {                    // t2 <= now <= t3
 
1192
        a = a2;
 
1193
    }
 
1194
 
 
1195
    return a;
 
1196
}
 
1197
 
 
1198
#define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;}
 
1199
#define skip(x) if (*p == (x)) ++p; else { return p; }
 
1200
#define skipopt(x) if (*p == (x)) { ++p; }
 
1201
 
 
1202
/**
 
1203
 * Parse a vector clip into an outline, using the proper scaling
 
1204
 * parameters.  Translate it to correct for screen borders, if needed.
 
1205
 */
 
1206
static char *parse_vector_clip(ass_renderer_t *render_priv, char *p)
 
1207
{
 
1208
    int scale = 1;
 
1209
    int res = 0;
 
1210
    ass_drawing_t *drawing;
 
1211
    render_priv->state.clip_drawing = ass_drawing_new(
 
1212
        render_priv->fontconfig_priv,
 
1213
        render_priv->state.font,
 
1214
        render_priv->settings.hinting,
 
1215
        render_priv->ftlibrary);
 
1216
    drawing = render_priv->state.clip_drawing;
 
1217
    skipopt('(');
 
1218
    res = mystrtoi(&p, &scale);
 
1219
    skipopt(',')
 
1220
    if (!res)
 
1221
        scale = 1;
 
1222
    drawing->scale = scale;
 
1223
    drawing->scale_x = render_priv->font_scale_x * render_priv->font_scale;
 
1224
    drawing->scale_y = render_priv->font_scale;
 
1225
    while (*p != ')' && *p != '}' && p != 0)
 
1226
        ass_drawing_add_char(drawing, *p++);
 
1227
    skipopt(')');
 
1228
    ass_drawing_parse(drawing, 1);
 
1229
    // We need to translate the clip according to screen borders
 
1230
    if (render_priv->settings.left_margin != 0 ||
 
1231
        render_priv->settings.top_margin != 0) {
 
1232
        FT_Vector trans = {
 
1233
            .x = int_to_d6(render_priv->settings.left_margin),
 
1234
            .y = -int_to_d6(render_priv->settings.top_margin),
 
1235
        };
 
1236
        FT_Outline_Translate(&drawing->glyph->outline, trans.x, trans.y);
 
1237
    }
 
1238
    ass_msg(render_priv->library, MSGL_DBG2,
 
1239
            "Parsed vector clip: scale %d, scales (%f, %f) string [%s]\n",
 
1240
            scale, drawing->scale_x, drawing->scale_y, drawing->text);
 
1241
 
 
1242
    return p;
 
1243
}
 
1244
 
 
1245
static void reset_render_context(ass_renderer_t *);
 
1246
 
 
1247
/**
 
1248
 * \brief Parse style override tag.
 
1249
 * \param p string to parse
 
1250
 * \param pwr multiplier for some tag effects (comes from \t tags)
 
1251
 */
 
1252
static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr)
 
1253
{
 
1254
    skip_to('\\');
 
1255
    skip('\\');
 
1256
    if ((*p == '}') || (*p == 0))
 
1257
        return p;
 
1258
 
 
1259
    // New tags introduced in vsfilter 2.39
 
1260
    if (mystrcmp(&p, "xbord")) {
 
1261
        double val;
 
1262
        if (mystrtod(&p, &val))
 
1263
            val = render_priv->state.border_x * (1 - pwr) + val * pwr;
 
1264
        else
 
1265
            val = -1.;
 
1266
        change_border(render_priv, val, render_priv->state.border_y);
 
1267
    } else if (mystrcmp(&p, "ybord")) {
 
1268
        double val;
 
1269
        if (mystrtod(&p, &val))
 
1270
            val = render_priv->state.border_y * (1 - pwr) + val * pwr;
 
1271
        else
 
1272
            val = -1.;
 
1273
        change_border(render_priv, render_priv->state.border_x, val);
 
1274
    } else if (mystrcmp(&p, "xshad")) {
 
1275
        double val;
 
1276
        if (mystrtod(&p, &val))
 
1277
            val = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
 
1278
        else
 
1279
            val = 0.;
 
1280
        render_priv->state.shadow_x = val;
 
1281
    } else if (mystrcmp(&p, "yshad")) {
 
1282
        double val;
 
1283
        if (mystrtod(&p, &val))
 
1284
            val = render_priv->state.shadow_y * (1 - pwr) + val * pwr;
 
1285
        else
 
1286
            val = 0.;
 
1287
        render_priv->state.shadow_y = val;
 
1288
    } else if (mystrcmp(&p, "fax")) {
 
1289
        double val;
 
1290
        if (mystrtod(&p, &val))
 
1291
            render_priv->state.fax =
 
1292
                val * pwr + render_priv->state.fax * (1 - pwr);
 
1293
        else
 
1294
            render_priv->state.fax = 0.;
 
1295
    } else if (mystrcmp(&p, "fay")) {
 
1296
        double val;
 
1297
        if (mystrtod(&p, &val))
 
1298
            render_priv->state.fay =
 
1299
                val * pwr + render_priv->state.fay * (1 - pwr);
 
1300
        else
 
1301
            render_priv->state.fay = 0.;
 
1302
    } else if (mystrcmp(&p, "iclip")) {
 
1303
        int x0, y0, x1, y1;
 
1304
        int res = 1;
 
1305
        char *start = p;
 
1306
        skipopt('(');
 
1307
        res &= mystrtoi(&p, &x0);
 
1308
        skipopt(',');
 
1309
        res &= mystrtoi(&p, &y0);
 
1310
        skipopt(',');
 
1311
        res &= mystrtoi(&p, &x1);
 
1312
        skipopt(',');
 
1313
        res &= mystrtoi(&p, &y1);
 
1314
        skipopt(')');
 
1315
        if (res) {
 
1316
            render_priv->state.clip_x0 =
 
1317
                render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
 
1318
            render_priv->state.clip_x1 =
 
1319
                render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
 
1320
            render_priv->state.clip_y0 =
 
1321
                render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
 
1322
            render_priv->state.clip_y1 =
 
1323
                render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
 
1324
            render_priv->state.clip_mode = 1;
 
1325
        } else if (!render_priv->state.clip_drawing) {
 
1326
            p = parse_vector_clip(render_priv, start);
 
1327
            render_priv->state.clip_drawing_mode = 1;
 
1328
        } else
 
1329
            render_priv->state.clip_mode = 0;
 
1330
    } else if (mystrcmp(&p, "blur")) {
 
1331
        double val;
 
1332
        if (mystrtod(&p, &val)) {
 
1333
            val = render_priv->state.blur * (1 - pwr) + val * pwr;
 
1334
            val = (val < 0) ? 0 : val;
 
1335
            val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val;
 
1336
            render_priv->state.blur = val;
 
1337
        } else
 
1338
            render_priv->state.blur = 0.0;
 
1339
        // ASS standard tags
 
1340
    } else if (mystrcmp(&p, "fsc")) {
 
1341
        char tp = *p++;
 
1342
        double val;
 
1343
        if (tp == 'x') {
 
1344
            if (mystrtod(&p, &val)) {
 
1345
                val /= 100;
 
1346
                render_priv->state.scale_x =
 
1347
                    render_priv->state.scale_x * (1 - pwr) + val * pwr;
 
1348
            } else
 
1349
                render_priv->state.scale_x =
 
1350
                    render_priv->state.style->ScaleX;
 
1351
        } else if (tp == 'y') {
 
1352
            if (mystrtod(&p, &val)) {
 
1353
                val /= 100;
 
1354
                render_priv->state.scale_y =
 
1355
                    render_priv->state.scale_y * (1 - pwr) + val * pwr;
 
1356
            } else
 
1357
                render_priv->state.scale_y =
 
1358
                    render_priv->state.style->ScaleY;
 
1359
        }
 
1360
    } else if (mystrcmp(&p, "fsp")) {
 
1361
        double val;
 
1362
        if (mystrtod(&p, &val))
 
1363
            render_priv->state.hspacing =
 
1364
                render_priv->state.hspacing * (1 - pwr) + val * pwr;
 
1365
        else
 
1366
            render_priv->state.hspacing = render_priv->state.style->Spacing;
 
1367
    } else if (mystrcmp(&p, "fs")) {
 
1368
        double val;
 
1369
        if (mystrtod(&p, &val))
 
1370
            val = render_priv->state.font_size * (1 - pwr) + val * pwr;
 
1371
        else
 
1372
            val = render_priv->state.style->FontSize;
 
1373
        if (render_priv->state.font)
 
1374
            change_font_size(render_priv, val);
 
1375
    } else if (mystrcmp(&p, "bord")) {
 
1376
        double val;
 
1377
        if (mystrtod(&p, &val)) {
 
1378
            if (render_priv->state.border_x == render_priv->state.border_y)
 
1379
                val = render_priv->state.border_x * (1 - pwr) + val * pwr;
 
1380
        } else
 
1381
            val = -1.;          // reset to default
 
1382
        change_border(render_priv, val, val);
 
1383
    } else if (mystrcmp(&p, "move")) {
 
1384
        double x1, x2, y1, y2;
 
1385
        long long t1, t2, delta_t, t;
 
1386
        double x, y;
 
1387
        double k;
 
1388
        skip('(');
 
1389
        mystrtod(&p, &x1);
 
1390
        skip(',');
 
1391
        mystrtod(&p, &y1);
 
1392
        skip(',');
 
1393
        mystrtod(&p, &x2);
 
1394
        skip(',');
 
1395
        mystrtod(&p, &y2);
 
1396
        if (*p == ',') {
 
1397
            skip(',');
 
1398
            mystrtoll(&p, &t1);
 
1399
            skip(',');
 
1400
            mystrtoll(&p, &t2);
 
1401
            ass_msg(render_priv->library, MSGL_DBG2,
 
1402
                   "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %"
 
1403
                   PRId64 ")\n", x1, y1, x2, y2, (int64_t) t1,
 
1404
                   (int64_t) t2);
 
1405
        } else {
 
1406
            t1 = 0;
 
1407
            t2 = render_priv->state.event->Duration;
 
1408
            ass_msg(render_priv->library, MSGL_DBG2,
 
1409
                   "movement: (%f, %f) -> (%f, %f)", x1, y1, x2, y2);
 
1410
        }
 
1411
        skip(')');
 
1412
        delta_t = t2 - t1;
 
1413
        t = render_priv->time - render_priv->state.event->Start;
 
1414
        if (t < t1)
 
1415
            k = 0.;
 
1416
        else if (t > t2)
 
1417
            k = 1.;
 
1418
        else
 
1419
            k = ((double) (t - t1)) / delta_t;
 
1420
        x = k * (x2 - x1) + x1;
 
1421
        y = k * (y2 - y1) + y1;
 
1422
        if (render_priv->state.evt_type != EVENT_POSITIONED) {
 
1423
            render_priv->state.pos_x = x;
 
1424
            render_priv->state.pos_y = y;
 
1425
            render_priv->state.detect_collisions = 0;
 
1426
            render_priv->state.evt_type = EVENT_POSITIONED;
 
1427
        }
 
1428
    } else if (mystrcmp(&p, "frx")) {
 
1429
        double val;
 
1430
        if (mystrtod(&p, &val)) {
 
1431
            val *= M_PI / 180;
 
1432
            render_priv->state.frx =
 
1433
                val * pwr + render_priv->state.frx * (1 - pwr);
 
1434
        } else
 
1435
            render_priv->state.frx = 0.;
 
1436
    } else if (mystrcmp(&p, "fry")) {
 
1437
        double val;
 
1438
        if (mystrtod(&p, &val)) {
 
1439
            val *= M_PI / 180;
 
1440
            render_priv->state.fry =
 
1441
                val * pwr + render_priv->state.fry * (1 - pwr);
 
1442
        } else
 
1443
            render_priv->state.fry = 0.;
 
1444
    } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) {
 
1445
        double val;
 
1446
        if (mystrtod(&p, &val)) {
 
1447
            val *= M_PI / 180;
 
1448
            render_priv->state.frz =
 
1449
                val * pwr + render_priv->state.frz * (1 - pwr);
 
1450
        } else
 
1451
            render_priv->state.frz =
 
1452
                M_PI * render_priv->state.style->Angle / 180.;
 
1453
    } else if (mystrcmp(&p, "fn")) {
 
1454
        char *start = p;
 
1455
        char *family;
 
1456
        skip_to('\\');
 
1457
        if (p > start) {
 
1458
            family = malloc(p - start + 1);
 
1459
            strncpy(family, start, p - start);
 
1460
            family[p - start] = '\0';
 
1461
        } else
 
1462
            family = strdup(render_priv->state.style->FontName);
 
1463
        if (render_priv->state.family)
 
1464
            free(render_priv->state.family);
 
1465
        render_priv->state.family = family;
 
1466
        update_font(render_priv);
 
1467
    } else if (mystrcmp(&p, "alpha")) {
 
1468
        uint32_t val;
 
1469
        int i;
 
1470
        if (strtocolor(render_priv->library, &p, &val)) {
 
1471
            unsigned char a = val >> 24;
 
1472
            for (i = 0; i < 4; ++i)
 
1473
                change_alpha(&render_priv->state.c[i], a, pwr);
 
1474
        } else {
 
1475
            change_alpha(&render_priv->state.c[0],
 
1476
                         render_priv->state.style->PrimaryColour, pwr);
 
1477
            change_alpha(&render_priv->state.c[1],
 
1478
                         render_priv->state.style->SecondaryColour, pwr);
 
1479
            change_alpha(&render_priv->state.c[2],
 
1480
                         render_priv->state.style->OutlineColour, pwr);
 
1481
            change_alpha(&render_priv->state.c[3],
 
1482
                         render_priv->state.style->BackColour, pwr);
 
1483
        }
 
1484
        // FIXME: simplify
 
1485
    } else if (mystrcmp(&p, "an")) {
 
1486
        int val;
 
1487
        if (mystrtoi(&p, &val) && val) {
 
1488
            int v = (val - 1) / 3;      // 0, 1 or 2 for vertical alignment
 
1489
            ass_msg(render_priv->library, MSGL_DBG2, "an %d", val);
 
1490
            if (v != 0)
 
1491
                v = 3 - v;
 
1492
            val = ((val - 1) % 3) + 1;  // horizontal alignment
 
1493
            val += v * 4;
 
1494
            ass_msg(render_priv->library, MSGL_DBG2, "align %d", val);
 
1495
            render_priv->state.alignment = val;
 
1496
        } else
 
1497
            render_priv->state.alignment =
 
1498
                render_priv->state.style->Alignment;
 
1499
    } else if (mystrcmp(&p, "a")) {
 
1500
        int val;
 
1501
        if (mystrtoi(&p, &val) && val)
 
1502
            render_priv->state.alignment = val;
 
1503
        else
 
1504
            render_priv->state.alignment =
 
1505
                render_priv->state.style->Alignment;
 
1506
    } else if (mystrcmp(&p, "pos")) {
 
1507
        double v1, v2;
 
1508
        skip('(');
 
1509
        mystrtod(&p, &v1);
 
1510
        skip(',');
 
1511
        mystrtod(&p, &v2);
 
1512
        skip(')');
 
1513
        ass_msg(render_priv->library, MSGL_DBG2, "pos(%f, %f)", v1, v2);
 
1514
        if (render_priv->state.evt_type == EVENT_POSITIONED) {
 
1515
            ass_msg(render_priv->library, MSGL_V, "Subtitle has a new \\pos "
 
1516
                   "after \\move or \\pos, ignoring");
 
1517
        } else {
 
1518
            render_priv->state.evt_type = EVENT_POSITIONED;
 
1519
            render_priv->state.detect_collisions = 0;
 
1520
            render_priv->state.pos_x = v1;
 
1521
            render_priv->state.pos_y = v2;
 
1522
        }
 
1523
    } else if (mystrcmp(&p, "fad")) {
 
1524
        int a1, a2, a3;
 
1525
        long long t1, t2, t3, t4;
 
1526
        if (*p == 'e')
 
1527
            ++p;                // either \fad or \fade
 
1528
        skip('(');
 
1529
        mystrtoi(&p, &a1);
 
1530
        skip(',');
 
1531
        mystrtoi(&p, &a2);
 
1532
        if (*p == ')') {
 
1533
            // 2-argument version (\fad, according to specs)
 
1534
            // a1 and a2 are fade-in and fade-out durations
 
1535
            t1 = 0;
 
1536
            t4 = render_priv->state.event->Duration;
 
1537
            t2 = a1;
 
1538
            t3 = t4 - a2;
 
1539
            a1 = 0xFF;
 
1540
            a2 = 0;
 
1541
            a3 = 0xFF;
 
1542
        } else {
 
1543
            // 6-argument version (\fade)
 
1544
            // a1 and a2 (and a3) are opacity values
 
1545
            skip(',');
 
1546
            mystrtoi(&p, &a3);
 
1547
            skip(',');
 
1548
            mystrtoll(&p, &t1);
 
1549
            skip(',');
 
1550
            mystrtoll(&p, &t2);
 
1551
            skip(',');
 
1552
            mystrtoll(&p, &t3);
 
1553
            skip(',');
 
1554
            mystrtoll(&p, &t4);
 
1555
        }
 
1556
        skip(')');
 
1557
        render_priv->state.fade =
 
1558
            interpolate_alpha(render_priv->time -
 
1559
                              render_priv->state.event->Start, t1, t2,
 
1560
                              t3, t4, a1, a2, a3);
 
1561
    } else if (mystrcmp(&p, "org")) {
 
1562
        int v1, v2;
 
1563
        skip('(');
 
1564
        mystrtoi(&p, &v1);
 
1565
        skip(',');
 
1566
        mystrtoi(&p, &v2);
 
1567
        skip(')');
 
1568
        ass_msg(render_priv->library, MSGL_DBG2, "org(%d, %d)", v1, v2);
 
1569
        if (!render_priv->state.have_origin) {
 
1570
            render_priv->state.org_x = v1;
 
1571
            render_priv->state.org_y = v2;
 
1572
            render_priv->state.have_origin = 1;
 
1573
            render_priv->state.detect_collisions = 0;
 
1574
        }
 
1575
    } else if (mystrcmp(&p, "t")) {
 
1576
        double v[3];
 
1577
        int v1, v2;
 
1578
        double v3;
 
1579
        int cnt;
 
1580
        long long t1, t2, t, delta_t;
 
1581
        double k;
 
1582
        skip('(');
 
1583
        for (cnt = 0; cnt < 3; ++cnt) {
 
1584
            if (*p == '\\')
 
1585
                break;
 
1586
            v[cnt] = strtod(p, &p);
 
1587
            skip(',');
 
1588
        }
 
1589
        if (cnt == 3) {
 
1590
            v1 = v[0];
 
1591
            v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
 
1592
            v3 = v[2];
 
1593
        } else if (cnt == 2) {
 
1594
            v1 = v[0];
 
1595
            v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
 
1596
            v3 = 1.;
 
1597
        } else if (cnt == 1) {
 
1598
            v1 = 0;
 
1599
            v2 = render_priv->state.event->Duration;
 
1600
            v3 = v[0];
 
1601
        } else {                // cnt == 0
 
1602
            v1 = 0;
 
1603
            v2 = render_priv->state.event->Duration;
 
1604
            v3 = 1.;
 
1605
        }
 
1606
        render_priv->state.detect_collisions = 0;
 
1607
        t1 = v1;
 
1608
        t2 = v2;
 
1609
        delta_t = v2 - v1;
 
1610
        if (v3 < 0.)
 
1611
            v3 = 0.;
 
1612
        t = render_priv->time - render_priv->state.event->Start;        // FIXME: move to render_context
 
1613
        if (t <= t1)
 
1614
            k = 0.;
 
1615
        else if (t >= t2)
 
1616
            k = 1.;
 
1617
        else {
 
1618
            assert(delta_t != 0.);
 
1619
            k = pow(((double) (t - t1)) / delta_t, v3);
 
1620
        }
 
1621
        while (*p == '\\')
 
1622
            p = parse_tag(render_priv, p, k);   // maybe k*pwr ? no, specs forbid nested \t's
 
1623
        skip_to(')');           // in case there is some unknown tag or a comment
 
1624
        skip(')');
 
1625
    } else if (mystrcmp(&p, "clip")) {
 
1626
        char *start = p;
 
1627
        int x0, y0, x1, y1;
 
1628
        int res = 1;
 
1629
        skipopt('(');
 
1630
        res &= mystrtoi(&p, &x0);
 
1631
        skipopt(',');
 
1632
        res &= mystrtoi(&p, &y0);
 
1633
        skipopt(',');
 
1634
        res &= mystrtoi(&p, &x1);
 
1635
        skipopt(',');
 
1636
        res &= mystrtoi(&p, &y1);
 
1637
        skipopt(')');
 
1638
        if (res) {
 
1639
            render_priv->state.clip_x0 =
 
1640
                render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
 
1641
            render_priv->state.clip_x1 =
 
1642
                render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
 
1643
            render_priv->state.clip_y0 =
 
1644
                render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
 
1645
            render_priv->state.clip_y1 =
 
1646
                render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
 
1647
        // Might be a vector clip
 
1648
        } else if (!render_priv->state.clip_drawing) {
 
1649
            p = parse_vector_clip(render_priv, start);
 
1650
            render_priv->state.clip_drawing_mode = 0;
 
1651
        } else {
 
1652
            render_priv->state.clip_x0 = 0;
 
1653
            render_priv->state.clip_y0 = 0;
 
1654
            render_priv->state.clip_x1 = render_priv->track->PlayResX;
 
1655
            render_priv->state.clip_y1 = render_priv->track->PlayResY;
 
1656
        }
 
1657
    } else if (mystrcmp(&p, "c")) {
 
1658
        uint32_t val;
 
1659
        if (!strtocolor(render_priv->library, &p, &val))
 
1660
            val = render_priv->state.style->PrimaryColour;
 
1661
        ass_msg(render_priv->library, MSGL_DBG2, "color: %X", val);
 
1662
        change_color(&render_priv->state.c[0], val, pwr);
 
1663
    } else if ((*p >= '1') && (*p <= '4') && (++p)
 
1664
               && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) {
 
1665
        char n = *(p - 2);
 
1666
        int cidx = n - '1';
 
1667
        char cmd = *(p - 1);
 
1668
        uint32_t val;
 
1669
        assert((n >= '1') && (n <= '4'));
 
1670
        if (!strtocolor(render_priv->library, &p, &val))
 
1671
            switch (n) {
 
1672
            case '1':
 
1673
                val = render_priv->state.style->PrimaryColour;
 
1674
                break;
 
1675
            case '2':
 
1676
                val = render_priv->state.style->SecondaryColour;
 
1677
                break;
 
1678
            case '3':
 
1679
                val = render_priv->state.style->OutlineColour;
 
1680
                break;
 
1681
            case '4':
 
1682
                val = render_priv->state.style->BackColour;
 
1683
                break;
 
1684
            default:
 
1685
                val = 0;
 
1686
                break;          // impossible due to assert; avoid compilation warning
 
1687
            }
 
1688
        switch (cmd) {
 
1689
        case 'c':
 
1690
            change_color(render_priv->state.c + cidx, val, pwr);
 
1691
            break;
 
1692
        case 'a':
 
1693
            change_alpha(render_priv->state.c + cidx, val >> 24, pwr);
 
1694
            break;
 
1695
        default:
 
1696
            ass_msg(render_priv->library, MSGL_WARN, "Bad command: %c%c",
 
1697
                    n, cmd);
 
1698
            break;
 
1699
        }
 
1700
        ass_msg(render_priv->library, MSGL_DBG2, "single c/a at %f: %c%c = %X",
 
1701
               pwr, n, cmd, render_priv->state.c[cidx]);
 
1702
    } else if (mystrcmp(&p, "r")) {
 
1703
        reset_render_context(render_priv);
 
1704
    } else if (mystrcmp(&p, "be")) {
 
1705
        int val;
 
1706
        if (mystrtoi(&p, &val)) {
 
1707
            // Clamp to a safe upper limit, since high values need excessive CPU
 
1708
            val = (val < 0) ? 0 : val;
 
1709
            val = (val > MAX_BE) ? MAX_BE : val;
 
1710
            render_priv->state.be = val;
 
1711
        } else
 
1712
            render_priv->state.be = 0;
 
1713
    } else if (mystrcmp(&p, "b")) {
 
1714
        int b;
 
1715
        if (mystrtoi(&p, &b)) {
 
1716
            if (pwr >= .5)
 
1717
                render_priv->state.bold = b;
 
1718
        } else
 
1719
            render_priv->state.bold = render_priv->state.style->Bold;
 
1720
        update_font(render_priv);
 
1721
    } else if (mystrcmp(&p, "i")) {
 
1722
        int i;
 
1723
        if (mystrtoi(&p, &i)) {
 
1724
            if (pwr >= .5)
 
1725
                render_priv->state.italic = i;
 
1726
        } else
 
1727
            render_priv->state.italic = render_priv->state.style->Italic;
 
1728
        update_font(render_priv);
 
1729
    } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) {
 
1730
        int val = 0;
 
1731
        mystrtoi(&p, &val);
 
1732
        render_priv->state.effect_type = EF_KARAOKE_KF;
 
1733
        if (render_priv->state.effect_timing)
 
1734
            render_priv->state.effect_skip_timing +=
 
1735
                render_priv->state.effect_timing;
 
1736
        render_priv->state.effect_timing = val * 10;
 
1737
    } else if (mystrcmp(&p, "ko")) {
 
1738
        int val = 0;
 
1739
        mystrtoi(&p, &val);
 
1740
        render_priv->state.effect_type = EF_KARAOKE_KO;
 
1741
        if (render_priv->state.effect_timing)
 
1742
            render_priv->state.effect_skip_timing +=
 
1743
                render_priv->state.effect_timing;
 
1744
        render_priv->state.effect_timing = val * 10;
 
1745
    } else if (mystrcmp(&p, "k")) {
 
1746
        int val = 0;
 
1747
        mystrtoi(&p, &val);
 
1748
        render_priv->state.effect_type = EF_KARAOKE;
 
1749
        if (render_priv->state.effect_timing)
 
1750
            render_priv->state.effect_skip_timing +=
 
1751
                render_priv->state.effect_timing;
 
1752
        render_priv->state.effect_timing = val * 10;
 
1753
    } else if (mystrcmp(&p, "shad")) {
 
1754
        double val;
 
1755
        if (mystrtod(&p, &val)) {
 
1756
            if (render_priv->state.shadow_x == render_priv->state.shadow_y)
 
1757
                val = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
 
1758
        } else
 
1759
            val = 0.;
 
1760
        render_priv->state.shadow_x = render_priv->state.shadow_y = val;
 
1761
    } else if (mystrcmp(&p, "s")) {
 
1762
        int val;
 
1763
        if (mystrtoi(&p, &val) && val)
 
1764
            render_priv->state.flags |= DECO_STRIKETHROUGH;
 
1765
        else
 
1766
            render_priv->state.flags &= ~DECO_STRIKETHROUGH;
 
1767
    } else if (mystrcmp(&p, "u")) {
 
1768
        int val;
 
1769
        if (mystrtoi(&p, &val) && val)
 
1770
            render_priv->state.flags |= DECO_UNDERLINE;
 
1771
        else
 
1772
            render_priv->state.flags &= ~DECO_UNDERLINE;
 
1773
    } else if (mystrcmp(&p, "pbo")) {
 
1774
        double val = 0;
 
1775
        if (mystrtod(&p, &val))
 
1776
            render_priv->state.drawing->pbo = val;
 
1777
    } else if (mystrcmp(&p, "p")) {
 
1778
        int val;
 
1779
        if (!mystrtoi(&p, &val))
 
1780
            val = 0;
 
1781
        if (val)
 
1782
            render_priv->state.drawing->scale = val;
 
1783
        render_priv->state.drawing_mode = !!val;
 
1784
    }
 
1785
 
 
1786
    return p;
 
1787
 
 
1788
#undef skip
 
1789
#undef skipopt
 
1790
#undef skip_to
 
1791
}
 
1792
 
 
1793
/**
 
1794
 * \brief Get next ucs4 char from string, parsing and executing style overrides
 
1795
 * \param str string pointer
 
1796
 * \return ucs4 code of the next char
 
1797
 * On return str points to the unparsed part of the string
 
1798
 */
 
1799
static unsigned get_next_char(ass_renderer_t *render_priv, char **str)
 
1800
{
 
1801
    char *p = *str;
 
1802
    unsigned chr;
 
1803
    if (*p == '{') {            // '\0' goes here
 
1804
        p++;
 
1805
        while (1) {
 
1806
            p = parse_tag(render_priv, p, 1.);
 
1807
            if (*p == '}') {    // end of tag
 
1808
                p++;
 
1809
                if (*p == '{') {
 
1810
                    p++;
 
1811
                    continue;
 
1812
                } else
 
1813
                    break;
 
1814
            } else if (*p != '\\')
 
1815
                ass_msg(render_priv->library, MSGL_V,
 
1816
                        "Unable to parse: '%s'", p);
 
1817
            if (*p == 0)
 
1818
                break;
 
1819
        }
 
1820
    }
 
1821
    if (*p == '\t') {
 
1822
        ++p;
 
1823
        *str = p;
 
1824
        return ' ';
 
1825
    }
 
1826
    if (*p == '\\') {
 
1827
        if ((*(p + 1) == 'N')
 
1828
            || ((*(p + 1) == 'n')
 
1829
                && (render_priv->track->WrapStyle == 2))) {
 
1830
            p += 2;
 
1831
            *str = p;
 
1832
            return '\n';
 
1833
        } else if ((*(p + 1) == 'n') || (*(p + 1) == 'h')) {
 
1834
            p += 2;
 
1835
            *str = p;
 
1836
            return ' ';
 
1837
        }
 
1838
    }
 
1839
    chr = ass_utf8_get_char((char **) &p);
 
1840
    *str = p;
 
1841
    return chr;
 
1842
}
 
1843
 
 
1844
static void
 
1845
apply_transition_effects(ass_renderer_t *render_priv, ass_event_t *event)
 
1846
{
 
1847
    int v[4];
 
1848
    int cnt;
 
1849
    char *p = event->Effect;
 
1850
 
 
1851
    if (!p || !*p)
 
1852
        return;
 
1853
 
 
1854
    cnt = 0;
 
1855
    while (cnt < 4 && (p = strchr(p, ';'))) {
 
1856
        v[cnt++] = atoi(++p);
 
1857
    }
 
1858
 
 
1859
    if (strncmp(event->Effect, "Banner;", 7) == 0) {
 
1860
        int delay;
 
1861
        if (cnt < 1) {
 
1862
            ass_msg(render_priv->library, MSGL_V,
 
1863
                    "Error parsing effect: '%s'", event->Effect);
 
1864
            return;
 
1865
        }
 
1866
        if (cnt >= 2 && v[1] == 0)      // right-to-left
 
1867
            render_priv->state.scroll_direction = SCROLL_RL;
 
1868
        else                    // left-to-right
 
1869
            render_priv->state.scroll_direction = SCROLL_LR;
 
1870
 
 
1871
        delay = v[0];
 
1872
        if (delay == 0)
 
1873
            delay = 1;          // ?
 
1874
        render_priv->state.scroll_shift =
 
1875
            (render_priv->time - render_priv->state.event->Start) / delay;
 
1876
        render_priv->state.evt_type = EVENT_HSCROLL;
 
1877
        return;
 
1878
    }
 
1879
 
 
1880
    if (strncmp(event->Effect, "Scroll up;", 10) == 0) {
 
1881
        render_priv->state.scroll_direction = SCROLL_BT;
 
1882
    } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) {
 
1883
        render_priv->state.scroll_direction = SCROLL_TB;
 
1884
    } else {
 
1885
        ass_msg(render_priv->library, MSGL_V,
 
1886
                "Unknown transition effect: '%s'", event->Effect);
 
1887
        return;
 
1888
    }
 
1889
    // parse scroll up/down parameters
 
1890
    {
 
1891
        int delay;
 
1892
        int y0, y1;
 
1893
        if (cnt < 3) {
 
1894
            ass_msg(render_priv->library, MSGL_V,
 
1895
                    "Error parsing effect: '%s'", event->Effect);
 
1896
            return;
 
1897
        }
 
1898
        delay = v[2];
 
1899
        if (delay == 0)
 
1900
            delay = 1;          // ?
 
1901
        render_priv->state.scroll_shift =
 
1902
            (render_priv->time - render_priv->state.event->Start) / delay;
 
1903
        if (v[0] < v[1]) {
 
1904
            y0 = v[0];
 
1905
            y1 = v[1];
 
1906
        } else {
 
1907
            y0 = v[1];
 
1908
            y1 = v[0];
 
1909
        }
 
1910
        if (y1 == 0)
 
1911
            y1 = render_priv->track->PlayResY;  // y0=y1=0 means fullscreen scrolling
 
1912
        render_priv->state.clip_y0 = y0;
 
1913
        render_priv->state.clip_y1 = y1;
 
1914
        render_priv->state.evt_type = EVENT_VSCROLL;
 
1915
        render_priv->state.detect_collisions = 0;
 
1916
    }
 
1917
 
 
1918
}
 
1919
 
 
1920
/**
 
1921
 * \brief partially reset render_context to style values
 
1922
 * Works like {\r}: resets some style overrides
 
1923
 */
 
1924
static void reset_render_context(ass_renderer_t *render_priv)
 
1925
{
 
1926
    render_priv->state.c[0] = render_priv->state.style->PrimaryColour;
 
1927
    render_priv->state.c[1] = render_priv->state.style->SecondaryColour;
 
1928
    render_priv->state.c[2] = render_priv->state.style->OutlineColour;
 
1929
    render_priv->state.c[3] = render_priv->state.style->BackColour;
 
1930
    render_priv->state.flags =
 
1931
        (render_priv->state.style->Underline ? DECO_UNDERLINE : 0) |
 
1932
        (render_priv->state.style->StrikeOut ? DECO_STRIKETHROUGH : 0);
 
1933
    render_priv->state.font_size = render_priv->state.style->FontSize;
 
1934
 
 
1935
    free(render_priv->state.family);
 
1936
    render_priv->state.family = NULL;
 
1937
    render_priv->state.family = strdup(render_priv->state.style->FontName);
 
1938
    render_priv->state.treat_family_as_pattern =
 
1939
        render_priv->state.style->treat_fontname_as_pattern;
 
1940
    render_priv->state.bold = render_priv->state.style->Bold;
 
1941
    render_priv->state.italic = render_priv->state.style->Italic;
 
1942
    update_font(render_priv);
 
1943
 
 
1944
    change_border(render_priv, -1., -1.);
 
1945
    render_priv->state.scale_x = render_priv->state.style->ScaleX;
 
1946
    render_priv->state.scale_y = render_priv->state.style->ScaleY;
 
1947
    render_priv->state.hspacing = render_priv->state.style->Spacing;
 
1948
    render_priv->state.be = 0;
 
1949
    render_priv->state.blur = 0.0;
 
1950
    render_priv->state.shadow_x = render_priv->state.style->Shadow;
 
1951
    render_priv->state.shadow_y = render_priv->state.style->Shadow;
 
1952
    render_priv->state.frx = render_priv->state.fry = 0.;
 
1953
    render_priv->state.frz = M_PI * render_priv->state.style->Angle / 180.;
 
1954
    render_priv->state.fax = render_priv->state.fay = 0.;
 
1955
 
 
1956
    // FIXME: does not reset unsupported attributes.
 
1957
}
 
1958
 
 
1959
/**
 
1960
 * \brief Start new event. Reset render_priv->state.
 
1961
 */
 
1962
static void
 
1963
init_render_context(ass_renderer_t *render_priv, ass_event_t *event)
 
1964
{
 
1965
    render_priv->state.event = event;
 
1966
    render_priv->state.style = render_priv->track->styles + event->Style;
 
1967
 
 
1968
    reset_render_context(render_priv);
 
1969
 
 
1970
    render_priv->state.evt_type = EVENT_NORMAL;
 
1971
    render_priv->state.alignment = render_priv->state.style->Alignment;
 
1972
    render_priv->state.pos_x = 0;
 
1973
    render_priv->state.pos_y = 0;
 
1974
    render_priv->state.org_x = 0;
 
1975
    render_priv->state.org_y = 0;
 
1976
    render_priv->state.have_origin = 0;
 
1977
    render_priv->state.clip_x0 = 0;
 
1978
    render_priv->state.clip_y0 = 0;
 
1979
    render_priv->state.clip_x1 = render_priv->track->PlayResX;
 
1980
    render_priv->state.clip_y1 = render_priv->track->PlayResY;
 
1981
    render_priv->state.detect_collisions = 1;
 
1982
    render_priv->state.fade = 0;
 
1983
    render_priv->state.drawing_mode = 0;
 
1984
    render_priv->state.effect_type = EF_NONE;
 
1985
    render_priv->state.effect_timing = 0;
 
1986
    render_priv->state.effect_skip_timing = 0;
 
1987
    render_priv->state.drawing =
 
1988
        ass_drawing_new(render_priv->fontconfig_priv,
 
1989
                        render_priv->state.font,
 
1990
                        render_priv->settings.hinting,
 
1991
                        render_priv->ftlibrary);
 
1992
 
 
1993
    apply_transition_effects(render_priv, event);
 
1994
}
 
1995
 
 
1996
static void free_render_context(ass_renderer_t *render_priv)
 
1997
{
 
1998
    free(render_priv->state.family);
 
1999
    ass_drawing_free(render_priv->state.drawing);
 
2000
 
 
2001
    render_priv->state.family = NULL;
 
2002
    render_priv->state.drawing = NULL;
 
2003
}
 
2004
 
 
2005
// Calculate the cbox of a series of points
 
2006
static void
 
2007
get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end)
 
2008
{
 
2009
    box->xMin = box->yMin = INT_MAX;
 
2010
    box->xMax = box->yMax = INT_MIN;
 
2011
    int i;
 
2012
 
 
2013
    for (i = start; i < end; i++) {
 
2014
        box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin;
 
2015
        box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax;
 
2016
        box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin;
 
2017
        box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax;
 
2018
    }
 
2019
}
 
2020
 
 
2021
/**
 
2022
 * \brief Fix-up stroker result for huge borders by removing the contours from
 
2023
 * the outline that are harmful.
 
2024
*/
 
2025
static void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x,
 
2026
                                 int border_y)
 
2027
{
 
2028
    int nc = glyph->outline.n_contours;
 
2029
    int begin, stop;
 
2030
    char modified = 0;
 
2031
    char *valid_cont;
 
2032
    int start = 0;
 
2033
    int end = -1;
 
2034
    FT_BBox *boxes = calloc(nc, sizeof(FT_BBox));
 
2035
    int i, j;
 
2036
 
 
2037
    // Create a list of cboxes of the contours
 
2038
    for (i = 0; i < nc; i++) {
 
2039
        start = end + 1;
 
2040
        end = glyph->outline.contours[i];
 
2041
        get_contour_cbox(&boxes[i], glyph->outline.points, start, end);
 
2042
    }
 
2043
 
 
2044
    // if a) contour's cbox is contained in another contours cbox
 
2045
    //    b) contour's height or width is smaller than the border*2
 
2046
    // the contour can be safely removed.
 
2047
    valid_cont = calloc(1, nc);
 
2048
    for (i = 0; i < nc; i++) {
 
2049
        valid_cont[i] = 1;
 
2050
        for (j = 0; j < nc; j++) {
 
2051
            if (i == j)
 
2052
                continue;
 
2053
            if (boxes[i].xMin >= boxes[j].xMin &&
 
2054
                boxes[i].xMax <= boxes[j].xMax &&
 
2055
                boxes[i].yMin >= boxes[j].yMin &&
 
2056
                boxes[i].yMax <= boxes[j].yMax) {
 
2057
                int width = boxes[i].xMax - boxes[i].xMin;
 
2058
                int height = boxes[i].yMax - boxes[i].yMin;
 
2059
                if (width < border_x * 2 || height < border_y * 2) {
 
2060
                    valid_cont[i] = 0;
 
2061
                    modified = 1;
 
2062
                    break;
 
2063
                }
 
2064
            }
 
2065
        }
 
2066
    }
 
2067
 
 
2068
    // Zero-out contours that can be removed; much simpler than copying
 
2069
    if (modified) {
 
2070
        for (i = 0; i < nc; i++) {
 
2071
            if (valid_cont[i])
 
2072
                continue;
 
2073
            begin = (i == 0) ? 0 : glyph->outline.contours[i - 1] + 1;
 
2074
            stop = glyph->outline.contours[i];
 
2075
            for (j = begin; j <= stop; j++) {
 
2076
                glyph->outline.points[j].x = 0;
 
2077
                glyph->outline.points[j].y = 0;
 
2078
                glyph->outline.tags[j] = 0;
 
2079
            }
 
2080
        }
 
2081
    }
 
2082
 
 
2083
    free(boxes);
 
2084
    free(valid_cont);
 
2085
}
 
2086
 
 
2087
/*
 
2088
 * Stroke an outline glyph in x/y direction.  Applies various fixups to get
 
2089
 * around limitations of the FreeType stroker.
 
2090
 */
 
2091
static void stroke_outline_glyph(ass_renderer_t *render_priv,
 
2092
                                 FT_OutlineGlyph *glyph, int sx, int sy)
 
2093
{
 
2094
    if (sx <= 0 && sy <= 0)
 
2095
        return;
 
2096
 
 
2097
    fix_freetype_stroker(*glyph, sx, sy);
 
2098
 
 
2099
    // Borders are equal; use the regular stroker
 
2100
    if (sx == sy && render_priv->state.stroker) {
 
2101
        int error;
 
2102
        error = FT_Glyph_StrokeBorder((FT_Glyph *) glyph,
 
2103
                                      render_priv->state.stroker, 0, 1);
 
2104
        if (error)
 
2105
            ass_msg(render_priv->library, MSGL_WARN,
 
2106
                    "FT_Glyph_Stroke error: %d", error);
 
2107
 
 
2108
    // "Stroke" with the outline emboldener in two passes.
 
2109
    // The outlines look uglier, but the emboldening never adds any points
 
2110
    } else {
 
2111
        int i;
 
2112
        FT_Outline *ol = &(*glyph)->outline;
 
2113
        FT_Outline nol;
 
2114
        FT_Outline_New(render_priv->ftlibrary, ol->n_points,
 
2115
                       ol->n_contours, &nol);
 
2116
        FT_Outline_Copy(ol, &nol);
 
2117
 
 
2118
        FT_Outline_Embolden(ol, sx * 2);
 
2119
        FT_Outline_Translate(ol, -sx, -sx);
 
2120
        FT_Outline_Embolden(&nol, sy * 2);
 
2121
        FT_Outline_Translate(&nol, -sy, -sy);
 
2122
 
 
2123
        for (i = 0; i < ol->n_points; i++)
 
2124
            ol->points[i].y = nol.points[i].y;
 
2125
 
 
2126
        FT_Outline_Done(render_priv->ftlibrary, &nol);
 
2127
    }
 
2128
}
 
2129
 
 
2130
/**
 
2131
 * \brief Get normal and outline (border) glyphs
 
2132
 * \param symbol ucs4 char
 
2133
 * \param info out: struct filled with extracted data
 
2134
 * Tries to get both glyphs from cache.
 
2135
 * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker,
 
2136
 * and add them to cache.
 
2137
 * The glyphs are returned in info->glyph and info->outline_glyph
 
2138
 */
 
2139
static void
 
2140
get_outline_glyph(ass_renderer_t *render_priv, int symbol,
 
2141
                  glyph_info_t *info, ass_drawing_t *drawing)
 
2142
{
 
2143
    glyph_hash_val_t *val;
 
2144
    glyph_hash_key_t key;
 
2145
    memset(&key, 0, sizeof(key));
 
2146
 
 
2147
    if (drawing->hash) {
 
2148
        key.scale_x = double_to_d16(render_priv->state.scale_x);
 
2149
        key.scale_y = double_to_d16(render_priv->state.scale_y);
 
2150
        key.outline.x = render_priv->state.border_x * 0xFFFF;
 
2151
        key.outline.y = render_priv->state.border_y * 0xFFFF;
 
2152
        key.drawing_hash = drawing->hash;
 
2153
    } else {
 
2154
        key.font = render_priv->state.font;
 
2155
        key.size = render_priv->state.font_size;
 
2156
        key.ch = symbol;
 
2157
        key.bold = render_priv->state.bold;
 
2158
        key.italic = render_priv->state.italic;
 
2159
        key.scale_x = double_to_d16(render_priv->state.scale_x);
 
2160
        key.scale_y = double_to_d16(render_priv->state.scale_y);
 
2161
        key.outline.x = render_priv->state.border_x * 0xFFFF;
 
2162
        key.outline.y = render_priv->state.border_y * 0xFFFF;
 
2163
        key.flags = render_priv->state.flags;
 
2164
    }
 
2165
    memset(info, 0, sizeof(glyph_info_t));
 
2166
 
 
2167
    val = cache_find_glyph(render_priv->cache.glyph_cache, &key);
 
2168
    if (val) {
 
2169
        FT_Glyph_Copy(val->glyph, &info->glyph);
 
2170
        if (val->outline_glyph)
 
2171
            FT_Glyph_Copy(val->outline_glyph, &info->outline_glyph);
 
2172
        info->bbox = val->bbox_scaled;
 
2173
        info->advance.x = val->advance.x;
 
2174
        info->advance.y = val->advance.y;
 
2175
        if (drawing->hash) {
 
2176
            drawing->asc = val->asc;
 
2177
            drawing->desc = val->desc;
 
2178
        }
 
2179
    } else {
 
2180
        glyph_hash_val_t v;
 
2181
        if (drawing->hash) {
 
2182
            ass_drawing_parse(drawing, 0);
 
2183
            FT_Glyph_Copy((FT_Glyph) drawing->glyph, &info->glyph);
 
2184
        } else {
 
2185
            info->glyph =
 
2186
                ass_font_get_glyph(render_priv->fontconfig_priv,
 
2187
                                   render_priv->state.font, symbol,
 
2188
                                   render_priv->settings.hinting,
 
2189
                                   render_priv->state.flags);
 
2190
        }
 
2191
        if (!info->glyph)
 
2192
            return;
 
2193
        info->advance.x = d16_to_d6(info->glyph->advance.x);
 
2194
        info->advance.y = d16_to_d6(info->glyph->advance.y);
 
2195
        FT_Glyph_Get_CBox(info->glyph, FT_GLYPH_BBOX_SUBPIXELS, &info->bbox);
 
2196
 
 
2197
        if (render_priv->state.border_x > 0 ||
 
2198
            render_priv->state.border_y > 0) {
 
2199
 
 
2200
            FT_Glyph_Copy(info->glyph, &info->outline_glyph);
 
2201
            stroke_outline_glyph(render_priv,
 
2202
                                 (FT_OutlineGlyph *) &info->outline_glyph,
 
2203
                                 double_to_d6(render_priv->state.border_x *
 
2204
                                              render_priv->border_scale),
 
2205
                                 double_to_d6(render_priv->state.border_y *
 
2206
                                              render_priv->border_scale));
 
2207
        }
 
2208
 
 
2209
        memset(&v, 0, sizeof(v));
 
2210
        FT_Glyph_Copy(info->glyph, &v.glyph);
 
2211
        if (info->outline_glyph)
 
2212
            FT_Glyph_Copy(info->outline_glyph, &v.outline_glyph);
 
2213
        v.advance = info->advance;
 
2214
        v.bbox_scaled = info->bbox;
 
2215
        if (drawing->hash) {
 
2216
            v.asc = drawing->asc;
 
2217
            v.desc = drawing->desc;
 
2218
        }
 
2219
        cache_add_glyph(render_priv->cache.glyph_cache, &key, &v);
 
2220
    }
 
2221
}
 
2222
 
 
2223
static void transform_3d(FT_Vector shift, FT_Glyph *glyph,
 
2224
                         FT_Glyph *glyph2, double frx, double fry,
 
2225
                         double frz, double fax, double fay, double scale);
 
2226
 
 
2227
/**
 
2228
 * \brief Get bitmaps for a glyph
 
2229
 * \param info glyph info
 
2230
 * Tries to get glyph bitmaps from bitmap cache.
 
2231
 * If they can't be found, they are generated by rotating and rendering the glyph.
 
2232
 * After that, bitmaps are added to the cache.
 
2233
 * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow).
 
2234
 */
 
2235
static void
 
2236
get_bitmap_glyph(ass_renderer_t *render_priv, glyph_info_t *info)
 
2237
{
 
2238
    bitmap_hash_val_t *val;
 
2239
    bitmap_hash_key_t *key = &info->hash_key;
 
2240
 
 
2241
    val = cache_find_bitmap(render_priv->cache.bitmap_cache, key);
 
2242
 
 
2243
    if (val) {
 
2244
        info->bm = val->bm;
 
2245
        info->bm_o = val->bm_o;
 
2246
        info->bm_s = val->bm_s;
 
2247
    } else {
 
2248
        FT_Vector shift;
 
2249
        bitmap_hash_val_t hash_val;
 
2250
        int error;
 
2251
        info->bm = info->bm_o = info->bm_s = 0;
 
2252
        if (info->glyph && info->symbol != '\n' && info->symbol != 0) {
 
2253
            // calculating rotation shift vector (from rotation origin to the glyph basepoint)
 
2254
            shift.x = info->hash_key.shift_x;
 
2255
            shift.y = info->hash_key.shift_y;
 
2256
            // apply rotation
 
2257
            transform_3d(shift, &info->glyph, &info->outline_glyph,
 
2258
                         info->frx, info->fry, info->frz, info->fax,
 
2259
                         info->fay, render_priv->font_scale);
 
2260
 
 
2261
            // subpixel shift
 
2262
            if (info->glyph)
 
2263
                FT_Outline_Translate(
 
2264
                    &((FT_OutlineGlyph) info->glyph)->outline,
 
2265
                    info->hash_key.advance.x,
 
2266
                    -info->hash_key.advance.y);
 
2267
            if (info->outline_glyph)
 
2268
                FT_Outline_Translate(
 
2269
                    &((FT_OutlineGlyph) info->outline_glyph)->outline,
 
2270
                    info->hash_key.advance.x,
 
2271
                    -info->hash_key.advance.y);
 
2272
 
 
2273
            // render glyph
 
2274
            error = glyph_to_bitmap(render_priv->library,
 
2275
                                    render_priv->synth_priv,
 
2276
                                    info->glyph, info->outline_glyph,
 
2277
                                    &info->bm, &info->bm_o,
 
2278
                                    &info->bm_s, info->be,
 
2279
                                    info->blur * render_priv->border_scale,
 
2280
                                    info->hash_key.shadow_offset);
 
2281
            if (error)
 
2282
                info->symbol = 0;
 
2283
 
 
2284
            // add bitmaps to cache
 
2285
            hash_val.bm_o = info->bm_o;
 
2286
            hash_val.bm = info->bm;
 
2287
            hash_val.bm_s = info->bm_s;
 
2288
            cache_add_bitmap(render_priv->cache.bitmap_cache,
 
2289
                             &(info->hash_key), &hash_val);
 
2290
        }
 
2291
    }
 
2292
    // deallocate glyphs
 
2293
    if (info->glyph)
 
2294
        FT_Done_Glyph(info->glyph);
 
2295
    if (info->outline_glyph)
 
2296
        FT_Done_Glyph(info->outline_glyph);
 
2297
}
 
2298
 
 
2299
/**
 
2300
 * This function goes through text_info and calculates text parameters.
 
2301
 * The following text_info fields are filled:
 
2302
 *   height
 
2303
 *   lines[].height
 
2304
 *   lines[].asc
 
2305
 *   lines[].desc
 
2306
 */
 
2307
static void measure_text(ass_renderer_t *render_priv)
 
2308
{
 
2309
    text_info_t *text_info = &render_priv->text_info;
 
2310
    int cur_line = 0;
 
2311
    double max_asc = 0., max_desc = 0.;
 
2312
    glyph_info_t *last = NULL;
 
2313
    int i;
 
2314
    int empty_line = 1;
 
2315
    text_info->height = 0.;
 
2316
    for (i = 0; i < text_info->length + 1; ++i) {
 
2317
        if ((i == text_info->length) || text_info->glyphs[i].linebreak) {
 
2318
            if (empty_line && cur_line > 0 && last && i < text_info->length) {
 
2319
                max_asc = d6_to_double(last->asc) / 2.0;
 
2320
                max_desc = d6_to_double(last->desc) / 2.0;
 
2321
            }
 
2322
            text_info->lines[cur_line].asc = max_asc;
 
2323
            text_info->lines[cur_line].desc = max_desc;
 
2324
            text_info->height += max_asc + max_desc;
 
2325
            cur_line++;
 
2326
            max_asc = max_desc = 0.;
 
2327
            empty_line = 1;
 
2328
        } else
 
2329
            empty_line = 0;
 
2330
        if (i < text_info->length) {
 
2331
            glyph_info_t *cur = text_info->glyphs + i;
 
2332
            if (d6_to_double(cur->asc) > max_asc)
 
2333
                max_asc = d6_to_double(cur->asc);
 
2334
            if (d6_to_double(cur->desc) > max_desc)
 
2335
                max_desc = d6_to_double(cur->desc);
 
2336
            if (cur->symbol != '\n' && cur->symbol != 0)
 
2337
                last = cur;
 
2338
        }
 
2339
    }
 
2340
    text_info->height +=
 
2341
        (text_info->n_lines -
 
2342
         1) * render_priv->settings.line_spacing;
 
2343
}
 
2344
 
 
2345
/**
 
2346
 * \brief rearrange text between lines
 
2347
 * \param max_text_width maximal text line width in pixels
 
2348
 * The algo is similar to the one in libvo/sub.c:
 
2349
 * 1. Place text, wrapping it when current line is full
 
2350
 * 2. Try moving words from the end of a line to the beginning of the next one while it reduces
 
2351
 * the difference in lengths between this two lines.
 
2352
 * The result may not be optimal, but usually is good enough.
 
2353
 */
 
2354
static void
 
2355
wrap_lines_smart(ass_renderer_t *render_priv, double max_text_width)
 
2356
{
 
2357
    int i;
 
2358
    glyph_info_t *cur, *s1, *e1, *s2, *s3, *w;
 
2359
    int last_space;
 
2360
    int break_type;
 
2361
    int exit;
 
2362
    double pen_shift_x;
 
2363
    double pen_shift_y;
 
2364
    int cur_line;
 
2365
    text_info_t *text_info = &render_priv->text_info;
 
2366
 
 
2367
    last_space = -1;
 
2368
    text_info->n_lines = 1;
 
2369
    break_type = 0;
 
2370
    s1 = text_info->glyphs;     // current line start
 
2371
    for (i = 0; i < text_info->length; ++i) {
 
2372
        int break_at;
 
2373
        double s_offset, len;
 
2374
        cur = text_info->glyphs + i;
 
2375
        break_at = -1;
 
2376
        s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x);
 
2377
        len = d6_to_double(cur->bbox.xMax + cur->pos.x) - s_offset;
 
2378
 
 
2379
        if (cur->symbol == '\n') {
 
2380
            break_type = 2;
 
2381
            break_at = i;
 
2382
            ass_msg(render_priv->library, MSGL_DBG2,
 
2383
                    "forced line break at %d", break_at);
 
2384
        }
 
2385
 
 
2386
        if ((len >= max_text_width)
 
2387
            && (render_priv->track->WrapStyle != 2)) {
 
2388
            break_type = 1;
 
2389
            break_at = last_space;
 
2390
            if (break_at == -1)
 
2391
                break_at = i - 1;
 
2392
            if (break_at == -1)
 
2393
                break_at = 0;
 
2394
            ass_msg(render_priv->library, MSGL_DBG2, "overfill at %d", i);
 
2395
            ass_msg(render_priv->library, MSGL_DBG2, "line break at %d",
 
2396
                    break_at);
 
2397
        }
 
2398
 
 
2399
        if (break_at != -1) {
 
2400
            // need to use one more line
 
2401
            // marking break_at+1 as start of a new line
 
2402
            int lead = break_at + 1;    // the first symbol of the new line
 
2403
            if (text_info->n_lines >= text_info->max_lines) {
 
2404
                // Raise maximum number of lines
 
2405
                text_info->max_lines *= 2;
 
2406
                text_info->lines = realloc(text_info->lines,
 
2407
                                           sizeof(line_info_t) *
 
2408
                                           text_info->max_lines);
 
2409
            }
 
2410
            if (lead < text_info->length)
 
2411
                text_info->glyphs[lead].linebreak = break_type;
 
2412
            last_space = -1;
 
2413
            s1 = text_info->glyphs + lead;
 
2414
            s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x);
 
2415
            text_info->n_lines++;
 
2416
        }
 
2417
 
 
2418
        if (cur->symbol == ' ')
 
2419
            last_space = i;
 
2420
 
 
2421
        // make sure the hard linebreak is not forgotten when
 
2422
        // there was a new soft linebreak just inserted
 
2423
        if (cur->symbol == '\n' && break_type == 1)
 
2424
            i--;
 
2425
    }
 
2426
#define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
 
2427
    exit = 0;
 
2428
    while (!exit) {
 
2429
        exit = 1;
 
2430
        w = s3 = text_info->glyphs;
 
2431
        s1 = s2 = 0;
 
2432
        for (i = 0; i <= text_info->length; ++i) {
 
2433
            cur = text_info->glyphs + i;
 
2434
            if ((i == text_info->length) || cur->linebreak) {
 
2435
                s1 = s2;
 
2436
                s2 = s3;
 
2437
                s3 = cur;
 
2438
                if (s1 && (s2->linebreak == 1)) {       // have at least 2 lines, and linebreak is 'soft'
 
2439
                    double l1, l2, l1_new, l2_new;
 
2440
 
 
2441
                    w = s2;
 
2442
                    do {
 
2443
                        --w;
 
2444
                    } while ((w > s1) && (w->symbol == ' '));
 
2445
                    while ((w > s1) && (w->symbol != ' ')) {
 
2446
                        --w;
 
2447
                    }
 
2448
                    e1 = w;
 
2449
                    while ((e1 > s1) && (e1->symbol == ' ')) {
 
2450
                        --e1;
 
2451
                    }
 
2452
                    if (w->symbol == ' ')
 
2453
                        ++w;
 
2454
 
 
2455
                    l1 = d6_to_double(((s2 - 1)->bbox.xMax + (s2 - 1)->pos.x) -
 
2456
                        (s1->bbox.xMin + s1->pos.x));
 
2457
                    l2 = d6_to_double(((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) -
 
2458
                        (s2->bbox.xMin + s2->pos.x));
 
2459
                    l1_new = d6_to_double(
 
2460
                        (e1->bbox.xMax + e1->pos.x) -
 
2461
                        (s1->bbox.xMin + s1->pos.x));
 
2462
                    l2_new = d6_to_double(
 
2463
                        ((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) -
 
2464
                        (w->bbox.xMin + w->pos.x));
 
2465
 
 
2466
                    if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) {
 
2467
                        w->linebreak = 1;
 
2468
                        s2->linebreak = 0;
 
2469
                        exit = 0;
 
2470
                    }
 
2471
                }
 
2472
            }
 
2473
            if (i == text_info->length)
 
2474
                break;
 
2475
        }
 
2476
 
 
2477
    }
 
2478
    assert(text_info->n_lines >= 1);
 
2479
#undef DIFF
 
2480
 
 
2481
    measure_text(render_priv);
 
2482
 
 
2483
    pen_shift_x = 0.;
 
2484
    pen_shift_y = 0.;
 
2485
    cur_line = 1;
 
2486
    for (i = 0; i < text_info->length; ++i) {
 
2487
        cur = text_info->glyphs + i;
 
2488
        if (cur->linebreak) {
 
2489
            double height =
 
2490
                text_info->lines[cur_line - 1].desc +
 
2491
                text_info->lines[cur_line].asc;
 
2492
            cur_line++;
 
2493
            pen_shift_x = d6_to_double(-cur->pos.x);
 
2494
            pen_shift_y += height + render_priv->settings.line_spacing;
 
2495
            ass_msg(render_priv->library, MSGL_DBG2,
 
2496
                   "shifting from %d to %d by (%f, %f)", i,
 
2497
                   text_info->length - 1, pen_shift_x, pen_shift_y);
 
2498
        }
 
2499
        cur->pos.x += double_to_d6(pen_shift_x);
 
2500
        cur->pos.y += double_to_d6(pen_shift_y);
 
2501
    }
 
2502
}
 
2503
 
 
2504
/**
 
2505
 * \brief determine karaoke effects
 
2506
 * Karaoke effects cannot be calculated during parse stage (get_next_char()),
 
2507
 * so they are done in a separate step.
 
2508
 * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's
 
2509
 * (the first glyph of the karaoke word)'s effect_type and effect_timing.
 
2510
 * This function:
 
2511
 * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
 
2512
 * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts
 
2513
 * (left part is filled with PrimaryColour, right one - with SecondaryColour).
 
2514
 */
 
2515
static void process_karaoke_effects(ass_renderer_t *render_priv)
 
2516
{
 
2517
    glyph_info_t *cur, *cur2;
 
2518
    glyph_info_t *s1, *e1;      // start and end of the current word
 
2519
    glyph_info_t *s2;           // start of the next word
 
2520
    int i;
 
2521
    int timing;                 // current timing
 
2522
    int tm_start, tm_end;       // timings at start and end of the current word
 
2523
    int tm_current;
 
2524
    double dt;
 
2525
    int x;
 
2526
    int x_start, x_end;
 
2527
 
 
2528
    tm_current = render_priv->time - render_priv->state.event->Start;
 
2529
    timing = 0;
 
2530
    s1 = s2 = 0;
 
2531
    for (i = 0; i <= render_priv->text_info.length; ++i) {
 
2532
        cur = render_priv->text_info.glyphs + i;
 
2533
        if ((i == render_priv->text_info.length)
 
2534
            || (cur->effect_type != EF_NONE)) {
 
2535
            s1 = s2;
 
2536
            s2 = cur;
 
2537
            if (s1) {
 
2538
                e1 = s2 - 1;
 
2539
                tm_start = timing + s1->effect_skip_timing;
 
2540
                tm_end = tm_start + s1->effect_timing;
 
2541
                timing = tm_end;
 
2542
                x_start = 1000000;
 
2543
                x_end = -1000000;
 
2544
                for (cur2 = s1; cur2 <= e1; ++cur2) {
 
2545
                    x_start = FFMIN(x_start, d6_to_int(cur2->bbox.xMin + cur2->pos.x));
 
2546
                    x_end = FFMAX(x_end, d6_to_int(cur2->bbox.xMax + cur2->pos.x));
 
2547
                }
 
2548
 
 
2549
                dt = (tm_current - tm_start);
 
2550
                if ((s1->effect_type == EF_KARAOKE)
 
2551
                    || (s1->effect_type == EF_KARAOKE_KO)) {
 
2552
                    if (dt > 0)
 
2553
                        x = x_end + 1;
 
2554
                    else
 
2555
                        x = x_start;
 
2556
                } else if (s1->effect_type == EF_KARAOKE_KF) {
 
2557
                    dt /= (tm_end - tm_start);
 
2558
                    x = x_start + (x_end - x_start) * dt;
 
2559
                } else {
 
2560
                    ass_msg(render_priv->library, MSGL_ERR,
 
2561
                            "Unknown effect type");
 
2562
                    continue;
 
2563
                }
 
2564
 
 
2565
                for (cur2 = s1; cur2 <= e1; ++cur2) {
 
2566
                    cur2->effect_type = s1->effect_type;
 
2567
                    cur2->effect_timing = x - d6_to_int(cur2->pos.x);
 
2568
                }
 
2569
            }
 
2570
        }
 
2571
    }
 
2572
}
 
2573
 
 
2574
/**
 
2575
 * \brief Calculate base point for positioning and rotation
 
2576
 * \param bbox text bbox
 
2577
 * \param alignment alignment
 
2578
 * \param bx, by out: base point coordinates
 
2579
 */
 
2580
static void get_base_point(double_bbox_t *bbox, int alignment, double *bx, double *by)
 
2581
{
 
2582
    const int halign = alignment & 3;
 
2583
    const int valign = alignment & 12;
 
2584
    if (bx)
 
2585
        switch (halign) {
 
2586
        case HALIGN_LEFT:
 
2587
            *bx = bbox->xMin;
 
2588
            break;
 
2589
        case HALIGN_CENTER:
 
2590
            *bx = (bbox->xMax + bbox->xMin) / 2.0;
 
2591
            break;
 
2592
        case HALIGN_RIGHT:
 
2593
            *bx = bbox->xMax;
 
2594
            break;
 
2595
        }
 
2596
    if (by)
 
2597
        switch (valign) {
 
2598
        case VALIGN_TOP:
 
2599
            *by = bbox->yMin;
 
2600
            break;
 
2601
        case VALIGN_CENTER:
 
2602
            *by = (bbox->yMax + bbox->yMin) / 2.0;
 
2603
            break;
 
2604
        case VALIGN_SUB:
 
2605
            *by = bbox->yMax;
 
2606
            break;
 
2607
        }
 
2608
}
 
2609
 
 
2610
/**
 
2611
 * \brief Apply transformation to outline points of a glyph
 
2612
 * Applies rotations given by frx, fry and frz and projects the points back
 
2613
 * onto the screen plane.
 
2614
 */
 
2615
static void
 
2616
transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx,
 
2617
                    double fry, double frz, double fax, double fay,
 
2618
                    double scale)
 
2619
{
 
2620
    double sx = sin(frx);
 
2621
    double sy = sin(fry);
 
2622
    double sz = sin(frz);
 
2623
    double cx = cos(frx);
 
2624
    double cy = cos(fry);
 
2625
    double cz = cos(frz);
 
2626
    FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline;
 
2627
    FT_Vector *p = outline->points;
 
2628
    double x, y, z, xx, yy, zz;
 
2629
    int i, dist;
 
2630
 
 
2631
    dist = 20000 * scale;
 
2632
    for (i = 0; i < outline->n_points; i++) {
 
2633
        x = (double) p[i].x + shift.x + (-fax * p[i].y);
 
2634
        y = (double) p[i].y + shift.y + (-fay * p[i].x);
 
2635
        z = 0.;
 
2636
 
 
2637
        xx = x * cz + y * sz;
 
2638
        yy = -(x * sz - y * cz);
 
2639
        zz = z;
 
2640
 
 
2641
        x = xx;
 
2642
        y = yy * cx + zz * sx;
 
2643
        z = yy * sx - zz * cx;
 
2644
 
 
2645
        xx = x * cy + z * sy;
 
2646
        yy = y;
 
2647
        zz = x * sy - z * cy;
 
2648
 
 
2649
        zz = FFMAX(zz, 1000 - dist);
 
2650
 
 
2651
        x = (xx * dist) / (zz + dist);
 
2652
        y = (yy * dist) / (zz + dist);
 
2653
        p[i].x = x - shift.x + 0.5;
 
2654
        p[i].y = y - shift.y + 0.5;
 
2655
    }
 
2656
}
 
2657
 
 
2658
/**
 
2659
 * \brief Apply 3d transformation to several objects
 
2660
 * \param shift FreeType vector
 
2661
 * \param glyph FreeType glyph
 
2662
 * \param glyph2 FreeType glyph
 
2663
 * \param frx x-axis rotation angle
 
2664
 * \param fry y-axis rotation angle
 
2665
 * \param frz z-axis rotation angle
 
2666
 * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
 
2667
 */
 
2668
static void
 
2669
transform_3d(FT_Vector shift, FT_Glyph *glyph, FT_Glyph *glyph2,
 
2670
             double frx, double fry, double frz, double fax, double fay,
 
2671
             double scale)
 
2672
{
 
2673
    frx = -frx;
 
2674
    frz = -frz;
 
2675
    if (frx != 0. || fry != 0. || frz != 0. || fax != 0. || fay != 0.) {
 
2676
        if (glyph && *glyph)
 
2677
            transform_3d_points(shift, *glyph, frx, fry, frz,
 
2678
                                fax, fay, scale);
 
2679
 
 
2680
        if (glyph2 && *glyph2)
 
2681
            transform_3d_points(shift, *glyph2, frx, fry, frz,
 
2682
                                fax, fay, scale);
 
2683
    }
 
2684
}
 
2685
 
 
2686
 
 
2687
/**
 
2688
 * \brief Main ass rendering function, glues everything together
 
2689
 * \param event event to render
 
2690
 * \param event_images struct containing resulting images, will also be initialized
 
2691
 * Process event, appending resulting ass_image_t's to images_root.
 
2692
 */
 
2693
static int
 
2694
ass_render_event(ass_renderer_t *render_priv, ass_event_t *event,
 
2695
                 event_images_t *event_images)
 
2696
{
 
2697
    char *p;
 
2698
    FT_UInt previous;
 
2699
    FT_UInt num_glyphs;
 
2700
    FT_Vector pen;
 
2701
    unsigned code;
 
2702
    double_bbox_t bbox;
 
2703
    int i, j;
 
2704
    int MarginL, MarginR, MarginV;
 
2705
    int last_break;
 
2706
    int alignment, halign, valign;
 
2707
    double device_x = 0;
 
2708
    double device_y = 0;
 
2709
    text_info_t *text_info = &render_priv->text_info;
 
2710
    ass_drawing_t *drawing;
 
2711
 
 
2712
    if (event->Style >= render_priv->track->n_styles) {
 
2713
        ass_msg(render_priv->library, MSGL_WARN, "No style found");
 
2714
        return 1;
 
2715
    }
 
2716
    if (!event->Text) {
 
2717
        ass_msg(render_priv->library, MSGL_WARN, "Empty event");
 
2718
        return 1;
 
2719
    }
 
2720
 
 
2721
    init_render_context(render_priv, event);
 
2722
 
 
2723
    drawing = render_priv->state.drawing;
 
2724
    text_info->length = 0;
 
2725
    pen.x = 0;
 
2726
    pen.y = 0;
 
2727
    previous = 0;
 
2728
    num_glyphs = 0;
 
2729
    p = event->Text;
 
2730
    // Event parsing.
 
2731
    while (1) {
 
2732
        // get next char, executing style override
 
2733
        // this affects render_context
 
2734
        do {
 
2735
            code = get_next_char(render_priv, &p);
 
2736
            if (render_priv->state.drawing_mode && code)
 
2737
                ass_drawing_add_char(drawing, (char) code);
 
2738
        } while (code && render_priv->state.drawing_mode);      // skip everything in drawing mode
 
2739
 
 
2740
        // Parse drawing
 
2741
        if (drawing->i) {
 
2742
            drawing->scale_x = render_priv->state.scale_x *
 
2743
                                     render_priv->font_scale_x *
 
2744
                                     render_priv->font_scale;
 
2745
            drawing->scale_y = render_priv->state.scale_y *
 
2746
                                     render_priv->font_scale;
 
2747
            ass_drawing_hash(drawing);
 
2748
            p--;
 
2749
            code = -1;
 
2750
        }
 
2751
 
 
2752
        // face could have been changed in get_next_char
 
2753
        if (!render_priv->state.font) {
 
2754
            free_render_context(render_priv);
 
2755
            return 1;
 
2756
        }
 
2757
 
 
2758
        if (code == 0)
 
2759
            break;
 
2760
 
 
2761
        if (text_info->length >= text_info->max_glyphs) {
 
2762
            // Raise maximum number of glyphs
 
2763
            text_info->max_glyphs *= 2;
 
2764
            text_info->glyphs =
 
2765
                realloc(text_info->glyphs,
 
2766
                        sizeof(glyph_info_t) * text_info->max_glyphs);
 
2767
        }
 
2768
 
 
2769
        // Add kerning to pen
 
2770
        if (previous && code && !drawing->hash) {
 
2771
            FT_Vector delta;
 
2772
            delta =
 
2773
                ass_font_get_kerning(render_priv->state.font, previous,
 
2774
                                     code);
 
2775
            pen.x += delta.x * render_priv->state.scale_x;
 
2776
            pen.y += delta.y * render_priv->state.scale_y;
 
2777
        }
 
2778
 
 
2779
        ass_font_set_transform(render_priv->state.font,
 
2780
                               render_priv->state.scale_x *
 
2781
                               render_priv->font_scale_x,
 
2782
                               render_priv->state.scale_y, NULL);
 
2783
 
 
2784
        get_outline_glyph(render_priv, code,
 
2785
                          text_info->glyphs + text_info->length, drawing);
 
2786
 
 
2787
        text_info->glyphs[text_info->length].pos.x = pen.x;
 
2788
        text_info->glyphs[text_info->length].pos.y = pen.y;
 
2789
 
 
2790
        pen.x += text_info->glyphs[text_info->length].advance.x;
 
2791
        pen.x += double_to_d6(render_priv->state.hspacing *
 
2792
                              render_priv->font_scale);
 
2793
        pen.y += text_info->glyphs[text_info->length].advance.y;
 
2794
        pen.y += render_priv->state.fay *
 
2795
                 text_info->glyphs[text_info->length].advance.x;
 
2796
 
 
2797
        previous = code;
 
2798
 
 
2799
        text_info->glyphs[text_info->length].symbol = code;
 
2800
        text_info->glyphs[text_info->length].linebreak = 0;
 
2801
        for (i = 0; i < 4; ++i) {
 
2802
            uint32_t clr = render_priv->state.c[i];
 
2803
            change_alpha(&clr,
 
2804
                         mult_alpha(_a(clr), render_priv->state.fade), 1.);
 
2805
            text_info->glyphs[text_info->length].c[i] = clr;
 
2806
        }
 
2807
        text_info->glyphs[text_info->length].effect_type =
 
2808
            render_priv->state.effect_type;
 
2809
        text_info->glyphs[text_info->length].effect_timing =
 
2810
            render_priv->state.effect_timing;
 
2811
        text_info->glyphs[text_info->length].effect_skip_timing =
 
2812
            render_priv->state.effect_skip_timing;
 
2813
        text_info->glyphs[text_info->length].be = render_priv->state.be;
 
2814
        text_info->glyphs[text_info->length].blur = render_priv->state.blur;
 
2815
        text_info->glyphs[text_info->length].shadow_x =
 
2816
            render_priv->state.shadow_x;
 
2817
        text_info->glyphs[text_info->length].shadow_y =
 
2818
            render_priv->state.shadow_y;
 
2819
        text_info->glyphs[text_info->length].frx = render_priv->state.frx;
 
2820
        text_info->glyphs[text_info->length].fry = render_priv->state.fry;
 
2821
        text_info->glyphs[text_info->length].frz = render_priv->state.frz;
 
2822
        text_info->glyphs[text_info->length].fax = render_priv->state.fax;
 
2823
        text_info->glyphs[text_info->length].fay = render_priv->state.fay;
 
2824
        if (drawing->hash) {
 
2825
            text_info->glyphs[text_info->length].asc = drawing->asc;
 
2826
            text_info->glyphs[text_info->length].desc = drawing->desc;
 
2827
        } else {
 
2828
            ass_font_get_asc_desc(render_priv->state.font, code,
 
2829
                                  &text_info->glyphs[text_info->length].asc,
 
2830
                                  &text_info->glyphs[text_info->length].desc);
 
2831
 
 
2832
            text_info->glyphs[text_info->length].asc *=
 
2833
                render_priv->state.scale_y;
 
2834
            text_info->glyphs[text_info->length].desc *=
 
2835
                render_priv->state.scale_y;
 
2836
        }
 
2837
 
 
2838
        // fill bitmap_hash_key
 
2839
        if (!drawing->hash) {
 
2840
            text_info->glyphs[text_info->length].hash_key.font =
 
2841
                render_priv->state.font;
 
2842
            text_info->glyphs[text_info->length].hash_key.size =
 
2843
                render_priv->state.font_size;
 
2844
            text_info->glyphs[text_info->length].hash_key.bold =
 
2845
                render_priv->state.bold;
 
2846
            text_info->glyphs[text_info->length].hash_key.italic =
 
2847
                render_priv->state.italic;
 
2848
        } else
 
2849
            text_info->glyphs[text_info->length].hash_key.drawing_hash =
 
2850
                drawing->hash;
 
2851
        text_info->glyphs[text_info->length].hash_key.ch = code;
 
2852
        text_info->glyphs[text_info->length].hash_key.outline.x =
 
2853
            render_priv->state.border_x * 0xFFFF;
 
2854
        text_info->glyphs[text_info->length].hash_key.outline.y =
 
2855
            render_priv->state.border_y * 0xFFFF;
 
2856
        text_info->glyphs[text_info->length].hash_key.scale_x =
 
2857
            render_priv->state.scale_x * 0xFFFF;
 
2858
        text_info->glyphs[text_info->length].hash_key.scale_y =
 
2859
            render_priv->state.scale_y * 0xFFFF;
 
2860
        text_info->glyphs[text_info->length].hash_key.frx =
 
2861
            render_priv->state.frx * 0xFFFF;
 
2862
        text_info->glyphs[text_info->length].hash_key.fry =
 
2863
            render_priv->state.fry * 0xFFFF;
 
2864
        text_info->glyphs[text_info->length].hash_key.frz =
 
2865
            render_priv->state.frz * 0xFFFF;
 
2866
        text_info->glyphs[text_info->length].hash_key.fax =
 
2867
            render_priv->state.fax * 0xFFFF;
 
2868
        text_info->glyphs[text_info->length].hash_key.fay =
 
2869
            render_priv->state.fay * 0xFFFF;
 
2870
        text_info->glyphs[text_info->length].hash_key.advance.x = pen.x;
 
2871
        text_info->glyphs[text_info->length].hash_key.advance.y = pen.y;
 
2872
        text_info->glyphs[text_info->length].hash_key.be =
 
2873
            render_priv->state.be;
 
2874
        text_info->glyphs[text_info->length].hash_key.blur =
 
2875
            render_priv->state.blur;
 
2876
        text_info->glyphs[text_info->length].hash_key.shadow_offset.x =
 
2877
            double_to_d6(
 
2878
                render_priv->state.shadow_x * render_priv->border_scale -
 
2879
                (int) (render_priv->state.shadow_x *
 
2880
                render_priv->border_scale));
 
2881
        text_info->glyphs[text_info->length].hash_key.shadow_offset.y =
 
2882
            double_to_d6(
 
2883
                render_priv->state.shadow_y * render_priv->border_scale -
 
2884
                (int) (render_priv->state.shadow_y *
 
2885
                render_priv->border_scale));
 
2886
 
 
2887
        text_info->length++;
 
2888
 
 
2889
        render_priv->state.effect_type = EF_NONE;
 
2890
        render_priv->state.effect_timing = 0;
 
2891
        render_priv->state.effect_skip_timing = 0;
 
2892
 
 
2893
        if (drawing->hash) {
 
2894
            ass_drawing_free(drawing);
 
2895
            drawing = render_priv->state.drawing =
 
2896
                ass_drawing_new(render_priv->fontconfig_priv,
 
2897
                    render_priv->state.font,
 
2898
                    render_priv->settings.hinting,
 
2899
                    render_priv->ftlibrary);
 
2900
        }
 
2901
    }
 
2902
 
 
2903
 
 
2904
    if (text_info->length == 0) {
 
2905
        // no valid symbols in the event; this can be smth like {comment}
 
2906
        free_render_context(render_priv);
 
2907
        return 1;
 
2908
    }
 
2909
    // depends on glyph x coordinates being monotonous, so it should be done before line wrap
 
2910
    process_karaoke_effects(render_priv);
 
2911
 
 
2912
    // alignments
 
2913
    alignment = render_priv->state.alignment;
 
2914
    halign = alignment & 3;
 
2915
    valign = alignment & 12;
 
2916
 
 
2917
    MarginL =
 
2918
        (event->MarginL) ? event->MarginL : render_priv->state.style->
 
2919
        MarginL;
 
2920
    MarginR =
 
2921
        (event->MarginR) ? event->MarginR : render_priv->state.style->
 
2922
        MarginR;
 
2923
    MarginV =
 
2924
        (event->MarginV) ? event->MarginV : render_priv->state.style->
 
2925
        MarginV;
 
2926
 
 
2927
    if (render_priv->state.evt_type != EVENT_HSCROLL) {
 
2928
        double max_text_width;
 
2929
 
 
2930
        // calculate max length of a line
 
2931
        max_text_width =
 
2932
            x2scr(render_priv,
 
2933
                  render_priv->track->PlayResX - MarginR) -
 
2934
            x2scr(render_priv, MarginL);
 
2935
 
 
2936
        // rearrange text in several lines
 
2937
        wrap_lines_smart(render_priv, max_text_width);
 
2938
 
 
2939
        // align text
 
2940
        last_break = -1;
 
2941
        for (i = 1; i < text_info->length + 1; ++i) {   // (text_info->length + 1) is the end of the last line
 
2942
            if ((i == text_info->length)
 
2943
                || text_info->glyphs[i].linebreak) {
 
2944
                double width, shift = 0;
 
2945
                glyph_info_t *first_glyph =
 
2946
                    text_info->glyphs + last_break + 1;
 
2947
                glyph_info_t *last_glyph = text_info->glyphs + i - 1;
 
2948
 
 
2949
                while ((last_glyph > first_glyph)
 
2950
                       && ((last_glyph->symbol == '\n')
 
2951
                           || (last_glyph->symbol == 0)))
 
2952
                    last_glyph--;
 
2953
 
 
2954
                width = d6_to_double(
 
2955
                    last_glyph->pos.x + last_glyph->advance.x -
 
2956
                    first_glyph->pos.x);
 
2957
                if (halign == HALIGN_LEFT) {    // left aligned, no action
 
2958
                    shift = 0;
 
2959
                } else if (halign == HALIGN_RIGHT) {    // right aligned
 
2960
                    shift = max_text_width - width;
 
2961
                } else if (halign == HALIGN_CENTER) {   // centered
 
2962
                    shift = (max_text_width - width) / 2.0;
 
2963
                }
 
2964
                for (j = last_break + 1; j < i; ++j) {
 
2965
                    text_info->glyphs[j].pos.x += double_to_d6(shift);
 
2966
                }
 
2967
                last_break = i - 1;
 
2968
            }
 
2969
        }
 
2970
    } else {                    // render_priv->state.evt_type == EVENT_HSCROLL
 
2971
        measure_text(render_priv);
 
2972
    }
 
2973
 
 
2974
    // determing text bounding box
 
2975
    compute_string_bbox(text_info, &bbox);
 
2976
 
 
2977
    // determine device coordinates for text
 
2978
 
 
2979
    // x coordinate for everything except positioned events
 
2980
    if (render_priv->state.evt_type == EVENT_NORMAL ||
 
2981
        render_priv->state.evt_type == EVENT_VSCROLL) {
 
2982
        device_x = x2scr(render_priv, MarginL);
 
2983
    } else if (render_priv->state.evt_type == EVENT_HSCROLL) {
 
2984
        if (render_priv->state.scroll_direction == SCROLL_RL)
 
2985
            device_x =
 
2986
                x2scr(render_priv,
 
2987
                      render_priv->track->PlayResX -
 
2988
                      render_priv->state.scroll_shift);
 
2989
        else if (render_priv->state.scroll_direction == SCROLL_LR)
 
2990
            device_x =
 
2991
                x2scr(render_priv,
 
2992
                      render_priv->state.scroll_shift) - (bbox.xMax -
 
2993
                                                          bbox.xMin);
 
2994
    }
 
2995
    // y coordinate for everything except positioned events
 
2996
    if (render_priv->state.evt_type == EVENT_NORMAL ||
 
2997
        render_priv->state.evt_type == EVENT_HSCROLL) {
 
2998
        if (valign == VALIGN_TOP) {     // toptitle
 
2999
            device_y =
 
3000
                y2scr_top(render_priv,
 
3001
                          MarginV) + text_info->lines[0].asc;
 
3002
        } else if (valign == VALIGN_CENTER) {   // midtitle
 
3003
            double scr_y =
 
3004
                y2scr(render_priv, render_priv->track->PlayResY / 2.0);
 
3005
            device_y = scr_y - (bbox.yMax + bbox.yMin) / 2.0;
 
3006
        } else {                // subtitle
 
3007
            double scr_y;
 
3008
            if (valign != VALIGN_SUB)
 
3009
                ass_msg(render_priv->library, MSGL_V,
 
3010
                       "Invalid valign, supposing 0 (subtitle)");
 
3011
            scr_y =
 
3012
                y2scr_sub(render_priv,
 
3013
                          render_priv->track->PlayResY - MarginV);
 
3014
            device_y = scr_y;
 
3015
            device_y -= text_info->height;
 
3016
            device_y += text_info->lines[0].asc;
 
3017
        }
 
3018
    } else if (render_priv->state.evt_type == EVENT_VSCROLL) {
 
3019
        if (render_priv->state.scroll_direction == SCROLL_TB)
 
3020
            device_y =
 
3021
                y2scr(render_priv,
 
3022
                      render_priv->state.clip_y0 +
 
3023
                      render_priv->state.scroll_shift) - (bbox.yMax -
 
3024
                                                          bbox.yMin);
 
3025
        else if (render_priv->state.scroll_direction == SCROLL_BT)
 
3026
            device_y =
 
3027
                y2scr(render_priv,
 
3028
                      render_priv->state.clip_y1 -
 
3029
                      render_priv->state.scroll_shift);
 
3030
    }
 
3031
    // positioned events are totally different
 
3032
    if (render_priv->state.evt_type == EVENT_POSITIONED) {
 
3033
        double base_x = 0;
 
3034
        double base_y = 0;
 
3035
        ass_msg(render_priv->library, MSGL_DBG2, "positioned event at %f, %f",
 
3036
               render_priv->state.pos_x, render_priv->state.pos_y);
 
3037
        get_base_point(&bbox, alignment, &base_x, &base_y);
 
3038
        device_x =
 
3039
            x2scr_pos(render_priv, render_priv->state.pos_x) - base_x;
 
3040
        device_y =
 
3041
            y2scr_pos(render_priv, render_priv->state.pos_y) - base_y;
 
3042
    }
 
3043
    // fix clip coordinates (they depend on alignment)
 
3044
    if (render_priv->state.evt_type == EVENT_NORMAL ||
 
3045
        render_priv->state.evt_type == EVENT_HSCROLL ||
 
3046
        render_priv->state.evt_type == EVENT_VSCROLL) {
 
3047
        render_priv->state.clip_x0 =
 
3048
            x2scr(render_priv, render_priv->state.clip_x0);
 
3049
        render_priv->state.clip_x1 =
 
3050
            x2scr(render_priv, render_priv->state.clip_x1);
 
3051
        if (valign == VALIGN_TOP) {
 
3052
            render_priv->state.clip_y0 =
 
3053
                y2scr_top(render_priv, render_priv->state.clip_y0);
 
3054
            render_priv->state.clip_y1 =
 
3055
                y2scr_top(render_priv, render_priv->state.clip_y1);
 
3056
        } else if (valign == VALIGN_CENTER) {
 
3057
            render_priv->state.clip_y0 =
 
3058
                y2scr(render_priv, render_priv->state.clip_y0);
 
3059
            render_priv->state.clip_y1 =
 
3060
                y2scr(render_priv, render_priv->state.clip_y1);
 
3061
        } else if (valign == VALIGN_SUB) {
 
3062
            render_priv->state.clip_y0 =
 
3063
                y2scr_sub(render_priv, render_priv->state.clip_y0);
 
3064
            render_priv->state.clip_y1 =
 
3065
                y2scr_sub(render_priv, render_priv->state.clip_y1);
 
3066
        }
 
3067
    } else if (render_priv->state.evt_type == EVENT_POSITIONED) {
 
3068
        render_priv->state.clip_x0 =
 
3069
            x2scr_pos(render_priv, render_priv->state.clip_x0);
 
3070
        render_priv->state.clip_x1 =
 
3071
            x2scr_pos(render_priv, render_priv->state.clip_x1);
 
3072
        render_priv->state.clip_y0 =
 
3073
            y2scr_pos(render_priv, render_priv->state.clip_y0);
 
3074
        render_priv->state.clip_y1 =
 
3075
            y2scr_pos(render_priv, render_priv->state.clip_y1);
 
3076
    }
 
3077
    // calculate rotation parameters
 
3078
    {
 
3079
        double_vector_t center;
 
3080
 
 
3081
        if (render_priv->state.have_origin) {
 
3082
            center.x = x2scr(render_priv, render_priv->state.org_x);
 
3083
            center.y = y2scr(render_priv, render_priv->state.org_y);
 
3084
        } else {
 
3085
            double bx = 0., by = 0.;
 
3086
            get_base_point(&bbox, alignment, &bx, &by);
 
3087
            center.x = device_x + bx;
 
3088
            center.y = device_y + by;
 
3089
        }
 
3090
 
 
3091
        for (i = 0; i < text_info->length; ++i) {
 
3092
            glyph_info_t *info = text_info->glyphs + i;
 
3093
 
 
3094
            if (info->hash_key.frx || info->hash_key.fry
 
3095
                || info->hash_key.frz || info->hash_key.fax
 
3096
                || info->hash_key.fay) {
 
3097
                info->hash_key.shift_x = info->pos.x + double_to_d6(device_x - center.x);
 
3098
                info->hash_key.shift_y =
 
3099
                    -(info->pos.y + double_to_d6(device_y - center.y));
 
3100
            } else {
 
3101
                info->hash_key.shift_x = 0;
 
3102
                info->hash_key.shift_y = 0;
 
3103
            }
 
3104
        }
 
3105
    }
 
3106
 
 
3107
    // convert glyphs to bitmaps
 
3108
    for (i = 0; i < text_info->length; ++i) {
 
3109
        glyph_info_t *g = text_info->glyphs + i;
 
3110
        g->hash_key.advance.x =
 
3111
            double_to_d6(device_x - (int) device_x +
 
3112
            d6_to_double(g->pos.x & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY;
 
3113
        g->hash_key.advance.y =
 
3114
            double_to_d6(device_y - (int) device_y +
 
3115
            d6_to_double(g->pos.y & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY;
 
3116
        get_bitmap_glyph(render_priv, text_info->glyphs + i);
 
3117
    }
 
3118
 
 
3119
    memset(event_images, 0, sizeof(*event_images));
 
3120
    event_images->top = device_y - text_info->lines[0].asc;
 
3121
    event_images->height = text_info->height;
 
3122
    event_images->detect_collisions = render_priv->state.detect_collisions;
 
3123
    event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1;
 
3124
    event_images->event = event;
 
3125
    event_images->imgs = render_text(render_priv, (int) device_x, (int) device_y);
 
3126
 
 
3127
    free_render_context(render_priv);
 
3128
 
 
3129
    return 0;
 
3130
}
 
3131
 
 
3132
/**
 
3133
 * \brief deallocate image list
 
3134
 * \param img list pointer
 
3135
 */
 
3136
static void ass_free_images(ass_image_t *img)
 
3137
{
 
3138
    while (img) {
 
3139
        ass_image_t *next = img->next;
 
3140
        free(img);
 
3141
        img = next;
 
3142
    }
 
3143
}
 
3144
 
 
3145
static void ass_reconfigure(ass_renderer_t *priv)
 
3146
{
 
3147
    priv->render_id++;
 
3148
    priv->cache.glyph_cache =
 
3149
        ass_glyph_cache_reset(priv->cache.glyph_cache);
 
3150
    priv->cache.bitmap_cache =
 
3151
        ass_bitmap_cache_reset(priv->cache.bitmap_cache);
 
3152
    priv->cache.composite_cache =
 
3153
        ass_composite_cache_reset(priv->cache.composite_cache);
 
3154
    ass_free_images(priv->prev_images_root);
 
3155
    priv->prev_images_root = 0;
 
3156
}
 
3157
 
 
3158
void ass_set_frame_size(ass_renderer_t *priv, int w, int h)
 
3159
{
 
3160
    if (priv->settings.frame_width != w || priv->settings.frame_height != h) {
 
3161
        priv->settings.frame_width = w;
 
3162
        priv->settings.frame_height = h;
 
3163
        if (priv->settings.aspect == 0.) {
 
3164
            priv->settings.aspect = ((double) w) / h;
 
3165
            priv->settings.pixel_ratio = ((double) w) / h;
 
3166
        }
 
3167
        ass_reconfigure(priv);
 
3168
    }
 
3169
}
 
3170
 
 
3171
void ass_set_margins(ass_renderer_t *priv, int t, int b, int l, int r)
 
3172
{
 
3173
    if (priv->settings.left_margin != l ||
 
3174
        priv->settings.right_margin != r ||
 
3175
        priv->settings.top_margin != t
 
3176
        || priv->settings.bottom_margin != b) {
 
3177
        priv->settings.left_margin = l;
 
3178
        priv->settings.right_margin = r;
 
3179
        priv->settings.top_margin = t;
 
3180
        priv->settings.bottom_margin = b;
 
3181
        ass_reconfigure(priv);
 
3182
    }
 
3183
}
 
3184
 
 
3185
void ass_set_use_margins(ass_renderer_t *priv, int use)
 
3186
{
 
3187
    priv->settings.use_margins = use;
 
3188
}
 
3189
 
 
3190
void ass_set_aspect_ratio(ass_renderer_t *priv, double ar, double par)
 
3191
{
 
3192
    if (priv->settings.aspect != ar || priv->settings.pixel_ratio != par) {
 
3193
        priv->settings.aspect = ar;
 
3194
        priv->settings.pixel_ratio = par;
 
3195
        ass_reconfigure(priv);
 
3196
    }
 
3197
}
 
3198
 
 
3199
void ass_set_font_scale(ass_renderer_t *priv, double font_scale)
 
3200
{
 
3201
    if (priv->settings.font_size_coeff != font_scale) {
 
3202
        priv->settings.font_size_coeff = font_scale;
 
3203
        ass_reconfigure(priv);
 
3204
    }
 
3205
}
 
3206
 
 
3207
void ass_set_hinting(ass_renderer_t *priv, ass_hinting_t ht)
 
3208
{
 
3209
    if (priv->settings.hinting != ht) {
 
3210
        priv->settings.hinting = ht;
 
3211
        ass_reconfigure(priv);
 
3212
    }
 
3213
}
 
3214
 
 
3215
void ass_set_line_spacing(ass_renderer_t *priv, double line_spacing)
 
3216
{
 
3217
    priv->settings.line_spacing = line_spacing;
 
3218
}
 
3219
 
 
3220
void ass_set_fonts(ass_renderer_t *priv, const char *default_font,
 
3221
                   const char *default_family, int fc, const char *config,
 
3222
                   int update)
 
3223
{
 
3224
    free(priv->settings.default_font);
 
3225
    free(priv->settings.default_family);
 
3226
    priv->settings.default_font = default_font ? strdup(default_font) : 0;
 
3227
    priv->settings.default_family =
 
3228
        default_family ? strdup(default_family) : 0;
 
3229
 
 
3230
    if (priv->fontconfig_priv)
 
3231
        fontconfig_done(priv->fontconfig_priv);
 
3232
    priv->fontconfig_priv =
 
3233
        fontconfig_init(priv->library, priv->ftlibrary, default_family,
 
3234
                        default_font, fc, config, update);
 
3235
}
 
3236
 
 
3237
int ass_fonts_update(ass_renderer_t *render_priv)
 
3238
{
 
3239
    return fontconfig_update(render_priv->fontconfig_priv);
 
3240
}
 
3241
 
 
3242
/**
 
3243
 * \brief Start a new frame
 
3244
 */
 
3245
static int
 
3246
ass_start_frame(ass_renderer_t *render_priv, ass_track_t *track,
 
3247
                long long now)
 
3248
{
 
3249
    ass_settings_t *settings_priv = &render_priv->settings;
 
3250
    cache_store_t *cache = &render_priv->cache;
 
3251
 
 
3252
    if (!render_priv->settings.frame_width
 
3253
        && !render_priv->settings.frame_height)
 
3254
        return 1;               // library not initialized
 
3255
 
 
3256
    if (render_priv->library != track->library)
 
3257
        return 1;
 
3258
 
 
3259
    free_list_clear(render_priv);
 
3260
 
 
3261
    if (track->n_events == 0)
 
3262
        return 1;               // nothing to do
 
3263
 
 
3264
    render_priv->width = settings_priv->frame_width;
 
3265
    render_priv->height = settings_priv->frame_height;
 
3266
    render_priv->orig_width =
 
3267
        settings_priv->frame_width - settings_priv->left_margin -
 
3268
        settings_priv->right_margin;
 
3269
    render_priv->orig_height =
 
3270
        settings_priv->frame_height - settings_priv->top_margin -
 
3271
        settings_priv->bottom_margin;
 
3272
    render_priv->orig_width_nocrop =
 
3273
        settings_priv->frame_width - FFMAX(settings_priv->left_margin,
 
3274
                                           0) -
 
3275
        FFMAX(settings_priv->right_margin, 0);
 
3276
    render_priv->orig_height_nocrop =
 
3277
        settings_priv->frame_height - FFMAX(settings_priv->top_margin,
 
3278
                                            0) -
 
3279
        FFMAX(settings_priv->bottom_margin, 0);
 
3280
    render_priv->track = track;
 
3281
    render_priv->time = now;
 
3282
 
 
3283
    ass_lazy_track_init(render_priv);
 
3284
 
 
3285
    render_priv->font_scale = settings_priv->font_size_coeff *
 
3286
        render_priv->orig_height / render_priv->track->PlayResY;
 
3287
    if (render_priv->track->ScaledBorderAndShadow)
 
3288
        render_priv->border_scale =
 
3289
            ((double) render_priv->orig_height) /
 
3290
            render_priv->track->PlayResY;
 
3291
    else
 
3292
        render_priv->border_scale = 1.;
 
3293
 
 
3294
    // PAR correction
 
3295
    render_priv->font_scale_x = render_priv->settings.aspect /
 
3296
                                render_priv->settings.pixel_ratio;
 
3297
 
 
3298
    render_priv->prev_images_root = render_priv->images_root;
 
3299
    render_priv->images_root = 0;
 
3300
 
 
3301
    if (cache->bitmap_cache->cache_size > cache->bitmap_max_size) {
 
3302
        ass_msg(render_priv->library, MSGL_V,
 
3303
                "Hitting hard bitmap cache limit (was: %ld bytes), "
 
3304
                "resetting.", (long) cache->bitmap_cache->cache_size);
 
3305
        cache->bitmap_cache = ass_bitmap_cache_reset(cache->bitmap_cache);
 
3306
        cache->composite_cache = ass_composite_cache_reset(
 
3307
            cache->composite_cache);
 
3308
        ass_free_images(render_priv->prev_images_root);
 
3309
        render_priv->prev_images_root = 0;
 
3310
    }
 
3311
 
 
3312
    if (cache->glyph_cache->count > cache->glyph_max) {
 
3313
        ass_msg(render_priv->library, MSGL_V,
 
3314
            "Hitting hard glyph cache limit (was: %ld glyphs), resetting.",
 
3315
            (long) cache->glyph_cache->count);
 
3316
        cache->glyph_cache = ass_glyph_cache_reset(cache->glyph_cache);
 
3317
    }
 
3318
 
 
3319
    return 0;
 
3320
}
 
3321
 
 
3322
static int cmp_event_layer(const void *p1, const void *p2)
 
3323
{
 
3324
    ass_event_t *e1 = ((event_images_t *) p1)->event;
 
3325
    ass_event_t *e2 = ((event_images_t *) p2)->event;
 
3326
    if (e1->Layer < e2->Layer)
 
3327
        return -1;
 
3328
    if (e1->Layer > e2->Layer)
 
3329
        return 1;
 
3330
    if (e1->ReadOrder < e2->ReadOrder)
 
3331
        return -1;
 
3332
    if (e1->ReadOrder > e2->ReadOrder)
 
3333
        return 1;
 
3334
    return 0;
 
3335
}
 
3336
 
 
3337
#define MAX_EVENTS 100
 
3338
 
 
3339
static render_priv_t *get_render_priv(ass_renderer_t *render_priv,
 
3340
                                      ass_event_t *event)
 
3341
{
 
3342
    if (!event->render_priv)
 
3343
        event->render_priv = calloc(1, sizeof(render_priv_t));
 
3344
    // FIXME: check render_id
 
3345
    if (render_priv->render_id != event->render_priv->render_id) {
 
3346
        memset(event->render_priv, 0, sizeof(render_priv_t));
 
3347
        event->render_priv->render_id = render_priv->render_id;
 
3348
    }
 
3349
    return event->render_priv;
 
3350
}
 
3351
 
 
3352
typedef struct {
 
3353
    int a, b;                   // top and height
 
3354
} segment_t;
 
3355
 
 
3356
static int overlap(segment_t *s1, segment_t *s2)
 
3357
{
 
3358
    if (s1->a >= s2->b || s2->a >= s1->b)
 
3359
        return 0;
 
3360
    return 1;
 
3361
}
 
3362
 
 
3363
static int cmp_segment(const void *p1, const void *p2)
 
3364
{
 
3365
    return ((segment_t *) p1)->a - ((segment_t *) p2)->a;
 
3366
}
 
3367
 
 
3368
static void
 
3369
shift_event(ass_renderer_t *render_priv, event_images_t *ei, int shift)
 
3370
{
 
3371
    ass_image_t *cur = ei->imgs;
 
3372
    while (cur) {
 
3373
        cur->dst_y += shift;
 
3374
        // clip top and bottom
 
3375
        if (cur->dst_y < 0) {
 
3376
            int clip = -cur->dst_y;
 
3377
            cur->h -= clip;
 
3378
            cur->bitmap += clip * cur->stride;
 
3379
            cur->dst_y = 0;
 
3380
        }
 
3381
        if (cur->dst_y + cur->h >= render_priv->height) {
 
3382
            int clip = cur->dst_y + cur->h - render_priv->height;
 
3383
            cur->h -= clip;
 
3384
        }
 
3385
        if (cur->h <= 0) {
 
3386
            cur->h = 0;
 
3387
            cur->dst_y = 0;
 
3388
        }
 
3389
        cur = cur->next;
 
3390
    }
 
3391
    ei->top += shift;
 
3392
}
 
3393
 
 
3394
// dir: 1 - move down
 
3395
//      -1 - move up
 
3396
static int fit_segment(segment_t *s, segment_t *fixed, int *cnt, int dir)
 
3397
{
 
3398
    int i;
 
3399
    int shift = 0;
 
3400
 
 
3401
    if (dir == 1)               // move down
 
3402
        for (i = 0; i < *cnt; ++i) {
 
3403
            if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b)
 
3404
                continue;
 
3405
            shift = fixed[i].b - s->a;
 
3406
    } else                      // dir == -1, move up
 
3407
        for (i = *cnt - 1; i >= 0; --i) {
 
3408
            if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b)
 
3409
                continue;
 
3410
            shift = fixed[i].a - s->b;
 
3411
        }
 
3412
 
 
3413
    fixed[*cnt].a = s->a + shift;
 
3414
    fixed[*cnt].b = s->b + shift;
 
3415
    (*cnt)++;
 
3416
    qsort(fixed, *cnt, sizeof(segment_t), cmp_segment);
 
3417
 
 
3418
    return shift;
 
3419
}
 
3420
 
 
3421
static void
 
3422
fix_collisions(ass_renderer_t *render_priv, event_images_t *imgs, int cnt)
 
3423
{
 
3424
    segment_t used[MAX_EVENTS];
 
3425
    int cnt_used = 0;
 
3426
    int i, j;
 
3427
 
 
3428
    // fill used[] with fixed events
 
3429
    for (i = 0; i < cnt; ++i) {
 
3430
        render_priv_t *priv;
 
3431
        if (!imgs[i].detect_collisions)
 
3432
            continue;
 
3433
        priv = get_render_priv(render_priv, imgs[i].event);
 
3434
        if (priv->height > 0) { // it's a fixed event
 
3435
            segment_t s;
 
3436
            s.a = priv->top;
 
3437
            s.b = priv->top + priv->height;
 
3438
            if (priv->height != imgs[i].height) {       // no, it's not
 
3439
                ass_msg(render_priv->library, MSGL_WARN,
 
3440
                        "Warning! Event height has changed");
 
3441
                priv->top = 0;
 
3442
                priv->height = 0;
 
3443
            }
 
3444
            for (j = 0; j < cnt_used; ++j)
 
3445
                if (overlap(&s, used + j)) {    // no, it's not
 
3446
                    priv->top = 0;
 
3447
                    priv->height = 0;
 
3448
                }
 
3449
            if (priv->height > 0) {     // still a fixed event
 
3450
                used[cnt_used].a = priv->top;
 
3451
                used[cnt_used].b = priv->top + priv->height;
 
3452
                cnt_used++;
 
3453
                shift_event(render_priv, imgs + i, priv->top - imgs[i].top);
 
3454
            }
 
3455
        }
 
3456
    }
 
3457
    qsort(used, cnt_used, sizeof(segment_t), cmp_segment);
 
3458
 
 
3459
    // try to fit other events in free spaces
 
3460
    for (i = 0; i < cnt; ++i) {
 
3461
        render_priv_t *priv;
 
3462
        if (!imgs[i].detect_collisions)
 
3463
            continue;
 
3464
        priv = get_render_priv(render_priv, imgs[i].event);
 
3465
        if (priv->height == 0) {        // not a fixed event
 
3466
            int shift;
 
3467
            segment_t s;
 
3468
            s.a = imgs[i].top;
 
3469
            s.b = imgs[i].top + imgs[i].height;
 
3470
            shift =
 
3471
                fit_segment(&s, used, &cnt_used, imgs[i].shift_direction);
 
3472
            if (shift)
 
3473
                shift_event(render_priv, imgs + i, shift);
 
3474
            // make it fixed
 
3475
            priv->top = imgs[i].top;
 
3476
            priv->height = imgs[i].height;
 
3477
        }
 
3478
 
 
3479
    }
 
3480
}
 
3481
 
 
3482
/**
 
3483
 * \brief compare two images
 
3484
 * \param i1 first image
 
3485
 * \param i2 second image
 
3486
 * \return 0 if identical, 1 if different positions, 2 if different content
 
3487
 */
 
3488
static int ass_image_compare(ass_image_t *i1, ass_image_t *i2)
 
3489
{
 
3490
    if (i1->w != i2->w)
 
3491
        return 2;
 
3492
    if (i1->h != i2->h)
 
3493
        return 2;
 
3494
    if (i1->stride != i2->stride)
 
3495
        return 2;
 
3496
    if (i1->color != i2->color)
 
3497
        return 2;
 
3498
    if (i1->bitmap != i2->bitmap)
 
3499
        return 2;
 
3500
    if (i1->dst_x != i2->dst_x)
 
3501
        return 1;
 
3502
    if (i1->dst_y != i2->dst_y)
 
3503
        return 1;
 
3504
    return 0;
 
3505
}
 
3506
 
 
3507
/**
 
3508
 * \brief compare current and previous image list
 
3509
 * \param priv library handle
 
3510
 * \return 0 if identical, 1 if different positions, 2 if different content
 
3511
 */
 
3512
static int ass_detect_change(ass_renderer_t *priv)
 
3513
{
 
3514
    ass_image_t *img, *img2;
 
3515
    int diff;
 
3516
 
 
3517
    img = priv->prev_images_root;
 
3518
    img2 = priv->images_root;
 
3519
    diff = 0;
 
3520
    while (img && diff < 2) {
 
3521
        ass_image_t *next, *next2;
 
3522
        next = img->next;
 
3523
        if (img2) {
 
3524
            int d = ass_image_compare(img, img2);
 
3525
            if (d > diff)
 
3526
                diff = d;
 
3527
            next2 = img2->next;
 
3528
        } else {
 
3529
            // previous list is shorter
 
3530
            diff = 2;
 
3531
            break;
 
3532
        }
 
3533
        img = next;
 
3534
        img2 = next2;
 
3535
    }
 
3536
 
 
3537
    // is the previous list longer?
 
3538
    if (img2)
 
3539
        diff = 2;
 
3540
 
 
3541
    return diff;
 
3542
}
 
3543
 
 
3544
/**
 
3545
 * \brief render a frame
 
3546
 * \param priv library handle
 
3547
 * \param track track
 
3548
 * \param now current video timestamp (ms)
 
3549
 * \param detect_change a value describing how the new images differ from the previous ones will be written here:
 
3550
 *        0 if identical, 1 if different positions, 2 if different content.
 
3551
 *        Can be NULL, in that case no detection is performed.
 
3552
 */
 
3553
ass_image_t *ass_render_frame(ass_renderer_t *priv, ass_track_t *track,
 
3554
                              long long now, int *detect_change)
 
3555
{
 
3556
    int i, cnt, rc;
 
3557
    event_images_t *last;
 
3558
    ass_image_t **tail;
 
3559
 
 
3560
    // init frame
 
3561
    rc = ass_start_frame(priv, track, now);
 
3562
    if (rc != 0)
 
3563
        return 0;
 
3564
 
 
3565
    // render events separately
 
3566
    cnt = 0;
 
3567
    for (i = 0; i < track->n_events; ++i) {
 
3568
        ass_event_t *event = track->events + i;
 
3569
        if ((event->Start <= now)
 
3570
            && (now < (event->Start + event->Duration))) {
 
3571
            if (cnt >= priv->eimg_size) {
 
3572
                priv->eimg_size += 100;
 
3573
                priv->eimg =
 
3574
                    realloc(priv->eimg,
 
3575
                            priv->eimg_size * sizeof(event_images_t));
 
3576
            }
 
3577
            rc = ass_render_event(priv, event, priv->eimg + cnt);
 
3578
            if (!rc)
 
3579
                ++cnt;
 
3580
        }
 
3581
    }
 
3582
 
 
3583
    // sort by layer
 
3584
    qsort(priv->eimg, cnt, sizeof(event_images_t), cmp_event_layer);
 
3585
 
 
3586
    // call fix_collisions for each group of events with the same layer
 
3587
    last = priv->eimg;
 
3588
    for (i = 1; i < cnt; ++i)
 
3589
        if (last->event->Layer != priv->eimg[i].event->Layer) {
 
3590
            fix_collisions(priv, last, priv->eimg + i - last);
 
3591
            last = priv->eimg + i;
 
3592
        }
 
3593
    if (cnt > 0)
 
3594
        fix_collisions(priv, last, priv->eimg + cnt - last);
 
3595
 
 
3596
    // concat lists
 
3597
    tail = &priv->images_root;
 
3598
    for (i = 0; i < cnt; ++i) {
 
3599
        ass_image_t *cur = priv->eimg[i].imgs;
 
3600
        while (cur) {
 
3601
            *tail = cur;
 
3602
            tail = &cur->next;
 
3603
            cur = cur->next;
 
3604
        }
 
3605
    }
 
3606
 
 
3607
    if (detect_change)
 
3608
        *detect_change = ass_detect_change(priv);
 
3609
 
 
3610
    // free the previous image list
 
3611
    ass_free_images(priv->prev_images_root);
 
3612
    priv->prev_images_root = 0;
 
3613
 
 
3614
    return priv->images_root;
 
3615
}