~ubuntu-branches/ubuntu/hardy/avidemux/hardy

« back to all changes in this revision

Viewing changes to avidemux/ADM_libraries/ADM_libass/ass_render.c

  • Committer: Bazaar Package Importer
  • Author(s): Matvey Kozhev
  • Date: 2007-12-18 13:53:04 UTC
  • mfrom: (1.1.7 upstream)
  • Revision ID: james.westby@ubuntu.com-20071218135304-cdqec2lg2bglyz15
Tags: 1:2.4~preview3-0.0ubuntu1
* Upload to Ubuntu. (LP: #163287, LP: #126572)
* debian/changelog: re-added Ubuntu releases.
* debian/control:
  - Require debhelper >= 5.0.51 (for dh_icons) and imagemagick.
  - Build-depend on libsdl1.2-dev instead of libsdl-dev.
  - Build against newer libx264-dev. (LP: #138854)
  - Removed libamrnb-dev, not in Ubuntu yet.
* debian/rules:
  - Install all icon sizes, using convert (upstream installs none).
  - Added missing calls to dh_installmenu, dh_installman, dh_icons and
    dh_desktop.
* debian/menu, debian/avidemux-qt.menu:
  - Corrected package and executable names.
* debian/avidemux-common.install: Install icons.
* debian/avidemux.common.manpages: Install man/avidemux.1.
* debian/links, debian/avidemux-cli.links, debian/avidemux-gtk.links:
  - Link manpages to avidemux.1.gz.
* debian/install, debian/avidemux-qt.install, debian/avidemux-gtk.desktop,
  debian/avidemux-qt.desktop: Install desktop files.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
 
2
// vim:ts=8:sw=8:noet:ai:
 
3
/*
 
4
  Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
 
5
 
 
6
  This program 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
  This program 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
 
17
  along with this program; if not, write to the Free Software
 
18
  Foundation, Inc., 51 Franklin St, 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
#ifdef HAVE_UNISTD_H
 
27
// avoid warnings due to different definition of this in freetype headers
 
28
#define WE_DO_HAVE_UNISTD_H
 
29
#undef HAVE_UNISTD_H
 
30
#endif
 
31
#include <ft2build.h>
 
32
#include FT_FREETYPE_H
 
33
#include FT_STROKER_H
 
34
#include FT_GLYPH_H
 
35
#include FT_SYNTHESIS_H
 
36
#ifdef WE_DO_HAVE_UNISTD_H
 
37
#undef HAVE_UNISTD_H
 
38
#define HAVE_UNISTD_H
 
39
#endif
 
40
 
 
41
#include "mputils.h"
 
42
 
 
43
#include "ass.h"
 
44
#include "ass_font.h"
 
45
#include "ass_bitmap.h"
 
46
#include "ass_cache.h"
 
47
#include "ass_utils.h"
 
48
#include "ass_fontconfig.h"
 
49
#include "ass_library.h"
 
50
 
 
51
#define MAX_GLYPHS 1000
 
52
#define MAX_LINES 100
 
53
 
 
54
static int last_render_id = 0;
 
55
 
 
56
typedef struct ass_settings_s {
 
57
        int frame_width;
 
58
        int frame_height;
 
59
        double font_size_coeff; // font size multiplier
 
60
        double line_spacing; // additional line spacing (in frame pixels)
 
61
        int top_margin; // height of top margin. Everything except toptitles is shifted down by top_margin.
 
62
        int bottom_margin; // height of bottom margin. (frame_height - top_margin - bottom_margin) is original video height.
 
63
        int left_margin;
 
64
        int right_margin;
 
65
        int use_margins; // 0 - place all subtitles inside original frame
 
66
                         // 1 - use margins for placing toptitles and subtitles
 
67
        double aspect; // frame aspect ratio, d_width / d_height.
 
68
        ass_hinting_t hinting;
 
69
 
 
70
        char* default_font;
 
71
        char* default_family;
 
72
} ass_settings_t;
 
73
 
 
74
// a rendered event
 
75
typedef struct event_images_s {
 
76
        ass_image_t* imgs;
 
77
        int top, height;
 
78
        int detect_collisions;
 
79
        int shift_direction;
 
80
        ass_event_t* event;
 
81
} event_images_t;
 
82
 
 
83
struct ass_renderer_s {
 
84
        ass_library_t* library;
 
85
        FT_Library ftlibrary;
 
86
        fc_instance_t* fontconfig_priv;
 
87
        ass_settings_t settings;
 
88
        int render_id;
 
89
        ass_synth_priv_t* synth_priv;
 
90
 
 
91
        ass_image_t* images_root; // rendering result is stored here
 
92
        ass_image_t* prev_images_root;
 
93
 
 
94
        event_images_t* eimg; // temporary buffer for sorting rendered events
 
95
        int eimg_size; // allocated buffer size
 
96
};
 
97
 
 
98
typedef enum {EF_NONE = 0, EF_KARAOKE, EF_KARAOKE_KF, EF_KARAOKE_KO} effect_t;
 
99
 
 
100
// describes a glyph
 
101
// glyph_info_t and text_info_t are used for text centering and word-wrapping operations
 
102
typedef struct glyph_info_s {
 
103
        unsigned symbol;
 
104
        FT_Glyph glyph;
 
105
        FT_Glyph outline_glyph;
 
106
        bitmap_t* bm; // glyph bitmap
 
107
        bitmap_t* bm_o; // outline bitmap
 
108
        bitmap_t* bm_s; // shadow bitmap
 
109
        FT_BBox bbox;
 
110
        FT_Vector pos;
 
111
        char linebreak; // the first (leading) glyph of some line ?
 
112
        uint32_t c[4]; // colors
 
113
        FT_Vector advance; // 26.6
 
114
        effect_t effect_type;
 
115
        int effect_timing; // time duration of current karaoke word
 
116
                           // after process_karaoke_effects: distance in pixels from the glyph origin.
 
117
                           // part of the glyph to the left of it is displayed in a different color.
 
118
        int effect_skip_timing; // delay after the end of last karaoke word
 
119
        int asc, desc; // font max ascender and descender
 
120
//      int height;
 
121
        int be; // blur edges
 
122
        int shadow;
 
123
        double frx, fry, frz; // rotation
 
124
        
 
125
        bitmap_hash_key_t hash_key;
 
126
} glyph_info_t;
 
127
 
 
128
typedef struct line_info_s {
 
129
        int asc, desc;
 
130
} line_info_t;
 
131
 
 
132
typedef struct text_info_s {
 
133
        glyph_info_t* glyphs;
 
134
        int length;
 
135
        line_info_t lines[MAX_LINES];
 
136
        int n_lines;
 
137
        int height;
 
138
} text_info_t;
 
139
 
 
140
 
 
141
// Renderer state.
 
142
// Values like current font face, color, screen position, clipping and so on are stored here.
 
143
typedef struct render_context_s {
 
144
        ass_event_t* event;
 
145
        ass_style_t* style;
 
146
        
 
147
        ass_font_t* font;
 
148
        char* font_path;
 
149
        double font_size;
 
150
        
 
151
        FT_Stroker stroker;
 
152
        int alignment; // alignment overrides go here; if zero, style value will be used
 
153
        double frx, fry, frz;
 
154
        enum {  EVENT_NORMAL, // "normal" top-, sub- or mid- title
 
155
                EVENT_POSITIONED, // happens after pos(,), margins are ignored
 
156
                EVENT_HSCROLL, // "Banner" transition effect, text_width is unlimited
 
157
                EVENT_VSCROLL // "Scroll up", "Scroll down" transition effects
 
158
                } evt_type;
 
159
        int pos_x, pos_y; // position
 
160
        int org_x, org_y; // origin
 
161
        char have_origin; // origin is explicitly defined; if 0, get_base_point() is used
 
162
        double scale_x, scale_y;
 
163
        double hspacing; // distance between letters, in pixels
 
164
        double border; // outline width
 
165
        uint32_t c[4]; // colors(Primary, Secondary, so on) in RGBA
 
166
        int clip_x0, clip_y0, clip_x1, clip_y1;
 
167
        char detect_collisions;
 
168
        uint32_t fade; // alpha from \fad
 
169
        char be; // blur edges
 
170
        int shadow;
 
171
 
 
172
        effect_t effect_type;
 
173
        int effect_timing;
 
174
        int effect_skip_timing;
 
175
 
 
176
        enum { SCROLL_LR, // left-to-right
 
177
               SCROLL_RL,
 
178
               SCROLL_TB, // top-to-bottom
 
179
               SCROLL_BT
 
180
               } scroll_direction; // for EVENT_HSCROLL, EVENT_VSCROLL
 
181
        int scroll_shift;
 
182
 
 
183
        // face properties
 
184
        char* family;
 
185
        unsigned bold;
 
186
        unsigned italic;
 
187
        
 
188
} render_context_t;
 
189
 
 
190
// frame-global data
 
191
typedef struct frame_context_s {
 
192
        ass_renderer_t* ass_priv;
 
193
        int width, height; // screen dimensions
 
194
        int orig_height; // frame height ( = screen height - margins )
 
195
        int orig_width; // frame width ( = screen width - margins )
 
196
        ass_track_t* track;
 
197
        long long time; // frame's timestamp, ms
 
198
        double font_scale;
 
199
        double font_scale_x; // x scale applied to all glyphs to preserve text aspect ratio
 
200
        double border_scale;
 
201
} frame_context_t;
 
202
 
 
203
static ass_renderer_t* ass_renderer;
 
204
static ass_settings_t* global_settings;
 
205
static text_info_t text_info;
 
206
static render_context_t render_context;
 
207
static frame_context_t frame_context;
 
208
 
 
209
struct render_priv_s {
 
210
        int top, height;
 
211
        int render_id;
 
212
};
 
213
 
 
214
static void ass_lazy_track_init(void)
 
215
{
 
216
        ass_track_t* track = frame_context.track;
 
217
        if (track->PlayResX && track->PlayResY)
 
218
                return;
 
219
        if (!track->PlayResX && !track->PlayResY) {
 
220
                mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NeitherPlayResXNorPlayResYDefined);
 
221
                track->PlayResX = 384;
 
222
                track->PlayResY = 288;
 
223
        } else {
 
224
                double orig_aspect = (global_settings->aspect * frame_context.height * frame_context.orig_width) /
 
225
                        frame_context.orig_height / frame_context.width;
 
226
                if (!track->PlayResY) {
 
227
                        track->PlayResY = track->PlayResX / orig_aspect + .5;
 
228
                        mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY);
 
229
                } else if (!track->PlayResX) {
 
230
                        track->PlayResX = track->PlayResY * orig_aspect + .5;
 
231
                        mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX);
 
232
                }
 
233
        }
 
234
}
 
235
 
 
236
ass_renderer_t* ass_renderer_init(ass_library_t* library)
 
237
{
 
238
        int error;
 
239
        FT_Library ft;
 
240
        ass_renderer_t* priv = 0;
 
241
        
 
242
        memset(&render_context, 0, sizeof(render_context));
 
243
        memset(&frame_context, 0, sizeof(frame_context));
 
244
        memset(&text_info, 0, sizeof(text_info));
 
245
 
 
246
        error = FT_Init_FreeType( &ft );
 
247
        if ( error ) { 
 
248
                mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FT_Init_FreeTypeFailed);
 
249
                goto ass_init_exit;
 
250
        }
 
251
 
 
252
        priv = calloc(1, sizeof(ass_renderer_t));
 
253
        if (!priv) {
 
254
                FT_Done_FreeType(ft);
 
255
                goto ass_init_exit;
 
256
        }
 
257
 
 
258
        priv->synth_priv = ass_synth_init();
 
259
 
 
260
        priv->library = library;
 
261
        priv->ftlibrary = ft;
 
262
        // images_root and related stuff is zero-filled in calloc
 
263
        
 
264
        ass_font_cache_init();
 
265
        ass_bitmap_cache_init();
 
266
        ass_glyph_cache_init();
 
267
 
 
268
        text_info.glyphs = calloc(MAX_GLYPHS, sizeof(glyph_info_t));
 
269
        
 
270
ass_init_exit:
 
271
        if (priv) mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_Init);
 
272
        else mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_InitFailed);
 
273
 
 
274
        return priv;
 
275
}
 
276
 
 
277
void ass_renderer_done(ass_renderer_t* priv)
 
278
{
 
279
        ass_font_cache_done();
 
280
        ass_bitmap_cache_done();
 
281
        ass_glyph_cache_done();
 
282
        if (render_context.stroker) {
 
283
                FT_Stroker_Done(render_context.stroker);
 
284
                render_context.stroker = 0;
 
285
        }
 
286
        if (priv && priv->ftlibrary) FT_Done_FreeType(priv->ftlibrary);
 
287
        if (priv && priv->fontconfig_priv) fontconfig_done(priv->fontconfig_priv);
 
288
        if (priv && priv->synth_priv) ass_synth_done(priv->synth_priv);
 
289
        if (priv && priv->eimg) free(priv->eimg);
 
290
        if (priv) free(priv);
 
291
        if (text_info.glyphs) free(text_info.glyphs);
 
292
}
 
293
 
 
294
/**
 
295
 * \brief Create a new ass_image_t
 
296
 * Parameters are the same as ass_image_t fields.
 
297
 */
 
298
static ass_image_t* my_draw_bitmap(unsigned char* bitmap, int bitmap_w, int bitmap_h, int stride, int dst_x, int dst_y, uint32_t color)
 
299
{
 
300
        ass_image_t* img = calloc(1, sizeof(ass_image_t));
 
301
        
 
302
        img->w = bitmap_w;
 
303
        img->h = bitmap_h;
 
304
        img->stride = stride;
 
305
        img->bitmap = bitmap;
 
306
        img->color = color;
 
307
        img->dst_x = dst_x;
 
308
        img->dst_y = dst_y;
 
309
 
 
310
        return img;
 
311
}
 
312
 
 
313
/**
 
314
 * \brief convert bitmap glyph into ass_image_t struct(s)
 
315
 * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY
 
316
 * \param dst_x bitmap x coordinate in video frame
 
317
 * \param dst_y bitmap y coordinate in video frame
 
318
 * \param color first color, RGBA
 
319
 * \param color2 second color, RGBA
 
320
 * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right
 
321
 * \param tail pointer to the last image's next field, head of the generated list should be stored here
 
322
 * \return pointer to the new list tail
 
323
 * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion.
 
324
 */
 
325
static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t color, uint32_t color2, int brk, ass_image_t** tail)
 
326
{
 
327
        // brk is relative to dst_x
 
328
        // color = color left of brk
 
329
        // color2 = color right of brk
 
330
        int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap
 
331
        int clip_x0, clip_y0, clip_x1, clip_y1;
 
332
        int tmp;
 
333
        ass_image_t* img;
 
334
 
 
335
        dst_x += bm->left;
 
336
        dst_y += bm->top;
 
337
        brk -= bm->left;
 
338
        
 
339
        // clipping
 
340
        clip_x0 = render_context.clip_x0;
 
341
        clip_y0 = render_context.clip_y0;
 
342
        clip_x1 = render_context.clip_x1;
 
343
        clip_y1 = render_context.clip_y1;
 
344
        b_x0 = 0;
 
345
        b_y0 = 0;
 
346
        b_x1 = bm->w;
 
347
        b_y1 = bm->h;
 
348
        
 
349
        tmp = dst_x - clip_x0;
 
350
        if (tmp < 0) {
 
351
                mp_msg(MSGT_ASS, MSGL_DBG2, "clip left\n");
 
352
                b_x0 = - tmp;
 
353
        }
 
354
        tmp = dst_y - clip_y0;
 
355
        if (tmp < 0) {
 
356
                mp_msg(MSGT_ASS, MSGL_DBG2, "clip top\n");
 
357
                b_y0 = - tmp;
 
358
        }
 
359
        tmp = clip_x1 - dst_x - bm->w;
 
360
        if (tmp < 0) {
 
361
                mp_msg(MSGT_ASS, MSGL_DBG2, "clip right\n");
 
362
                b_x1 = bm->w + tmp;
 
363
        }
 
364
        tmp = clip_y1 - dst_y - bm->h;
 
365
        if (tmp < 0) {
 
366
                mp_msg(MSGT_ASS, MSGL_DBG2, "clip bottom\n");
 
367
                b_y1 = bm->h + tmp;
 
368
        }
 
369
        
 
370
        if ((b_y0 >= b_y1) || (b_x0 >= b_x1))
 
371
                return tail;
 
372
 
 
373
        if (brk > b_x0) { // draw left part
 
374
                if (brk > b_x1) brk = b_x1;
 
375
                img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0, 
 
376
                        brk - b_x0, b_y1 - b_y0, bm->w,
 
377
                        dst_x + b_x0, dst_y + b_y0, color);
 
378
                *tail = img;
 
379
                tail = &img->next;
 
380
        }
 
381
        if (brk < b_x1) { // draw right part
 
382
                if (brk < b_x0) brk = b_x0;
 
383
                img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk, 
 
384
                        b_x1 - brk, b_y1 - b_y0, bm->w,
 
385
                        dst_x + brk, dst_y + b_y0, color2);
 
386
                *tail = img;
 
387
                tail = &img->next;
 
388
        }
 
389
        return tail;
 
390
}
 
391
 
 
392
/**
 
393
 * \brief Convert text_info_t struct to ass_image_t list
 
394
 * Splits glyphs in halves when needed (for \kf karaoke).
 
395
 */
 
396
static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y)
 
397
{
 
398
        int pen_x, pen_y;
 
399
        int i;
 
400
        bitmap_t* bm;
 
401
        ass_image_t* head;
 
402
        ass_image_t** tail = &head;
 
403
 
 
404
        for (i = 0; i < text_info->length; ++i) {
 
405
                glyph_info_t* info = text_info->glyphs + i;
 
406
                if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s || (info->shadow == 0))
 
407
                        continue;
 
408
 
 
409
                pen_x = dst_x + info->pos.x + info->shadow;
 
410
                pen_y = dst_y + info->pos.y + info->shadow;
 
411
                bm = info->bm_s;
 
412
 
 
413
                tail = render_glyph(bm, pen_x, pen_y, info->c[3], 0, 1000000, tail);
 
414
        }
 
415
 
 
416
        for (i = 0; i < text_info->length; ++i) {
 
417
                glyph_info_t* info = text_info->glyphs + i;
 
418
                if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o)
 
419
                        continue;
 
420
 
 
421
                pen_x = dst_x + info->pos.x;
 
422
                pen_y = dst_y + info->pos.y;
 
423
                bm = info->bm_o;
 
424
                
 
425
                if ((info->effect_type == EF_KARAOKE_KO) && (info->effect_timing <= info->bbox.xMax)) {
 
426
                        // do nothing
 
427
                } else
 
428
                        tail = render_glyph(bm, pen_x, pen_y, info->c[2], 0, 1000000, tail);
 
429
        }
 
430
        for (i = 0; i < text_info->length; ++i) {
 
431
                glyph_info_t* info = text_info->glyphs + i;
 
432
                if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm)
 
433
                        continue;
 
434
 
 
435
                pen_x = dst_x + info->pos.x;
 
436
                pen_y = dst_y + info->pos.y;
 
437
                bm = info->bm;
 
438
 
 
439
                if ((info->effect_type == EF_KARAOKE) || (info->effect_type == EF_KARAOKE_KO)) {
 
440
                        if (info->effect_timing > info->bbox.xMax)
 
441
                                tail = render_glyph(bm, pen_x, pen_y, info->c[0], 0, 1000000, tail);
 
442
                        else
 
443
                                tail = render_glyph(bm, pen_x, pen_y, info->c[1], 0, 1000000, tail);
 
444
                } else if (info->effect_type == EF_KARAOKE_KF) {
 
445
                        tail = render_glyph(bm, pen_x, pen_y, info->c[0], info->c[1], info->effect_timing, tail);
 
446
                } else
 
447
                        tail = render_glyph(bm, pen_x, pen_y, info->c[0], 0, 1000000, tail);
 
448
        }
 
449
 
 
450
        *tail = 0;
 
451
        return head;
 
452
}
 
453
 
 
454
/**
 
455
 * \brief Mapping between script and screen coordinates
 
456
 */
 
457
static int x2scr(int x) {
 
458
        return x*frame_context.orig_width / frame_context.track->PlayResX + global_settings->left_margin;
 
459
}
 
460
/**
 
461
 * \brief Mapping between script and screen coordinates
 
462
 */
 
463
static int y2scr(int y) {
 
464
        return y * frame_context.orig_height / frame_context.track->PlayResY + global_settings->top_margin;
 
465
}
 
466
// the same for toptitles
 
467
static int y2scr_top(int y) {
 
468
        if (global_settings->use_margins)
 
469
                return y * frame_context.orig_height / frame_context.track->PlayResY;
 
470
        else
 
471
                return y * frame_context.orig_height / frame_context.track->PlayResY + global_settings->top_margin;
 
472
}
 
473
// the same for subtitles
 
474
static int y2scr_sub(int y) {
 
475
        if (global_settings->use_margins)
 
476
                return y * frame_context.orig_height / frame_context.track->PlayResY +
 
477
                       global_settings->top_margin + global_settings->bottom_margin;
 
478
        else
 
479
                return y * frame_context.orig_height / frame_context.track->PlayResY + global_settings->top_margin;
 
480
}
 
481
 
 
482
static void compute_string_bbox( text_info_t* info, FT_BBox *abbox ) {
 
483
        FT_BBox bbox;
 
484
        int i;
 
485
        
 
486
        if (text_info.length > 0) {
 
487
                bbox.xMin = 32000;
 
488
                bbox.xMax = -32000;
 
489
                bbox.yMin = - d6_to_int(text_info.lines[0].asc) + text_info.glyphs[0].pos.y;
 
490
                bbox.yMax = d6_to_int(text_info.height - text_info.lines[0].asc) + text_info.glyphs[0].pos.y;
 
491
 
 
492
                for (i = 0; i < text_info.length; ++i) {
 
493
                        int s = text_info.glyphs[i].pos.x;
 
494
                        int e = s + d6_to_int(text_info.glyphs[i].advance.x);
 
495
                        bbox.xMin = FFMIN(bbox.xMin, s);
 
496
                        bbox.xMax = FFMAX(bbox.xMax, e);
 
497
                }
 
498
        } else
 
499
                bbox.xMin = bbox.xMax = bbox.yMin = bbox.yMax = 0;
 
500
 
 
501
        /* return string bbox */
 
502
        *abbox = bbox;
 
503
}
 
504
 
 
505
 
 
506
/**
 
507
 * \brief Check if starting part of (*p) matches sample. If true, shift p to the first symbol after the matching part.
 
508
 */
 
509
static inline int mystrcmp(char** p, const char* sample) {
 
510
        int len = strlen(sample);
 
511
        if (strncmp(*p, sample, len) == 0) {
 
512
                (*p) += len;
 
513
                return 1;
 
514
        } else
 
515
                return 0;
 
516
}
 
517
 
 
518
static void change_font_size(double sz)
 
519
{
 
520
        double size = sz * frame_context.font_scale;
 
521
 
 
522
        if (size < 1)
 
523
                size = 1;
 
524
        else if (size > frame_context.height * 2)
 
525
                size = frame_context.height * 2;
 
526
 
 
527
        ass_font_set_size(render_context.font, size);
 
528
 
 
529
        render_context.font_size = sz;
 
530
}
 
531
 
 
532
/**
 
533
 * \brief Change current font, using setting from render_context.
 
534
 */
 
535
static void update_font(void)
 
536
{
 
537
        unsigned val;
 
538
        ass_renderer_t* priv = frame_context.ass_priv;
 
539
        ass_font_desc_t desc;
 
540
        desc.family = strdup(render_context.family);
 
541
 
 
542
        val = render_context.bold;
 
543
        // 0 = normal, 1 = bold, >1 = exact weight
 
544
        if (val == 0) val = 80; // normal
 
545
        else if (val == 1) val = 200; // bold
 
546
        desc.bold = val;
 
547
 
 
548
        val = render_context.italic;
 
549
        if (val == 0) val = 0; // normal
 
550
        else if (val == 1) val = 110; //italic
 
551
        desc.italic = val;
 
552
 
 
553
        render_context.font = ass_font_new(priv->library, priv->ftlibrary, priv->fontconfig_priv, &desc);
 
554
        free(desc.family);
 
555
        
 
556
        if (render_context.font)
 
557
                change_font_size(render_context.font_size);
 
558
}
 
559
 
 
560
/**
 
561
 * \brief Change border width
 
562
 * negative value resets border to style value
 
563
 */
 
564
static void change_border(double border)
 
565
{
 
566
        int b;
 
567
        if (!render_context.font) return;
 
568
 
 
569
        if (border < 0) {
 
570
                if (render_context.style->BorderStyle == 1) {
 
571
                        if (render_context.style->Outline == 0 && render_context.style->Shadow > 0)
 
572
                                border = 1.;
 
573
                        else
 
574
                                border = render_context.style->Outline;
 
575
                } else
 
576
                        border = 1.;
 
577
        }
 
578
        render_context.border = border;
 
579
 
 
580
        b = 64 * border * frame_context.border_scale;
 
581
        if (b > 0) {
 
582
                if (!render_context.stroker) {
 
583
                        int error;
 
584
#if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1))
 
585
                        error = FT_Stroker_New( ass_renderer->ftlibrary, &render_context.stroker );
 
586
#else // < 2.2
 
587
                        error = FT_Stroker_New( render_context.font->faces[0]->memory, &render_context.stroker );
 
588
#endif
 
589
                        if (error) {
 
590
                                mp_msg(MSGT_ASS, MSGL_V, "failed to get stroker\n");
 
591
                                render_context.stroker = 0;
 
592
                        }
 
593
                }
 
594
                if (render_context.stroker)
 
595
                        FT_Stroker_Set( render_context.stroker, b,
 
596
                                        FT_STROKER_LINECAP_ROUND,
 
597
                                        FT_STROKER_LINEJOIN_ROUND,
 
598
                                        0 );
 
599
        } else {
 
600
                FT_Stroker_Done(render_context.stroker);
 
601
                render_context.stroker = 0;
 
602
        }
 
603
}
 
604
 
 
605
#define _r(c)  ((c)>>24)
 
606
#define _g(c)  (((c)>>16)&0xFF)
 
607
#define _b(c)  (((c)>>8)&0xFF)
 
608
#define _a(c)  ((c)&0xFF)
 
609
 
 
610
/**
 
611
 * \brief Calculate a weighted average of two colors
 
612
 * calculates c1*(1-a) + c2*a, but separately for each component except alpha
 
613
 */
 
614
static void change_color(uint32_t* var, uint32_t new, double pwr)
 
615
{
 
616
        (*var)= ((uint32_t)(_r(*var) * (1 - pwr) + _r(new) * pwr) << 24) +
 
617
                ((uint32_t)(_g(*var) * (1 - pwr) + _g(new) * pwr) << 16) +
 
618
                ((uint32_t)(_b(*var) * (1 - pwr) + _b(new) * pwr) << 8) +
 
619
                _a(*var);
 
620
}
 
621
 
 
622
// like change_color, but for alpha component only
 
623
static void change_alpha(uint32_t* var, uint32_t new, double pwr)
 
624
{
 
625
        *var = (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) + (_a(*var) * (1 - pwr) + _a(new) * pwr);
 
626
}
 
627
 
 
628
/**
 
629
 * \brief Multiply two alpha values
 
630
 * \param a first value
 
631
 * \param b second value
 
632
 * \return result of multiplication
 
633
 * Parameters and result are limited by 0xFF.
 
634
 */
 
635
static uint32_t mult_alpha(uint32_t a, uint32_t b)
 
636
{
 
637
        return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF;
 
638
}
 
639
 
 
640
/**
 
641
 * \brief Calculate alpha value by piecewise linear function
 
642
 * Used for \fad, \fade implementation.
 
643
 */
 
644
static unsigned interpolate_alpha(long long now, 
 
645
                long long t1, long long t2, long long t3, long long t4,
 
646
                unsigned a1, unsigned a2, unsigned a3)
 
647
{
 
648
        unsigned a;
 
649
        double cf;
 
650
        if (now <= t1) {
 
651
                a = a1;
 
652
        } else if (now >= t4) {
 
653
                a = a3;
 
654
        } else if (now < t2) { // and > t1
 
655
                cf = ((double)(now - t1)) / (t2 - t1);
 
656
                a = a1 * (1 - cf) + a2 * cf;
 
657
        } else if (now > t3) {
 
658
                cf = ((double)(now - t3)) / (t4 - t3);
 
659
                a = a2 * (1 - cf) + a3 * cf;
 
660
        } else { // t2 <= now <= t3
 
661
                a = a2;
 
662
        }
 
663
 
 
664
        return a;
 
665
}
 
666
 
 
667
static void reset_render_context(void);
 
668
 
 
669
/**
 
670
 * \brief Parse style override tag.
 
671
 * \param p string to parse
 
672
 * \param pwr multiplier for some tag effects (comes from \t tags)
 
673
 */
 
674
static char* parse_tag(char* p, double pwr) {
 
675
#define skip_all(x) if (*p == (x)) ++p; else { \
 
676
        while ((*p != (x)) && (*p != '}') && (*p != 0)) {++p;} }
 
677
#define skip(x) if (*p == (x)) ++p; else { return p; }
 
678
        
 
679
        skip_all('\\');
 
680
        if ((*p == '}') || (*p == 0))
 
681
                return p;
 
682
 
 
683
        if (mystrcmp(&p, "fsc")) {
 
684
                char tp = *p++;
 
685
                double val;
 
686
                if (tp == 'x') {
 
687
                        if (mystrtod(&p, &val)) {
 
688
                                val /= 100;
 
689
                                render_context.scale_x = render_context.scale_x * ( 1 - pwr) + val * pwr;
 
690
                        } else
 
691
                                render_context.scale_x = render_context.style->ScaleX;
 
692
                } else if (tp == 'y') {
 
693
                        if (mystrtod(&p, &val)) {
 
694
                                val /= 100;
 
695
                                render_context.scale_y = render_context.scale_y * ( 1 - pwr) + val * pwr;
 
696
                        } else
 
697
                                render_context.scale_y = render_context.style->ScaleY;
 
698
                }
 
699
        } else if (mystrcmp(&p, "fsp")) {
 
700
                double val;
 
701
                if (mystrtod(&p, &val))
 
702
                        render_context.hspacing = render_context.hspacing * ( 1 - pwr ) + val * pwr;
 
703
                else
 
704
                        render_context.hspacing = render_context.style->Spacing;
 
705
        } else if (mystrcmp(&p, "fs")) {
 
706
                double val;
 
707
                if (mystrtod(&p, &val))
 
708
                        val = render_context.font_size * ( 1 - pwr ) + val * pwr;
 
709
                else
 
710
                        val = render_context.style->FontSize;
 
711
                if (render_context.font)
 
712
                        change_font_size(val);
 
713
        } else if (mystrcmp(&p, "bord")) {
 
714
                double val;
 
715
                if (mystrtod(&p, &val))
 
716
                        val = render_context.border * ( 1 - pwr ) + val * pwr;
 
717
                else
 
718
                        val = -1.; // reset to default
 
719
                change_border(val);
 
720
        } else if (mystrcmp(&p, "move")) {
 
721
                int x1, x2, y1, y2;
 
722
                long long t1, t2, delta_t, t;
 
723
                int x, y;
 
724
                double k;
 
725
                skip('(');
 
726
                x1 = strtol(p, &p, 10);
 
727
                skip(',');
 
728
                y1 = strtol(p, &p, 10);
 
729
                skip(',');
 
730
                x2 = strtol(p, &p, 10);
 
731
                skip(',');
 
732
                y2 = strtol(p, &p, 10);
 
733
                if (*p == ',') {
 
734
                        skip(',');
 
735
                        t1 = strtoll(p, &p, 10);
 
736
                        skip(',');
 
737
                        t2 = strtoll(p, &p, 10);
 
738
                        mp_msg(MSGT_ASS, MSGL_DBG2, "movement6: (%d, %d) -> (%d, %d), (%" PRId64 " .. %" PRId64 ")\n", 
 
739
                                x1, y1, x2, y2, (int64_t)t1, (int64_t)t2);
 
740
                } else {
 
741
                        t1 = 0;
 
742
                        t2 = render_context.event->Duration;
 
743
                        mp_msg(MSGT_ASS, MSGL_DBG2, "movement: (%d, %d) -> (%d, %d)\n", x1, y1, x2, y2);
 
744
                }
 
745
                skip(')');
 
746
                delta_t = t2 - t1;
 
747
                t = frame_context.time - render_context.event->Start;
 
748
                if (t < t1)
 
749
                        k = 0.;
 
750
                else if (t > t2)
 
751
                        k = 1.;
 
752
                else k = ((double)(t - t1)) / delta_t;
 
753
                x = k * (x2 - x1) + x1;
 
754
                y = k * (y2 - y1) + y1;
 
755
                render_context.pos_x = x;
 
756
                render_context.pos_y = y;
 
757
                render_context.detect_collisions = 0;
 
758
                render_context.evt_type = EVENT_POSITIONED;
 
759
        } else if (mystrcmp(&p, "frx")) {
 
760
                double val;
 
761
                if (mystrtod(&p, &val)) {
 
762
                        val *= M_PI / 180;
 
763
                        render_context.frx = val * pwr + render_context.frx * (1-pwr);
 
764
                } else
 
765
                        render_context.frx = 0.;
 
766
        } else if (mystrcmp(&p, "fry")) {
 
767
                double val;
 
768
                if (mystrtod(&p, &val)) {
 
769
                        val *= M_PI / 180;
 
770
                        render_context.fry = val * pwr + render_context.fry * (1-pwr);
 
771
                } else
 
772
                        render_context.fry = 0.;
 
773
        } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) {
 
774
                double val;
 
775
                if (mystrtod(&p, &val)) {
 
776
                        val *= M_PI / 180;
 
777
                        render_context.frz = val * pwr + render_context.frz * (1-pwr);
 
778
                } else
 
779
                        render_context.frz = M_PI * render_context.style->Angle / 180.;
 
780
        } else if (mystrcmp(&p, "fn")) {
 
781
                char* start = p;
 
782
                char* family;
 
783
                skip_all('\\');
 
784
                if (p > start) {
 
785
                        family = malloc(p - start + 1);
 
786
                        strncpy(family, start, p - start);
 
787
                        family[p - start] = '\0';
 
788
                } else
 
789
                        family = strdup(render_context.style->FontName);
 
790
                if (render_context.family)
 
791
                        free(render_context.family);
 
792
                render_context.family = family;
 
793
                update_font();
 
794
        } else if (mystrcmp(&p, "alpha")) {
 
795
                uint32_t val;
 
796
                int i;
 
797
                if (strtocolor(&p, &val)) {
 
798
                        unsigned char a = val >> 24;
 
799
                        for (i = 0; i < 4; ++i)
 
800
                                change_alpha(&render_context.c[i], a, pwr);
 
801
                } else {
 
802
                        change_alpha(&render_context.c[0], render_context.style->PrimaryColour, pwr);
 
803
                        change_alpha(&render_context.c[1], render_context.style->SecondaryColour, pwr);
 
804
                        change_alpha(&render_context.c[2], render_context.style->OutlineColour, pwr);
 
805
                        change_alpha(&render_context.c[3], render_context.style->BackColour, pwr);
 
806
                }
 
807
                // FIXME: simplify
 
808
        } else if (mystrcmp(&p, "an")) {
 
809
                int val;
 
810
                if (mystrtoi(&p, 10, &val) && val) {
 
811
                        int v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
 
812
                        mp_msg(MSGT_ASS, MSGL_DBG2, "an %d\n", val);
 
813
                        if (v != 0) v = 3 - v;
 
814
                        val = ((val - 1) % 3) + 1; // horizontal alignment
 
815
                        val += v*4;
 
816
                        mp_msg(MSGT_ASS, MSGL_DBG2, "align %d\n", val);
 
817
                        render_context.alignment = val;
 
818
                } else
 
819
                        render_context.alignment = render_context.style->Alignment;
 
820
        } else if (mystrcmp(&p, "a")) {
 
821
                int val;
 
822
                if (mystrtoi(&p, 10, &val) && val)
 
823
                        render_context.alignment = val;
 
824
                else
 
825
                        render_context.alignment = render_context.style->Alignment;
 
826
        } else if (mystrcmp(&p, "pos")) {
 
827
                int v1, v2;
 
828
                skip('(');
 
829
                v1 = strtol(p, &p, 10);
 
830
                skip(',');
 
831
                v2 = strtol(p, &p, 10);
 
832
                skip(')');
 
833
                mp_msg(MSGT_ASS, MSGL_DBG2, "pos(%d, %d)\n", v1, v2);
 
834
                render_context.evt_type = EVENT_POSITIONED;
 
835
                render_context.detect_collisions = 0;
 
836
                render_context.pos_x = v1;
 
837
                render_context.pos_y = v2;
 
838
        } else if (mystrcmp(&p, "fad")) {
 
839
                int a1, a2, a3;
 
840
                long long t1, t2, t3, t4;
 
841
                if (*p == 'e') ++p; // either \fad or \fade
 
842
                skip('(');
 
843
                a1 = strtol(p, &p, 10);
 
844
                skip(',');
 
845
                a2 = strtol(p, &p, 10);
 
846
                if (*p == ')') {
 
847
                        // 2-argument version (\fad, according to specs)
 
848
                        // a1 and a2 are fade-in and fade-out durations
 
849
                        t1 = 0;
 
850
                        t4 = render_context.event->Duration;
 
851
                        t2 = a1;
 
852
                        t3 = t4 - a2;
 
853
                        a1 = 0xFF;
 
854
                        a2 = 0;
 
855
                        a3 = 0xFF;
 
856
                } else {
 
857
                        // 6-argument version (\fade)
 
858
                        // a1 and a2 (and a3) are opacity values
 
859
                        skip(',');
 
860
                        a3 = strtol(p, &p, 10);
 
861
                        skip(',');
 
862
                        t1 = strtoll(p, &p, 10);
 
863
                        skip(',');
 
864
                        t2 = strtoll(p, &p, 10);
 
865
                        skip(',');
 
866
                        t3 = strtoll(p, &p, 10);
 
867
                        skip(',');
 
868
                        t4 = strtoll(p, &p, 10);
 
869
                }
 
870
                skip(')');
 
871
                render_context.fade = interpolate_alpha(frame_context.time - render_context.event->Start, t1, t2, t3, t4, a1, a2, a3);
 
872
        } else if (mystrcmp(&p, "org")) {
 
873
                int v1, v2;
 
874
                skip('(');
 
875
                v1 = strtol(p, &p, 10);
 
876
                skip(',');
 
877
                v2 = strtol(p, &p, 10);
 
878
                skip(')');
 
879
                mp_msg(MSGT_ASS, MSGL_DBG2, "org(%d, %d)\n", v1, v2);
 
880
                //                              render_context.evt_type = EVENT_POSITIONED;
 
881
                render_context.org_x = v1;
 
882
                render_context.org_y = v2;
 
883
                render_context.have_origin = 1;
 
884
        } else if (mystrcmp(&p, "t")) {
 
885
                double v[3];
 
886
                int v1, v2;
 
887
                double v3;
 
888
                int cnt;
 
889
                long long t1, t2, t, delta_t;
 
890
                double k;
 
891
                skip('(');
 
892
                for (cnt = 0; cnt < 3; ++cnt) {
 
893
                        if (*p == '\\')
 
894
                                break;
 
895
                        v[cnt] = strtod(p, &p);
 
896
                        skip(',');
 
897
                }
 
898
                if (cnt == 3) {
 
899
                        v1 = v[0]; v2 = v[1]; v3 = v[2];
 
900
                } else if (cnt == 2) {
 
901
                        v1 = v[0]; v2 = v[1]; v3 = 1.;
 
902
                } else if (cnt == 1) {
 
903
                        v1 = 0; v2 = render_context.event->Duration; v3 = v[0];
 
904
                } else { // cnt == 0
 
905
                        v1 = 0; v2 = render_context.event->Duration; v3 = 1.;
 
906
                }
 
907
                render_context.detect_collisions = 0;
 
908
                t1 = v1;
 
909
                t2 = v2;
 
910
                delta_t = v2 - v1;
 
911
                if (v3 < 0.)
 
912
                        v3 = 0.;
 
913
                t = frame_context.time - render_context.event->Start; // FIXME: move to render_context
 
914
                if (t <= t1)
 
915
                        k = 0.;
 
916
                else if (t >= t2)
 
917
                        k = 1.;
 
918
                else {
 
919
                        assert(delta_t != 0.);
 
920
                        k = pow(((double)(t - t1)) / delta_t, v3);
 
921
                }
 
922
                while (*p == '\\')
 
923
                        p = parse_tag(p, k); // maybe k*pwr ? no, specs forbid nested \t's 
 
924
                skip_all(')'); // FIXME: better skip(')'), but much more tags support required
 
925
        } else if (mystrcmp(&p, "clip")) {
 
926
                int x0, y0, x1, y1;
 
927
                int res = 1;
 
928
                skip('(');
 
929
                res &= mystrtoi(&p, 10, &x0);
 
930
                skip(',');
 
931
                res &= mystrtoi(&p, 10, &y0);
 
932
                skip(',');
 
933
                res &= mystrtoi(&p, 10, &x1);
 
934
                skip(',');
 
935
                res &= mystrtoi(&p, 10, &y1);
 
936
                skip(')');
 
937
                if (res) {
 
938
                        render_context.clip_x0 = render_context.clip_x0 * (1-pwr) + x0 * pwr;
 
939
                        render_context.clip_x1 = render_context.clip_x1 * (1-pwr) + x1 * pwr;
 
940
                        render_context.clip_y0 = render_context.clip_y0 * (1-pwr) + y0 * pwr;
 
941
                        render_context.clip_y1 = render_context.clip_y1 * (1-pwr) + y1 * pwr;
 
942
                } else {
 
943
                        render_context.clip_x0 = 0;
 
944
                        render_context.clip_y0 = 0;
 
945
                        render_context.clip_x1 = frame_context.track->PlayResX;
 
946
                        render_context.clip_y1 = frame_context.track->PlayResY;
 
947
                }
 
948
        } else if (mystrcmp(&p, "c")) {
 
949
                uint32_t val;
 
950
                if (!strtocolor(&p, &val))
 
951
                        val = render_context.style->PrimaryColour;
 
952
                mp_msg(MSGT_ASS, MSGL_DBG2, "color: %X\n", val);
 
953
                change_color(&render_context.c[0], val, pwr);
 
954
        } else if ((*p >= '1') && (*p <= '4') && (++p) && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) {
 
955
                char n = *(p-2);
 
956
                int cidx = n - '1';
 
957
                char cmd = *(p-1);
 
958
                uint32_t val;
 
959
                assert((n >= '1') && (n <= '4'));
 
960
                if (!strtocolor(&p, &val))
 
961
                        switch(n) {
 
962
                                case '1': val = render_context.style->PrimaryColour; break;
 
963
                                case '2': val = render_context.style->SecondaryColour; break;
 
964
                                case '3': val = render_context.style->OutlineColour; break;
 
965
                                case '4': val = render_context.style->BackColour; break;
 
966
                                default : val = 0; break; // impossible due to assert; avoid compilation warning
 
967
                        }
 
968
                switch (cmd) {
 
969
                        case 'c': change_color(render_context.c + cidx, val, pwr); break;
 
970
                        case 'a': change_alpha(render_context.c + cidx, val >> 24, pwr); break;
 
971
                        default: mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadCommand, n, cmd); break;
 
972
                }
 
973
                mp_msg(MSGT_ASS, MSGL_DBG2, "single c/a at %f: %c%c = %X   \n", pwr, n, cmd, render_context.c[cidx]);
 
974
        } else if (mystrcmp(&p, "r")) {
 
975
                reset_render_context();
 
976
        } else if (mystrcmp(&p, "be")) {
 
977
                int val;
 
978
                if (mystrtoi(&p, 10, &val))
 
979
                        render_context.be = val ? 1 : 0;
 
980
                else
 
981
                        render_context.be = 0;
 
982
        } else if (mystrcmp(&p, "b")) {
 
983
                int b;
 
984
                if (mystrtoi(&p, 10, &b)) {
 
985
                        if (pwr >= .5)
 
986
                                render_context.bold = b;
 
987
                } else
 
988
                        render_context.bold = render_context.style->Bold;
 
989
                update_font();
 
990
        } else if (mystrcmp(&p, "i")) {
 
991
                int i;
 
992
                if (mystrtoi(&p, 10, &i)) {
 
993
                        if (pwr >= .5)
 
994
                                render_context.italic = i;
 
995
                } else
 
996
                        render_context.italic = render_context.style->Italic;
 
997
                update_font();
 
998
        } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) {
 
999
                int val = strtol(p, &p, 10);
 
1000
                render_context.effect_type = EF_KARAOKE_KF;
 
1001
                if (render_context.effect_timing)
 
1002
                        render_context.effect_skip_timing += render_context.effect_timing;
 
1003
                render_context.effect_timing = val * 10;
 
1004
        } else if (mystrcmp(&p, "ko")) {
 
1005
                int val = strtol(p, &p, 10);
 
1006
                render_context.effect_type = EF_KARAOKE_KO;
 
1007
                if (render_context.effect_timing)
 
1008
                        render_context.effect_skip_timing += render_context.effect_timing;
 
1009
                render_context.effect_timing = val * 10;
 
1010
        } else if (mystrcmp(&p, "k")) {
 
1011
                int val = strtol(p, &p, 10);
 
1012
                render_context.effect_type = EF_KARAOKE;
 
1013
                if (render_context.effect_timing)
 
1014
                        render_context.effect_skip_timing += render_context.effect_timing;
 
1015
                render_context.effect_timing = val * 10;
 
1016
        } else if (mystrcmp(&p, "shad")) {
 
1017
                int val;
 
1018
                if (mystrtoi(&p, 10, &val))
 
1019
                        render_context.shadow = val;
 
1020
                else
 
1021
                        render_context.shadow = render_context.style->Shadow;
 
1022
        }
 
1023
 
 
1024
        return p;
 
1025
 
 
1026
#undef skip
 
1027
#undef skip_all
 
1028
}
 
1029
 
 
1030
/**
 
1031
 * \brief Get next ucs4 char from string, parsing and executing style overrides
 
1032
 * \param str string pointer
 
1033
 * \return ucs4 code of the next char
 
1034
 * On return str points to the unparsed part of the string
 
1035
 */
 
1036
static unsigned get_next_char(char** str)
 
1037
{
 
1038
        char* p = *str;
 
1039
        unsigned chr;
 
1040
        if (*p == '{') { // '\0' goes here
 
1041
                p++;
 
1042
                while (1) {
 
1043
                        p = parse_tag(p, 1.);
 
1044
                        if (*p == '}') { // end of tag
 
1045
                                p++;
 
1046
                                if (*p == '{') {
 
1047
                                        p++;
 
1048
                                        continue;
 
1049
                                } else
 
1050
                                        break;
 
1051
                        } else if (*p != '\\')
 
1052
                                mp_msg(MSGT_ASS, MSGL_V, "Unable to parse: \"%s\" \n", p);
 
1053
                        if (*p == 0)
 
1054
                                break;
 
1055
                }
 
1056
        }
 
1057
        if (*p == '\t') {
 
1058
                ++p;
 
1059
                *str = p;
 
1060
                return ' ';
 
1061
        }
 
1062
        if (*p == '\\') {
 
1063
                if ((*(p+1) == 'N') || ((*(p+1) == 'n') && (frame_context.track->WrapStyle == 2))) {
 
1064
                        p += 2;
 
1065
                        *str = p;
 
1066
                        return '\n';
 
1067
                } else if (*(p+1) == 'n') {
 
1068
                        p += 2;
 
1069
                        *str = p;
 
1070
                        return ' ';
 
1071
                }
 
1072
        }
 
1073
        chr = utf8_get_char(&p);
 
1074
        *str = p;
 
1075
        return chr;
 
1076
}
 
1077
 
 
1078
static void apply_transition_effects(ass_event_t* event)
 
1079
{
 
1080
        int v[4];
 
1081
        int cnt;
 
1082
        char* p = event->Effect;
 
1083
 
 
1084
        if (!p || !*p) return;
 
1085
 
 
1086
        cnt = 0;
 
1087
        while (cnt < 4 && (p = strchr(p, ';'))) {
 
1088
                v[cnt++] = atoi(++p);
 
1089
        }
 
1090
        
 
1091
        if (strncmp(event->Effect, "Banner;", 7) == 0) {
 
1092
                int delay;
 
1093
                if (cnt < 1) {
 
1094
                        mp_msg(MSGT_ASS, MSGL_V, "Error parsing effect: %s \n", event->Effect);
 
1095
                        return;
 
1096
                }
 
1097
                if (cnt >= 2 && v[1] == 0) // right-to-left
 
1098
                        render_context.scroll_direction = SCROLL_RL;
 
1099
                else // left-to-right
 
1100
                        render_context.scroll_direction = SCROLL_LR;
 
1101
 
 
1102
                delay = v[0];
 
1103
                if (delay == 0) delay = 1; // ?
 
1104
                render_context.scroll_shift = (frame_context.time - render_context.event->Start) / delay;
 
1105
                render_context.evt_type = EVENT_HSCROLL;
 
1106
                return;
 
1107
        }
 
1108
 
 
1109
        if (strncmp(event->Effect, "Scroll up;", 10) == 0) {
 
1110
                render_context.scroll_direction = SCROLL_BT;
 
1111
        } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) {
 
1112
                render_context.scroll_direction = SCROLL_TB;
 
1113
        } else {
 
1114
                mp_msg(MSGT_ASS, MSGL_V, "Unknown transition effect: %s \n", event->Effect);
 
1115
                return;
 
1116
        }
 
1117
        // parse scroll up/down parameters
 
1118
        {
 
1119
                int delay;
 
1120
                int y0, y1;
 
1121
                if (cnt < 3) {
 
1122
                        mp_msg(MSGT_ASS, MSGL_V, "Error parsing effect: %s \n", event->Effect);
 
1123
                        return;
 
1124
                }
 
1125
                delay = v[2];
 
1126
                if (delay == 0) delay = 1; // ?
 
1127
                render_context.scroll_shift = (frame_context.time - render_context.event->Start) / delay;
 
1128
                if (v[0] < v[1]) {
 
1129
                        y0 = v[0]; y1 = v[1];
 
1130
                } else {
 
1131
                        y0 = v[1]; y1 = v[0];
 
1132
                }
 
1133
                if (y1 == 0)
 
1134
                        y1 = frame_context.track->PlayResY; // y0=y1=0 means fullscreen scrolling
 
1135
                render_context.clip_y0 = y0;
 
1136
                render_context.clip_y1 = y1;
 
1137
                render_context.evt_type = EVENT_VSCROLL;
 
1138
                render_context.detect_collisions = 0;
 
1139
        }
 
1140
 
 
1141
}
 
1142
 
 
1143
/**
 
1144
 * \brief partially reset render_context to style values
 
1145
 * Works like {\r}: resets some style overrides
 
1146
 */
 
1147
static void reset_render_context(void)
 
1148
{
 
1149
        render_context.c[0] = render_context.style->PrimaryColour;
 
1150
        render_context.c[1] = render_context.style->SecondaryColour;
 
1151
        render_context.c[2] = render_context.style->OutlineColour;
 
1152
        render_context.c[3] = render_context.style->BackColour;
 
1153
        render_context.font_size = render_context.style->FontSize;
 
1154
 
 
1155
        if (render_context.family)
 
1156
                free(render_context.family);
 
1157
        render_context.family = strdup(render_context.style->FontName);
 
1158
        render_context.bold = render_context.style->Bold;
 
1159
        render_context.italic = render_context.style->Italic;
 
1160
        update_font();
 
1161
 
 
1162
        change_border(-1.);
 
1163
        render_context.scale_x = render_context.style->ScaleX;
 
1164
        render_context.scale_y = render_context.style->ScaleY;
 
1165
        render_context.hspacing = render_context.style->Spacing;
 
1166
        render_context.be = 0;
 
1167
        render_context.shadow = render_context.style->Shadow;
 
1168
        render_context.frx = render_context.fry = 0.;
 
1169
        render_context.frz = M_PI * render_context.style->Angle / 180.;
 
1170
 
 
1171
        // FIXME: does not reset unsupported attributes.
 
1172
}
 
1173
 
 
1174
/**
 
1175
 * \brief Start new event. Reset render_context.
 
1176
 */
 
1177
static void init_render_context(ass_event_t* event)
 
1178
{
 
1179
        render_context.event = event;
 
1180
        render_context.style = frame_context.track->styles + event->Style;
 
1181
 
 
1182
        reset_render_context();
 
1183
 
 
1184
        render_context.evt_type = EVENT_NORMAL;
 
1185
        render_context.alignment = render_context.style->Alignment;
 
1186
        render_context.pos_x = 0;
 
1187
        render_context.pos_y = 0;
 
1188
        render_context.org_x = 0;
 
1189
        render_context.org_y = 0;
 
1190
        render_context.have_origin = 0;
 
1191
        render_context.clip_x0 = 0;
 
1192
        render_context.clip_y0 = 0;
 
1193
        render_context.clip_x1 = frame_context.track->PlayResX;
 
1194
        render_context.clip_y1 = frame_context.track->PlayResY;
 
1195
        render_context.detect_collisions = 1;
 
1196
        render_context.fade = 0;
 
1197
        render_context.effect_type = EF_NONE;
 
1198
        render_context.effect_timing = 0;
 
1199
        render_context.effect_skip_timing = 0;
 
1200
        
 
1201
        apply_transition_effects(event);
 
1202
}
 
1203
 
 
1204
static void free_render_context(void)
 
1205
{
 
1206
}
 
1207
 
 
1208
/**
 
1209
 * \brief Get normal and outline (border) glyphs
 
1210
 * \param symbol ucs4 char
 
1211
 * \param info out: struct filled with extracted data
 
1212
 * \param advance subpixel shift vector used for cache lookup
 
1213
 * Tries to get both glyphs from cache.
 
1214
 * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker,
 
1215
 * and add them to cache.
 
1216
 * The glyphs are returned in info->glyph and info->outline_glyph
 
1217
 */
 
1218
static void get_outline_glyph(int symbol, glyph_info_t* info, FT_Vector* advance)
 
1219
{
 
1220
        int error;
 
1221
        glyph_hash_val_t* val;
 
1222
        glyph_hash_key_t key;
 
1223
        key.font = render_context.font;
 
1224
        key.size = render_context.font_size;
 
1225
        key.ch = symbol;
 
1226
        key.scale_x = (render_context.scale_x * 0xFFFF);
 
1227
        key.scale_y = (render_context.scale_y * 0xFFFF);
 
1228
        key.advance = *advance;
 
1229
        key.bold = render_context.bold;
 
1230
        key.italic = render_context.italic;
 
1231
        key.outline = render_context.border * 0xFFFF;
 
1232
 
 
1233
        info->glyph = info->outline_glyph = 0;
 
1234
 
 
1235
        val = cache_find_glyph(&key);
 
1236
        if (val) {
 
1237
                FT_Glyph_Copy(val->glyph, &info->glyph);
 
1238
                if (val->outline_glyph)
 
1239
                        FT_Glyph_Copy(val->outline_glyph, &info->outline_glyph);
 
1240
                info->bbox = val->bbox_scaled;
 
1241
                info->advance.x = val->advance.x;
 
1242
                info->advance.y = val->advance.y;
 
1243
        } else {
 
1244
                glyph_hash_val_t v;
 
1245
                info->glyph = ass_font_get_glyph(frame_context.ass_priv->fontconfig_priv, render_context.font, symbol, global_settings->hinting);
 
1246
                if (!info->glyph)
 
1247
                        return;
 
1248
                info->advance.x = d16_to_d6(info->glyph->advance.x);
 
1249
                info->advance.y = d16_to_d6(info->glyph->advance.y);
 
1250
                FT_Glyph_Get_CBox( info->glyph, FT_GLYPH_BBOX_PIXELS, &info->bbox);
 
1251
 
 
1252
                if (render_context.stroker) {
 
1253
                        info->outline_glyph = info->glyph;
 
1254
                        error = FT_Glyph_StrokeBorder( &(info->outline_glyph), render_context.stroker, 0 , 0 ); // don't destroy original
 
1255
                        if (error) {
 
1256
                                mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FT_Glyph_Stroke_Error, error);
 
1257
                        }
 
1258
                }
 
1259
 
 
1260
                memset(&v, 0, sizeof(v));
 
1261
                FT_Glyph_Copy(info->glyph, &v.glyph);
 
1262
                if (info->outline_glyph)
 
1263
                        FT_Glyph_Copy(info->outline_glyph, &v.outline_glyph);
 
1264
                v.advance = info->advance;
 
1265
                v.bbox_scaled = info->bbox;
 
1266
                cache_add_glyph(&key, &v);
 
1267
        }
 
1268
}
 
1269
 
 
1270
static void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz);
 
1271
 
 
1272
/**
 
1273
 * \brief Get bitmaps for a glyph
 
1274
 * \param info glyph info
 
1275
 * Tries to get glyph bitmaps from bitmap cache.
 
1276
 * If they can't be found, they are generated by rotating and rendering the glyph.
 
1277
 * After that, bitmaps are added to the cache.
 
1278
 * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow).
 
1279
 */
 
1280
static void get_bitmap_glyph(glyph_info_t* info)
 
1281
{
 
1282
        bitmap_hash_val_t* val;
 
1283
        bitmap_hash_key_t* key = &info->hash_key;
 
1284
        
 
1285
        val = cache_find_bitmap(key);
 
1286
/*      val = 0; */
 
1287
        
 
1288
        if (val) {
 
1289
                info->bm = val->bm;
 
1290
                info->bm_o = val->bm_o;
 
1291
                info->bm_s = val->bm_s;
 
1292
        } else {
 
1293
                FT_Vector shift;
 
1294
                bitmap_hash_val_t hash_val;
 
1295
                int error;
 
1296
                info->bm = info->bm_o = info->bm_s = 0;
 
1297
                if (info->glyph && info->symbol != '\n' && info->symbol != 0) {
 
1298
                        // calculating rotation shift vector (from rotation origin to the glyph basepoint)
 
1299
                        shift.x = int_to_d6(info->hash_key.shift_x);
 
1300
                        shift.y = int_to_d6(info->hash_key.shift_y);
 
1301
                        // apply rotation
 
1302
                        transform_3d(shift, &info->glyph, &info->outline_glyph, info->frx, info->fry, info->frz);
 
1303
 
 
1304
                        // render glyph
 
1305
                        error = glyph_to_bitmap(ass_renderer->synth_priv,
 
1306
                                        info->glyph, info->outline_glyph,
 
1307
                                        &info->bm, &info->bm_o,
 
1308
                                        &info->bm_s, info->be);
 
1309
                        if (error)
 
1310
                                info->symbol = 0;
 
1311
 
 
1312
                        // add bitmaps to cache
 
1313
                        hash_val.bm_o = info->bm_o;
 
1314
                        hash_val.bm = info->bm;
 
1315
                        hash_val.bm_s = info->bm_s;
 
1316
                        cache_add_bitmap(&(info->hash_key), &hash_val);
 
1317
                }
 
1318
        }
 
1319
        // deallocate glyphs
 
1320
        if (info->glyph)
 
1321
                FT_Done_Glyph(info->glyph);
 
1322
        if (info->outline_glyph)
 
1323
                FT_Done_Glyph(info->outline_glyph);
 
1324
}
 
1325
 
 
1326
/**
 
1327
 * This function goes through text_info and calculates text parameters.
 
1328
 * The following text_info fields are filled:
 
1329
 *   n_lines
 
1330
 *   height
 
1331
 *   lines[].height
 
1332
 *   lines[].asc
 
1333
 *   lines[].desc
 
1334
 */
 
1335
static void measure_text(void)
 
1336
{
 
1337
        int cur_line = 0, max_asc = 0, max_desc = 0;
 
1338
        int i;
 
1339
        text_info.height = 0;
 
1340
        for (i = 0; i < text_info.length + 1; ++i) {
 
1341
                if ((i == text_info.length) || text_info.glyphs[i].linebreak) {
 
1342
                        text_info.lines[cur_line].asc = max_asc;
 
1343
                        text_info.lines[cur_line].desc = max_desc;
 
1344
                        text_info.height += max_asc + max_desc;
 
1345
                        cur_line ++;
 
1346
                        max_asc = max_desc = 0;
 
1347
                }
 
1348
                if (i < text_info.length) {
 
1349
                        glyph_info_t* cur = text_info.glyphs + i;
 
1350
                        if (cur->asc > max_asc)
 
1351
                                max_asc = cur->asc;
 
1352
                        if (cur->desc > max_desc)
 
1353
                                max_desc = cur->desc;
 
1354
                }
 
1355
        }
 
1356
}
 
1357
 
 
1358
/**
 
1359
 * \brief rearrange text between lines
 
1360
 * \param max_text_width maximal text line width in pixels
 
1361
 * The algo is similar to the one in libvo/sub.c:
 
1362
 * 1. Place text, wrapping it when current line is full
 
1363
 * 2. Try moving words from the end of a line to the beginning of the next one while it reduces
 
1364
 * the difference in lengths between this two lines.
 
1365
 * The result may not be optimal, but usually is good enough.
 
1366
 */
 
1367
static void wrap_lines_smart(int max_text_width)
 
1368
{
 
1369
        int i, j;
 
1370
        glyph_info_t *cur, *s1, *e1, *s2, *s3, *w;
 
1371
        int last_space;
 
1372
        int break_type;
 
1373
        int exit;
 
1374
        int pen_shift_x;
 
1375
        int pen_shift_y;
 
1376
        int cur_line;
 
1377
 
 
1378
        last_space = -1;
 
1379
        text_info.n_lines = 1;
 
1380
        break_type = 0;
 
1381
        s1 = text_info.glyphs; // current line start
 
1382
        for (i = 0; i < text_info.length; ++i) {
 
1383
                int break_at, s_offset, len;
 
1384
                cur = text_info.glyphs + i;
 
1385
                break_at = -1;
 
1386
                s_offset = s1->bbox.xMin + s1->pos.x;
 
1387
                len = (cur->bbox.xMax + cur->pos.x) - s_offset;
 
1388
 
 
1389
                if (cur->symbol == '\n') {
 
1390
                        break_type = 2;
 
1391
                        break_at = i;
 
1392
                        mp_msg(MSGT_ASS, MSGL_DBG2, "forced line break at %d\n", break_at);
 
1393
                }
 
1394
                
 
1395
                if (len >= max_text_width) {
 
1396
                        break_type = 1;
 
1397
                        break_at = last_space;
 
1398
                        if (break_at == -1)
 
1399
                                break_at = i - 1;
 
1400
                        if (break_at == -1)
 
1401
                                break_at = 0;
 
1402
                        mp_msg(MSGT_ASS, MSGL_DBG2, "overfill at %d\n", i);
 
1403
                        mp_msg(MSGT_ASS, MSGL_DBG2, "line break at %d\n", break_at);
 
1404
                }
 
1405
 
 
1406
                if (break_at != -1) {
 
1407
                        // need to use one more line
 
1408
                        // marking break_at+1 as start of a new line
 
1409
                        int lead = break_at + 1; // the first symbol of the new line
 
1410
                        if (text_info.n_lines >= MAX_LINES) {
 
1411
                                // to many lines ! 
 
1412
                                // no more linebreaks
 
1413
                                for (j = lead; j < text_info.length; ++j)
 
1414
                                        text_info.glyphs[j].linebreak = 0;
 
1415
                                break;
 
1416
                        }
 
1417
                        if (lead < text_info.length)
 
1418
                                text_info.glyphs[lead].linebreak = break_type;
 
1419
                        last_space = -1;
 
1420
                        s1 = text_info.glyphs + lead;
 
1421
                        s_offset = s1->bbox.xMin + s1->pos.x;
 
1422
                        text_info.n_lines ++;
 
1423
                }
 
1424
                
 
1425
                if (cur->symbol == ' ')
 
1426
                        last_space = i;
 
1427
 
 
1428
                // make sure the hard linebreak is not forgotten when
 
1429
                // there was a new soft linebreak just inserted
 
1430
                if (cur->symbol == '\n' && break_type == 1)
 
1431
                        i--;
 
1432
        }
 
1433
#define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
 
1434
        exit = 0;
 
1435
        while (!exit) {
 
1436
                exit = 1;
 
1437
                w = s3 = text_info.glyphs;
 
1438
                s1 = s2 = 0;
 
1439
                for (i = 0; i <= text_info.length; ++i) {
 
1440
                        cur = text_info.glyphs + i;
 
1441
                        if ((i == text_info.length) || cur->linebreak) {
 
1442
                                s1 = s2;
 
1443
                                s2 = s3;
 
1444
                                s3 = cur;
 
1445
                                if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft'
 
1446
                                        int l1, l2, l1_new, l2_new;
 
1447
 
 
1448
                                        w = s2;
 
1449
                                        do { --w; } while ((w > s1) && (w->symbol == ' '));
 
1450
                                        while ((w > s1) && (w->symbol != ' ')) { --w; }
 
1451
                                        e1 = w;
 
1452
                                        while ((e1 > s1) && (e1->symbol == ' ')) { --e1; }
 
1453
                                        if (w->symbol == ' ') ++w;
 
1454
 
 
1455
                                        l1 = ((s2-1)->bbox.xMax + (s2-1)->pos.x) - (s1->bbox.xMin + s1->pos.x);
 
1456
                                        l2 = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (s2->bbox.xMin + s2->pos.x);
 
1457
                                        l1_new = (e1->bbox.xMax + e1->pos.x) - (s1->bbox.xMin + s1->pos.x);
 
1458
                                        l2_new = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (w->bbox.xMin + w->pos.x);
 
1459
 
 
1460
                                        if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) {
 
1461
                                                w->linebreak = 1;
 
1462
                                                s2->linebreak = 0;
 
1463
                                                exit = 0;
 
1464
                                        }
 
1465
                                }
 
1466
                        }
 
1467
                        if (i == text_info.length)
 
1468
                                break;
 
1469
                }
 
1470
                
 
1471
        }
 
1472
        assert(text_info.n_lines >= 1);
 
1473
#undef DIFF
 
1474
        
 
1475
        measure_text();
 
1476
 
 
1477
        pen_shift_x = 0;
 
1478
        pen_shift_y = 0;
 
1479
        cur_line = 1;
 
1480
        for (i = 0; i < text_info.length; ++i) {
 
1481
                cur = text_info.glyphs + i;
 
1482
                if (cur->linebreak) {
 
1483
                        int height = text_info.lines[cur_line - 1].desc + text_info.lines[cur_line].asc;
 
1484
                        cur_line ++;
 
1485
                        pen_shift_x = - cur->pos.x;
 
1486
                        pen_shift_y += d6_to_int(height) + global_settings->line_spacing;
 
1487
                        mp_msg(MSGT_ASS, MSGL_DBG2, "shifting from %d to %d by (%d, %d)\n", i, text_info.length - 1, pen_shift_x, pen_shift_y);
 
1488
                }
 
1489
                cur->pos.x += pen_shift_x;
 
1490
                cur->pos.y += pen_shift_y;
 
1491
        }
 
1492
}
 
1493
 
 
1494
/**
 
1495
 * \brief determine karaoke effects
 
1496
 * Karaoke effects cannot be calculated during parse stage (get_next_char()),
 
1497
 * so they are done in a separate step.
 
1498
 * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's 
 
1499
 * (the first glyph of the karaoke word)'s effect_type and effect_timing.
 
1500
 * This function:
 
1501
 * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
 
1502
 * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts
 
1503
 * (left part is filled with PrimaryColour, right one - with SecondaryColour).
 
1504
 */
 
1505
static void process_karaoke_effects(void)
 
1506
{
 
1507
        glyph_info_t *cur, *cur2;
 
1508
        glyph_info_t *s1, *e1; // start and end of the current word
 
1509
        glyph_info_t *s2; // start of the next word
 
1510
        int i;
 
1511
        int timing; // current timing
 
1512
        int tm_start, tm_end; // timings at start and end of the current word
 
1513
        int tm_current;
 
1514
        double dt;
 
1515
        int x;
 
1516
        int x_start, x_end;
 
1517
 
 
1518
        tm_current = frame_context.time - render_context.event->Start;
 
1519
        timing = 0;
 
1520
        s1 = s2 = 0;
 
1521
        for (i = 0; i <= text_info.length; ++i) {
 
1522
                cur = text_info.glyphs + i;
 
1523
                if ((i == text_info.length) || (cur->effect_type != EF_NONE)) {
 
1524
                        s1 = s2;
 
1525
                        s2 = cur;
 
1526
                        if (s1) {
 
1527
                                e1 = s2 - 1;
 
1528
                                tm_start = timing + s1->effect_skip_timing;
 
1529
                                tm_end = tm_start + s1->effect_timing;
 
1530
                                timing = tm_end;
 
1531
                                x_start = 1000000;
 
1532
                                x_end = -1000000;
 
1533
                                for (cur2 = s1; cur2 <= e1; ++cur2) {
 
1534
                                        x_start = FFMIN(x_start, cur2->bbox.xMin + cur2->pos.x);
 
1535
                                        x_end = FFMAX(x_end, cur2->bbox.xMax + cur2->pos.x);
 
1536
                                }
 
1537
 
 
1538
                                dt = (tm_current - tm_start);
 
1539
                                if ((s1->effect_type == EF_KARAOKE) || (s1->effect_type == EF_KARAOKE_KO)) {
 
1540
                                        if (dt > 0)
 
1541
                                                x = x_end + 1;
 
1542
                                        else
 
1543
                                                x = x_start;
 
1544
                                } else if (s1->effect_type == EF_KARAOKE_KF) {
 
1545
                                        dt /= (tm_end - tm_start);
 
1546
                                        x = x_start + (x_end - x_start) * dt;
 
1547
                                } else {
 
1548
                                        mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_UnknownEffectType_InternalError);
 
1549
                                        continue;
 
1550
                                }
 
1551
 
 
1552
                                for (cur2 = s1; cur2 <= e1; ++cur2) {
 
1553
                                        cur2->effect_type = s1->effect_type;
 
1554
                                        cur2->effect_timing = x - cur2->pos.x;
 
1555
                                }
 
1556
                        }
 
1557
                }
 
1558
        }
 
1559
}
 
1560
 
 
1561
/**
 
1562
 * \brief Calculate base point for positioning and rotation
 
1563
 * \param bbox text bbox
 
1564
 * \param alignment alignment
 
1565
 * \param bx, by out: base point coordinates
 
1566
 */
 
1567
static void get_base_point(FT_BBox bbox, int alignment, int* bx, int* by)
 
1568
{
 
1569
        const int halign = alignment & 3;
 
1570
        const int valign = alignment & 12;
 
1571
        if (bx)
 
1572
                switch(halign) {
 
1573
                case HALIGN_LEFT:
 
1574
                        *bx = bbox.xMin;
 
1575
                        break;
 
1576
                case HALIGN_CENTER:
 
1577
                        *bx = (bbox.xMax + bbox.xMin) / 2;
 
1578
                        break;
 
1579
                case HALIGN_RIGHT:
 
1580
                        *bx = bbox.xMax;
 
1581
                        break;
 
1582
                }
 
1583
        if (by)
 
1584
                switch(valign) {
 
1585
                case VALIGN_TOP:
 
1586
                        *by = bbox.yMin;
 
1587
                        break;
 
1588
                case VALIGN_CENTER:
 
1589
                        *by = (bbox.yMax + bbox.yMin) / 2;
 
1590
                        break;
 
1591
                case VALIGN_SUB:
 
1592
                        *by = bbox.yMax;
 
1593
                        break;
 
1594
                }
 
1595
}
 
1596
 
 
1597
/**
 
1598
 * \brief Multiply 4-vector by 4-matrix
 
1599
 * \param a 4-vector
 
1600
 * \param m 4-matrix]
 
1601
 * \param b out: 4-vector
 
1602
 * Calculates a * m and stores result in b
 
1603
 */
 
1604
static inline void transform_point_3d(double *a, double *m, double *b)
 
1605
{
 
1606
        b[0] = a[0] * m[0] + a[1] * m[4] + a[2] * m[8] +  a[3] * m[12];
 
1607
        b[1] = a[0] * m[1] + a[1] * m[5] + a[2] * m[9] +  a[3] * m[13];
 
1608
        b[2] = a[0] * m[2] + a[1] * m[6] + a[2] * m[10] + a[3] * m[14];
 
1609
        b[3] = a[0] * m[3] + a[1] * m[7] + a[2] * m[11] + a[3] * m[15];
 
1610
}
 
1611
 
 
1612
/**
 
1613
 * \brief Apply 3d transformation to a vector
 
1614
 * \param v FreeType vector (2d)
 
1615
 * \param m 4-matrix
 
1616
 * Transforms v by m, projects the result back to the screen plane
 
1617
 * Result is returned in v.
 
1618
 */
 
1619
static inline void transform_vector_3d(FT_Vector* v, double *m) {
 
1620
        const double camera = 2500 * frame_context.border_scale; // camera distance
 
1621
        double a[4], b[4];
 
1622
        a[0] = d6_to_double(v->x);
 
1623
        a[1] = d6_to_double(v->y);
 
1624
        a[2] = 0.;
 
1625
        a[3] = 1.;
 
1626
        transform_point_3d(a, m, b);
 
1627
        /* Apply perspective projection with the following matrix:
 
1628
           2500     0     0     0
 
1629
              0  2500     0     0
 
1630
              0     0     0     0
 
1631
              0     0     8     2500
 
1632
           where 2500 is camera distance, 8 - z-axis scale.
 
1633
           Camera is always located in (org_x, org_y, -2500). This means
 
1634
           that different subtitle events can be displayed at the same time
 
1635
           using different cameras. */
 
1636
        b[0] *= camera;
 
1637
        b[1] *= camera;
 
1638
        b[3] = 8 * b[2] + camera;
 
1639
        if (b[3] < 0.001 && b[3] > -0.001)
 
1640
                b[3] = b[3] < 0. ? -0.001 : 0.001;
 
1641
        v->x = double_to_d6(b[0] / b[3]);
 
1642
        v->y = double_to_d6(b[1] / b[3]);
 
1643
}
 
1644
 
 
1645
/**
 
1646
 * \brief Apply 3d transformation to a glyph
 
1647
 * \param glyph FreeType glyph
 
1648
 * \param m 4-matrix
 
1649
 * Transforms glyph by m, projects the result back to the screen plane
 
1650
 * Result is returned in glyph.
 
1651
 */
 
1652
static inline void transform_glyph_3d(FT_Glyph glyph, double *m, FT_Vector shift) {
 
1653
        int i;
 
1654
        FT_Outline* outline = &((FT_OutlineGlyph)glyph)->outline;
 
1655
        FT_Vector* p = outline->points;
 
1656
 
 
1657
        for (i=0; i<outline->n_points; i++) {
 
1658
                p[i].x += shift.x;
 
1659
                p[i].y += shift.y;
 
1660
                transform_vector_3d(p + i, m);
 
1661
                p[i].x -= shift.x;
 
1662
                p[i].y -= shift.y;
 
1663
        }
 
1664
 
 
1665
        //transform_vector_3d(&glyph->advance, m);
 
1666
}
 
1667
 
 
1668
/**
 
1669
 * \brief Apply 3d transformation to several objects
 
1670
 * \param shift FreeType vector
 
1671
 * \param glyph FreeType glyph
 
1672
 * \param glyph2 FreeType glyph
 
1673
 * \param frx x-axis rotation angle
 
1674
 * \param fry y-axis rotation angle
 
1675
 * \param frz z-axis rotation angle
 
1676
 * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
 
1677
 */
 
1678
static void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz)
 
1679
{
 
1680
        fry = - fry; // FreeType's y axis goes in the opposite direction
 
1681
        if (frx != 0. || fry != 0. || frz != 0.) {
 
1682
                double m[16];
 
1683
                double sx = sin(frx);
 
1684
                double sy = sin(fry);
 
1685
                double sz = sin(frz);
 
1686
                double cx = cos(frx);
 
1687
                double cy = cos(fry);
 
1688
                double cz = cos(frz);
 
1689
                m[0] = cy * cz;            m[1] = cy*sz;              m[2]  = -sy;    m[3] = 0.0;
 
1690
                m[4] = -cx*sz + sx*sy*cz;  m[5] = cx*cz + sx*sy*sz;   m[6]  = sx*cy;  m[7] = 0.0;
 
1691
                m[8] = sx*sz + cx*sy*cz;   m[9] = -sx*cz + cx*sy*sz;  m[10] = cx*cy;  m[11] = 0.0;
 
1692
                m[12] = 0.0;               m[13] = 0.0;               m[14] = 0.0;    m[15] = 1.0;
 
1693
 
 
1694
                if (glyph && *glyph)
 
1695
                        transform_glyph_3d(*glyph, m, shift);
 
1696
 
 
1697
                if (glyph2 && *glyph2)
 
1698
                        transform_glyph_3d(*glyph2, m, shift);
 
1699
        }
 
1700
}
 
1701
 
 
1702
/**
 
1703
 * \brief Main ass rendering function, glues everything together
 
1704
 * \param event event to render
 
1705
 * Process event, appending resulting ass_image_t's to images_root.
 
1706
 */
 
1707
static int ass_render_event(ass_event_t* event, event_images_t* event_images)
 
1708
{
 
1709
        char* p;
 
1710
        FT_UInt previous; 
 
1711
        FT_UInt num_glyphs;
 
1712
        FT_Vector pen;
 
1713
        unsigned code;
 
1714
        FT_BBox bbox;
 
1715
        int i, j;
 
1716
        FT_Vector shift;
 
1717
        int MarginL, MarginR, MarginV;
 
1718
        int last_break;
 
1719
        int alignment, halign, valign;
 
1720
        int device_x = 0, device_y = 0;
 
1721
 
 
1722
        if (event->Style >= frame_context.track->n_styles) {
 
1723
                mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleFound);
 
1724
                return 1;
 
1725
        }
 
1726
        if (!event->Text) {
 
1727
                mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EmptyEvent);
 
1728
                return 1;
 
1729
        }
 
1730
 
 
1731
        init_render_context(event);
 
1732
 
 
1733
        text_info.length = 0;
 
1734
        pen.x = 0;
 
1735
        pen.y = 0;
 
1736
        previous = 0;
 
1737
        num_glyphs = 0;
 
1738
        p = event->Text;
 
1739
        // Event parsing.
 
1740
        while (1) {
 
1741
                // get next char, executing style override
 
1742
                // this affects render_context
 
1743
                code = get_next_char(&p);
 
1744
                
 
1745
                // face could have been changed in get_next_char
 
1746
                if (!render_context.font) {
 
1747
                        free_render_context();
 
1748
                        return 1;
 
1749
                }
 
1750
 
 
1751
                if (code == 0)
 
1752
                        break;
 
1753
 
 
1754
                if (text_info.length >= MAX_GLYPHS) {
 
1755
                        mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_MAX_GLYPHS_Reached, 
 
1756
                                        (int)(event - frame_context.track->events), event->Start, event->Duration, event->Text);
 
1757
                        break;
 
1758
                }
 
1759
 
 
1760
                if ( previous && code ) {
 
1761
                        FT_Vector delta;
 
1762
                        delta = ass_font_get_kerning(render_context.font, previous, code);
 
1763
                        pen.x += delta.x * render_context.scale_x;
 
1764
                        pen.y += delta.y * render_context.scale_y;
 
1765
                }
 
1766
 
 
1767
                shift.x = pen.x & 63;
 
1768
                shift.y = pen.y & 63;
 
1769
 
 
1770
                ass_font_set_transform(render_context.font,
 
1771
                                       render_context.scale_x * frame_context.font_scale_x,
 
1772
                                       render_context.scale_y,
 
1773
                                       &shift );
 
1774
 
 
1775
                get_outline_glyph(code, text_info.glyphs + text_info.length, &shift);
 
1776
                
 
1777
                text_info.glyphs[text_info.length].pos.x = pen.x >> 6;
 
1778
                text_info.glyphs[text_info.length].pos.y = pen.y >> 6;
 
1779
                
 
1780
                pen.x += text_info.glyphs[text_info.length].advance.x;
 
1781
                pen.x += double_to_d6(render_context.hspacing);
 
1782
                pen.y += text_info.glyphs[text_info.length].advance.y;
 
1783
                
 
1784
                previous = code;
 
1785
 
 
1786
                text_info.glyphs[text_info.length].symbol = code;
 
1787
                text_info.glyphs[text_info.length].linebreak = 0;
 
1788
                for (i = 0; i < 4; ++i) {
 
1789
                        uint32_t clr = render_context.c[i];
 
1790
                        change_alpha(&clr, mult_alpha(_a(clr), render_context.fade), 1.);
 
1791
                        text_info.glyphs[text_info.length].c[i] = clr;
 
1792
                }
 
1793
                text_info.glyphs[text_info.length].effect_type = render_context.effect_type;
 
1794
                text_info.glyphs[text_info.length].effect_timing = render_context.effect_timing;
 
1795
                text_info.glyphs[text_info.length].effect_skip_timing = render_context.effect_skip_timing;
 
1796
                text_info.glyphs[text_info.length].be = render_context.be;
 
1797
                text_info.glyphs[text_info.length].shadow = render_context.shadow;
 
1798
                text_info.glyphs[text_info.length].frx = render_context.frx;
 
1799
                text_info.glyphs[text_info.length].fry = render_context.fry;
 
1800
                text_info.glyphs[text_info.length].frz = render_context.frz;
 
1801
                ass_font_get_asc_desc(render_context.font, code,
 
1802
                                      &text_info.glyphs[text_info.length].asc,
 
1803
                                      &text_info.glyphs[text_info.length].desc);
 
1804
                text_info.glyphs[text_info.length].asc *= render_context.scale_y;
 
1805
                text_info.glyphs[text_info.length].desc *= render_context.scale_y;
 
1806
 
 
1807
                // fill bitmap_hash_key
 
1808
                text_info.glyphs[text_info.length].hash_key.font = render_context.font;
 
1809
                text_info.glyphs[text_info.length].hash_key.size = render_context.font_size;
 
1810
                text_info.glyphs[text_info.length].hash_key.outline = render_context.border * 0xFFFF;
 
1811
                text_info.glyphs[text_info.length].hash_key.scale_x = render_context.scale_x * 0xFFFF;
 
1812
                text_info.glyphs[text_info.length].hash_key.scale_y = render_context.scale_y * 0xFFFF;
 
1813
                text_info.glyphs[text_info.length].hash_key.frx = render_context.frx * 0xFFFF;
 
1814
                text_info.glyphs[text_info.length].hash_key.fry = render_context.fry * 0xFFFF;
 
1815
                text_info.glyphs[text_info.length].hash_key.frz = render_context.frz * 0xFFFF;
 
1816
                text_info.glyphs[text_info.length].hash_key.bold = render_context.bold;
 
1817
                text_info.glyphs[text_info.length].hash_key.italic = render_context.italic;
 
1818
                text_info.glyphs[text_info.length].hash_key.ch = code;
 
1819
                text_info.glyphs[text_info.length].hash_key.advance = shift;
 
1820
                text_info.glyphs[text_info.length].hash_key.be = render_context.be;
 
1821
 
 
1822
                text_info.length++;
 
1823
 
 
1824
                render_context.effect_type = EF_NONE;
 
1825
                render_context.effect_timing = 0;
 
1826
                render_context.effect_skip_timing = 0;
 
1827
        }
 
1828
        
 
1829
        if (text_info.length == 0) {
 
1830
                // no valid symbols in the event; this can be smth like {comment}
 
1831
                free_render_context();
 
1832
                return 1;
 
1833
        }
 
1834
        
 
1835
        // depends on glyph x coordinates being monotonous, so it should be done before line wrap
 
1836
        process_karaoke_effects();
 
1837
        
 
1838
        // alignments
 
1839
        alignment = render_context.alignment;
 
1840
        halign = alignment & 3;
 
1841
        valign = alignment & 12;
 
1842
 
 
1843
        MarginL = (event->MarginL) ? event->MarginL : render_context.style->MarginL; 
 
1844
        MarginR = (event->MarginR) ? event->MarginR : render_context.style->MarginR; 
 
1845
        MarginV = (event->MarginV) ? event->MarginV : render_context.style->MarginV;
 
1846
 
 
1847
        if (render_context.evt_type != EVENT_HSCROLL) {
 
1848
                int max_text_width;
 
1849
 
 
1850
                // calculate max length of a line
 
1851
                max_text_width = x2scr(frame_context.track->PlayResX - MarginR) - x2scr(MarginL);
 
1852
 
 
1853
                // rearrange text in several lines
 
1854
                wrap_lines_smart(max_text_width);
 
1855
 
 
1856
                // align text
 
1857
                last_break = -1;
 
1858
                for (i = 1; i < text_info.length + 1; ++i) { // (text_info.length + 1) is the end of the last line
 
1859
                        if ((i == text_info.length) || text_info.glyphs[i].linebreak) {
 
1860
                                int width, shift = 0;
 
1861
                                glyph_info_t* first_glyph = text_info.glyphs + last_break + 1;
 
1862
                                glyph_info_t* last_glyph = text_info.glyphs + i - 1;
 
1863
 
 
1864
                                while ((last_glyph > first_glyph) && ((last_glyph->symbol == '\n') || (last_glyph->symbol == 0)))
 
1865
                                        last_glyph --;
 
1866
 
 
1867
                                width = last_glyph->pos.x + d6_to_int(last_glyph->advance.x) - first_glyph->pos.x;
 
1868
                                if (halign == HALIGN_LEFT) { // left aligned, no action
 
1869
                                        shift = 0;
 
1870
                                } else if (halign == HALIGN_RIGHT) { // right aligned
 
1871
                                        shift = max_text_width - width;
 
1872
                                } else if (halign == HALIGN_CENTER) { // centered
 
1873
                                        shift = (max_text_width - width) / 2;
 
1874
                                }
 
1875
                                for (j = last_break + 1; j < i; ++j) {
 
1876
                                        text_info.glyphs[j].pos.x += shift;
 
1877
                                }
 
1878
                                last_break = i - 1;
 
1879
                        }
 
1880
                }
 
1881
        } else { // render_context.evt_type == EVENT_HSCROLL
 
1882
                measure_text();
 
1883
        }
 
1884
        
 
1885
        // determing text bounding box
 
1886
        compute_string_bbox(&text_info, &bbox);
 
1887
        
 
1888
        // determine device coordinates for text
 
1889
        
 
1890
        // x coordinate for everything except positioned events
 
1891
        if (render_context.evt_type == EVENT_NORMAL ||
 
1892
            render_context.evt_type == EVENT_VSCROLL) {
 
1893
                device_x = x2scr(MarginL);
 
1894
        } else if (render_context.evt_type == EVENT_HSCROLL) {
 
1895
                if (render_context.scroll_direction == SCROLL_RL)
 
1896
                        device_x = x2scr(frame_context.track->PlayResX - render_context.scroll_shift);
 
1897
                else if (render_context.scroll_direction == SCROLL_LR)
 
1898
                        device_x = x2scr(render_context.scroll_shift) - (bbox.xMax - bbox.xMin);
 
1899
        }
 
1900
 
 
1901
        // y coordinate for everything except positioned events
 
1902
        if (render_context.evt_type == EVENT_NORMAL ||
 
1903
            render_context.evt_type == EVENT_HSCROLL) {
 
1904
                if (valign == VALIGN_TOP) { // toptitle
 
1905
                        device_y = y2scr_top(MarginV) + d6_to_int(text_info.lines[0].asc);
 
1906
                } else if (valign == VALIGN_CENTER) { // midtitle
 
1907
                        int scr_y = y2scr(frame_context.track->PlayResY / 2);
 
1908
                        device_y = scr_y - (bbox.yMax - bbox.yMin) / 2;
 
1909
                } else { // subtitle
 
1910
                        int scr_y;
 
1911
                        if (valign != VALIGN_SUB)
 
1912
                                mp_msg(MSGT_ASS, MSGL_V, "Invalid valign, supposing 0 (subtitle)\n");
 
1913
                        scr_y = y2scr_sub(frame_context.track->PlayResY - MarginV);
 
1914
                        device_y = scr_y;
 
1915
                        device_y -= d6_to_int(text_info.height);
 
1916
                        device_y += d6_to_int(text_info.lines[0].asc);
 
1917
                }
 
1918
        } else if (render_context.evt_type == EVENT_VSCROLL) {
 
1919
                if (render_context.scroll_direction == SCROLL_TB)
 
1920
                        device_y = y2scr(render_context.clip_y0 + render_context.scroll_shift) - (bbox.yMax - bbox.yMin);
 
1921
                else if (render_context.scroll_direction == SCROLL_BT)
 
1922
                        device_y = y2scr(render_context.clip_y1 - render_context.scroll_shift);
 
1923
        }
 
1924
 
 
1925
        // positioned events are totally different
 
1926
        if (render_context.evt_type == EVENT_POSITIONED) {
 
1927
                int base_x = 0;
 
1928
                int base_y = 0;
 
1929
                mp_msg(MSGT_ASS, MSGL_DBG2, "positioned event at %d, %d\n", render_context.pos_x, render_context.pos_y);
 
1930
                get_base_point(bbox, alignment, &base_x, &base_y);
 
1931
                device_x = x2scr(render_context.pos_x) - base_x;
 
1932
                device_y = y2scr(render_context.pos_y) - base_y;
 
1933
        }
 
1934
        
 
1935
        // fix clip coordinates (they depend on alignment)
 
1936
        render_context.clip_x0 = x2scr(render_context.clip_x0);
 
1937
        render_context.clip_x1 = x2scr(render_context.clip_x1);
 
1938
        if (render_context.evt_type == EVENT_NORMAL ||
 
1939
            render_context.evt_type == EVENT_HSCROLL ||
 
1940
            render_context.evt_type == EVENT_VSCROLL) {
 
1941
                if (valign == VALIGN_TOP) {
 
1942
                        render_context.clip_y0 = y2scr_top(render_context.clip_y0);
 
1943
                        render_context.clip_y1 = y2scr_top(render_context.clip_y1);
 
1944
                } else if (valign == VALIGN_CENTER) {
 
1945
                        render_context.clip_y0 = y2scr(render_context.clip_y0);
 
1946
                        render_context.clip_y1 = y2scr(render_context.clip_y1);
 
1947
                } else if (valign == VALIGN_SUB) {
 
1948
                        render_context.clip_y0 = y2scr_sub(render_context.clip_y0);
 
1949
                        render_context.clip_y1 = y2scr_sub(render_context.clip_y1);
 
1950
                }
 
1951
        } else if (render_context.evt_type == EVENT_POSITIONED) {
 
1952
                render_context.clip_y0 = y2scr(render_context.clip_y0);
 
1953
                render_context.clip_y1 = y2scr(render_context.clip_y1);
 
1954
        }
 
1955
 
 
1956
        // calculate rotation parameters
 
1957
        {
 
1958
                FT_Vector center;
 
1959
                
 
1960
                if (render_context.have_origin) {
 
1961
                        center.x = x2scr(render_context.org_x);
 
1962
                        center.y = y2scr(render_context.org_y);
 
1963
                } else {
 
1964
                        int bx, by;
 
1965
                        get_base_point(bbox, alignment, &bx, &by);
 
1966
                        center.x = device_x + bx;
 
1967
                        center.y = device_y + by;
 
1968
                }
 
1969
 
 
1970
                for (i = 0; i < text_info.length; ++i) {
 
1971
                        glyph_info_t* info = text_info.glyphs + i;
 
1972
 
 
1973
                        if (info->hash_key.frx || info->hash_key.fry || info->hash_key.frz) {
 
1974
                                info->hash_key.shift_x = info->pos.x + device_x - center.x;
 
1975
                                info->hash_key.shift_y = - (info->pos.y + device_y - center.y);
 
1976
                        } else {
 
1977
                                info->hash_key.shift_x = 0;
 
1978
                                info->hash_key.shift_y = 0;
 
1979
                        }
 
1980
                }
 
1981
        }
 
1982
 
 
1983
        // convert glyphs to bitmaps
 
1984
        for (i = 0; i < text_info.length; ++i)
 
1985
                get_bitmap_glyph(text_info.glyphs + i);
 
1986
 
 
1987
        event_images->top = device_y - d6_to_int(text_info.lines[0].asc);
 
1988
        event_images->height = d6_to_int(text_info.height);
 
1989
        event_images->detect_collisions = render_context.detect_collisions;
 
1990
        event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1;
 
1991
        event_images->event = event;
 
1992
        event_images->imgs = render_text(&text_info, device_x, device_y);
 
1993
 
 
1994
        free_render_context();
 
1995
        
 
1996
        return 0;
 
1997
}
 
1998
 
 
1999
/**
 
2000
 * \brief deallocate image list
 
2001
 * \param img list pointer
 
2002
 */
 
2003
void ass_free_images(ass_image_t* img)
 
2004
{
 
2005
        while (img) {
 
2006
                ass_image_t* next = img->next;
 
2007
                free(img);
 
2008
                img = next;
 
2009
        }
 
2010
}
 
2011
 
 
2012
static void ass_reconfigure(ass_renderer_t* priv)
 
2013
{
 
2014
        priv->render_id = ++last_render_id;
 
2015
        ass_glyph_cache_reset();
 
2016
        ass_bitmap_cache_reset();
 
2017
        ass_free_images(priv->prev_images_root);
 
2018
        priv->prev_images_root = 0;
 
2019
}
 
2020
 
 
2021
void ass_set_frame_size(ass_renderer_t* priv, int w, int h)
 
2022
{
 
2023
        if (priv->settings.frame_width != w || priv->settings.frame_height != h) {
 
2024
                priv->settings.frame_width = w;
 
2025
                priv->settings.frame_height = h;
 
2026
                if (priv->settings.aspect == 0.)
 
2027
                        priv->settings.aspect = ((double)w) / h;
 
2028
                ass_reconfigure(priv);
 
2029
        }
 
2030
}
 
2031
 
 
2032
void ass_set_margins(ass_renderer_t* priv, int t, int b, int l, int r)
 
2033
{
 
2034
        if (priv->settings.left_margin != l ||
 
2035
            priv->settings.right_margin != r ||
 
2036
            priv->settings.top_margin != t ||
 
2037
            priv->settings.bottom_margin != b) {
 
2038
                priv->settings.left_margin = l;
 
2039
                priv->settings.right_margin = r;
 
2040
                priv->settings.top_margin = t;
 
2041
                priv->settings.bottom_margin = b;
 
2042
                ass_reconfigure(priv);
 
2043
        }
 
2044
}
 
2045
 
 
2046
void ass_set_use_margins(ass_renderer_t* priv, int use)
 
2047
{
 
2048
        priv->settings.use_margins = use;
 
2049
}
 
2050
 
 
2051
void ass_set_aspect_ratio(ass_renderer_t* priv, double ar)
 
2052
{
 
2053
        if (priv->settings.aspect != ar) {
 
2054
                priv->settings.aspect = ar;
 
2055
                ass_reconfigure(priv);
 
2056
        }
 
2057
}
 
2058
 
 
2059
void ass_set_font_scale(ass_renderer_t* priv, double font_scale)
 
2060
{
 
2061
        if (priv->settings.font_size_coeff != font_scale) {
 
2062
                priv->settings.font_size_coeff = font_scale;
 
2063
                ass_reconfigure(priv);
 
2064
        }
 
2065
}
 
2066
 
 
2067
void ass_set_hinting(ass_renderer_t* priv, ass_hinting_t ht)
 
2068
{
 
2069
        if (priv->settings.hinting != ht) {
 
2070
                priv->settings.hinting = ht;
 
2071
                ass_reconfigure(priv);
 
2072
        }
 
2073
}
 
2074
 
 
2075
int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family)
 
2076
{
 
2077
        if (priv->settings.default_font)
 
2078
                free(priv->settings.default_font);
 
2079
        if (priv->settings.default_family)
 
2080
                free(priv->settings.default_family);
 
2081
 
 
2082
        priv->settings.default_font = default_font ? strdup(default_font) : 0;
 
2083
        priv->settings.default_family = default_family ? strdup(default_family) : 0;
 
2084
 
 
2085
        if (priv->fontconfig_priv)
 
2086
                fontconfig_done(priv->fontconfig_priv);
 
2087
        priv->fontconfig_priv = fontconfig_init(priv->library, priv->ftlibrary, default_family, default_font);
 
2088
 
 
2089
        return !!priv->fontconfig_priv;
 
2090
}
 
2091
 
 
2092
/**
 
2093
 * \brief Start a new frame
 
2094
 */
 
2095
static int ass_start_frame(ass_renderer_t *priv, ass_track_t* track, long long now)
 
2096
{
 
2097
        ass_renderer = priv;
 
2098
        global_settings = &priv->settings;
 
2099
 
 
2100
        if (!priv->settings.frame_width && !priv->settings.frame_height)
 
2101
                return 1; // library not initialized
 
2102
        
 
2103
        frame_context.ass_priv = priv;
 
2104
        frame_context.width = global_settings->frame_width;
 
2105
        frame_context.height = global_settings->frame_height;
 
2106
        frame_context.orig_width = global_settings->frame_width - global_settings->left_margin - global_settings->right_margin;
 
2107
        frame_context.orig_height = global_settings->frame_height - global_settings->top_margin - global_settings->bottom_margin;
 
2108
        frame_context.track = track;
 
2109
        frame_context.time = now;
 
2110
 
 
2111
        ass_lazy_track_init();
 
2112
        
 
2113
        frame_context.font_scale = global_settings->font_size_coeff *
 
2114
                                   frame_context.orig_height / frame_context.track->PlayResY;
 
2115
        frame_context.border_scale = ((double)frame_context.orig_height) / frame_context.track->PlayResY;
 
2116
 
 
2117
        if (frame_context.orig_width * track->PlayResY == frame_context.orig_height * track->PlayResX)
 
2118
                frame_context.font_scale_x = 1.;
 
2119
        else
 
2120
                frame_context.font_scale_x = ((double)(frame_context.orig_width * track->PlayResY)) / (frame_context.orig_height * track->PlayResX);
 
2121
 
 
2122
        priv->prev_images_root = priv->images_root;
 
2123
        priv->images_root = 0;
 
2124
 
 
2125
        return 0;
 
2126
}
 
2127
 
 
2128
static int cmp_event_layer(const void* p1, const void* p2)
 
2129
{
 
2130
        ass_event_t* e1 = ((event_images_t*)p1)->event;
 
2131
        ass_event_t* e2 = ((event_images_t*)p2)->event;
 
2132
        if (e1->Layer < e2->Layer)
 
2133
                return -1;
 
2134
        if (e1->Layer > e2->Layer)
 
2135
                return 1;
 
2136
        if (e1->ReadOrder < e2->ReadOrder)
 
2137
                return -1;
 
2138
        if (e1->ReadOrder > e2->ReadOrder)
 
2139
                return 1;
 
2140
        return 0;
 
2141
}
 
2142
 
 
2143
#define MAX_EVENTS 100
 
2144
 
 
2145
static render_priv_t* get_render_priv(ass_event_t* event)
 
2146
{
 
2147
        if (!event->render_priv)
 
2148
                event->render_priv = calloc(1, sizeof(render_priv_t));
 
2149
        // FIXME: check render_id
 
2150
        if (ass_renderer->render_id != event->render_priv->render_id) {
 
2151
                memset(event->render_priv, 0, sizeof(render_priv_t));
 
2152
                event->render_priv->render_id = ass_renderer->render_id;
 
2153
        }
 
2154
        return event->render_priv;
 
2155
}
 
2156
 
 
2157
typedef struct segment_s {
 
2158
        int a, b; // top and height
 
2159
} segment_t;
 
2160
 
 
2161
static int overlap(segment_t* s1, segment_t* s2)
 
2162
{
 
2163
        if (s1->a >= s2->b || s2->a >= s1->b)
 
2164
                return 0;
 
2165
        return 1;
 
2166
}
 
2167
 
 
2168
static int cmp_segment(const void* p1, const void* p2)
 
2169
{
 
2170
        return ((segment_t*)p1)->a - ((segment_t*)p2)->a;
 
2171
}
 
2172
 
 
2173
static void shift_event(event_images_t* ei, int shift)
 
2174
{
 
2175
        ass_image_t* cur = ei->imgs;
 
2176
        while (cur) {
 
2177
                cur->dst_y += shift;
 
2178
                // clip top and bottom
 
2179
                if (cur->dst_y < 0) {
 
2180
                        int clip = - cur->dst_y;
 
2181
                        cur->h -= clip;
 
2182
                        cur->bitmap += clip * cur->stride;
 
2183
                        cur->dst_y = 0;
 
2184
                }
 
2185
                if (cur->dst_y + cur->h >= frame_context.height) {
 
2186
                        int clip = cur->dst_y + cur->h - frame_context.height;
 
2187
                        cur->h -= clip;
 
2188
                }
 
2189
                if (cur->h <= 0) {
 
2190
                        cur->h = 0;
 
2191
                        cur->dst_y = 0;
 
2192
                }
 
2193
                cur = cur->next;
 
2194
        }
 
2195
        ei->top += shift;
 
2196
}
 
2197
 
 
2198
// dir: 1 - move down
 
2199
//      -1 - move up
 
2200
static int fit_segment(segment_t* s, segment_t* fixed, int* cnt, int dir)
 
2201
{
 
2202
        int i;
 
2203
        int shift = 0;
 
2204
 
 
2205
        if (dir == 1) // move down
 
2206
                for (i = 0; i < *cnt; ++i) {
 
2207
                        if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b)
 
2208
                                continue;
 
2209
                        shift = fixed[i].b - s->a;
 
2210
                }
 
2211
        else // dir == -1, move up
 
2212
                for (i = *cnt-1; i >= 0; --i) {
 
2213
                        if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b)
 
2214
                                continue;
 
2215
                        shift = fixed[i].a - s->b;
 
2216
                }
 
2217
 
 
2218
        fixed[*cnt].a = s->a + shift;
 
2219
        fixed[*cnt].b = s->b + shift;
 
2220
        (*cnt)++;
 
2221
        qsort(fixed, *cnt, sizeof(segment_t), cmp_segment);
 
2222
        
 
2223
        return shift;
 
2224
}
 
2225
 
 
2226
static void fix_collisions(event_images_t* imgs, int cnt)
 
2227
{
 
2228
        segment_t used[MAX_EVENTS];
 
2229
        int cnt_used = 0;
 
2230
        int i, j;
 
2231
 
 
2232
        // fill used[] with fixed events
 
2233
        for (i = 0; i < cnt; ++i) {
 
2234
                render_priv_t* priv;
 
2235
                if (!imgs[i].detect_collisions) continue;
 
2236
                priv = get_render_priv(imgs[i].event);
 
2237
                if (priv->height > 0) { // it's a fixed event
 
2238
                        segment_t s;
 
2239
                        s.a = priv->top;
 
2240
                        s.b = priv->top + priv->height;
 
2241
                        if (priv->height != imgs[i].height) { // no, it's not
 
2242
                                mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventHeightHasChanged);
 
2243
                                priv->top = 0;
 
2244
                                priv->height = 0;
 
2245
                        }
 
2246
                        for (j = 0; j < cnt_used; ++j)
 
2247
                                if (overlap(&s, used + j)) { // no, it's not
 
2248
                                        priv->top = 0;
 
2249
                                        priv->height = 0;
 
2250
                                }
 
2251
                        if (priv->height > 0) { // still a fixed event
 
2252
                                used[cnt_used].a = priv->top;
 
2253
                                used[cnt_used].b = priv->top + priv->height;
 
2254
                                cnt_used ++;
 
2255
                                shift_event(imgs + i, priv->top - imgs[i].top);
 
2256
                        }
 
2257
                }
 
2258
        }
 
2259
        qsort(used, cnt_used, sizeof(segment_t), cmp_segment);
 
2260
 
 
2261
        // try to fit other events in free spaces
 
2262
        for (i = 0; i < cnt; ++i) {
 
2263
                render_priv_t* priv;
 
2264
                if (!imgs[i].detect_collisions) continue;
 
2265
                priv = get_render_priv(imgs[i].event);
 
2266
                if (priv->height == 0) { // not a fixed event
 
2267
                        int shift;
 
2268
                        segment_t s;
 
2269
                        s.a = imgs[i].top;
 
2270
                        s.b = imgs[i].top + imgs[i].height;
 
2271
                        shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction);
 
2272
                        if (shift) shift_event(imgs + i, shift);
 
2273
                        // make it fixed
 
2274
                        priv->top = imgs[i].top;
 
2275
                        priv->height = imgs[i].height;
 
2276
                }
 
2277
                
 
2278
        }
 
2279
}
 
2280
 
 
2281
/**
 
2282
 * \brief compare two images
 
2283
 * \param i1 first image
 
2284
 * \param i2 second image
 
2285
 * \return 0 if identical, 1 if different positions, 2 if different content
 
2286
 */
 
2287
int ass_image_compare(ass_image_t *i1, ass_image_t *i2)
 
2288
{
 
2289
        if (i1->w != i2->w) return 2;
 
2290
        if (i1->h != i2->h) return 2;
 
2291
        if (i1->stride != i2->stride) return 2;
 
2292
        if (i1->color != i2->color) return 2;
 
2293
        if (i1->bitmap != i2->bitmap)
 
2294
                return 2;
 
2295
        if (i1->dst_x != i2->dst_x) return 1;
 
2296
        if (i1->dst_y != i2->dst_y) return 1;
 
2297
        return 0;
 
2298
}
 
2299
 
 
2300
/**
 
2301
 * \brief compare current and previous image list
 
2302
 * \param priv library handle
 
2303
 * \return 0 if identical, 1 if different positions, 2 if different content
 
2304
 */
 
2305
int ass_detect_change(ass_renderer_t *priv)
 
2306
{
 
2307
        ass_image_t* img, *img2;
 
2308
        int diff;
 
2309
 
 
2310
        img = priv->prev_images_root;
 
2311
        img2 = priv->images_root;
 
2312
        diff = 0;
 
2313
        while (img && diff < 2) {
 
2314
                ass_image_t* next, *next2;
 
2315
                next = img->next;
 
2316
                if (img2) {
 
2317
                        int d = ass_image_compare(img, img2);
 
2318
                        if (d > diff) diff = d;
 
2319
                        next2 = img2->next;
 
2320
                } else {
 
2321
                        // previous list is shorter
 
2322
                        diff = 2;
 
2323
                        break;
 
2324
                }
 
2325
                img = next;
 
2326
                img2 = next2;
 
2327
        }
 
2328
 
 
2329
        // is the previous list longer?
 
2330
        if (img2)
 
2331
                diff = 2;
 
2332
 
 
2333
        return diff;
 
2334
}
 
2335
 
 
2336
/**
 
2337
 * \brief render a frame
 
2338
 * \param priv library handle
 
2339
 * \param track track
 
2340
 * \param now current video timestamp (ms)
 
2341
 * \param detect_change a value describing how the new images differ from the previous ones will be written here:
 
2342
 *        0 if identical, 1 if different positions, 2 if different content.
 
2343
 *        Can be NULL, in that case no detection is performed.
 
2344
 */
 
2345
ass_image_t* ass_render_frame(ass_renderer_t *priv, ass_track_t* track, long long now, int* detect_change)
 
2346
{
 
2347
        int i, cnt, rc;
 
2348
        event_images_t* last;
 
2349
        ass_image_t** tail;
 
2350
        
 
2351
        // init frame
 
2352
        rc = ass_start_frame(priv, track, now);
 
2353
        if (rc != 0)
 
2354
                return 0;
 
2355
 
 
2356
        // render events separately
 
2357
        cnt = 0;
 
2358
        for (i = 0; i < track->n_events; ++i) {
 
2359
                ass_event_t* event = track->events + i;
 
2360
                if ( (event->Start <= now) && (now < (event->Start + event->Duration)) ) {
 
2361
                        if (cnt >= priv->eimg_size) {
 
2362
                                priv->eimg_size += 100;
 
2363
                                priv->eimg = realloc(priv->eimg, priv->eimg_size * sizeof(event_images_t));
 
2364
                        }
 
2365
                        rc = ass_render_event(event, priv->eimg + cnt);
 
2366
                        if (!rc) ++cnt;
 
2367
                }
 
2368
        }
 
2369
 
 
2370
        // sort by layer
 
2371
        qsort(priv->eimg, cnt, sizeof(event_images_t), cmp_event_layer);
 
2372
 
 
2373
        // call fix_collisions for each group of events with the same layer
 
2374
        last = priv->eimg;
 
2375
        for (i = 1; i < cnt; ++i)
 
2376
                if (last->event->Layer != priv->eimg[i].event->Layer) {
 
2377
                        fix_collisions(last, priv->eimg + i - last);
 
2378
                        last = priv->eimg + i;
 
2379
                }
 
2380
        if (cnt > 0)
 
2381
                fix_collisions(last, priv->eimg + cnt - last);
 
2382
 
 
2383
        // concat lists
 
2384
        tail = &ass_renderer->images_root;
 
2385
        for (i = 0; i < cnt; ++i) {
 
2386
                ass_image_t* cur = priv->eimg[i].imgs;
 
2387
                while (cur) {
 
2388
                        *tail = cur;
 
2389
                        tail = &cur->next;
 
2390
                        cur = cur->next;
 
2391
                }
 
2392
        }
 
2393
 
 
2394
        if (detect_change)
 
2395
                *detect_change = ass_detect_change(priv);
 
2396
        
 
2397
        // free the previous image list
 
2398
        ass_free_images(priv->prev_images_root);
 
2399
        priv->prev_images_root = 0;
 
2400
 
 
2401
        return ass_renderer->images_root;
 
2402
}
 
2403