Mercurial > mplayer.hg
diff libass/ass_render.c @ 30200:48d020c5ceca
Update internal libass copy to commit 8db4a5
| author | greg |
|---|---|
| date | Fri, 08 Jan 2010 18:35:44 +0000 |
| parents | e1c1e13cb646 |
| children | e64df5862cea |
line wrap: on
line diff
--- a/libass/ass_render.c Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_render.c Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -31,8 +29,6 @@ #include FT_GLYPH_H #include FT_SYNTHESIS_H -#include "mputils.h" - #include "ass.h" #include "ass_font.h" #include "ass_bitmap.h" @@ -40,301 +36,283 @@ #include "ass_utils.h" #include "ass_fontconfig.h" #include "ass_library.h" - -#define MAX_GLYPHS 3000 -#define MAX_LINES 300 -#define BLUR_MAX_RADIUS 50.0 -#define MAX_BE 100 -#define ROUND(x) ((int) ((x) + .5)) -#define SUBPIXEL_MASK 56 // d6 bitmask for subpixel accuracy adjustment - -static int last_render_id = 0; - -typedef struct ass_settings_s { - int frame_width; - int frame_height; - double font_size_coeff; // font size multiplier - double line_spacing; // additional line spacing (in frame pixels) - int top_margin; // height of top margin. Everything except toptitles is shifted down by top_margin. - int bottom_margin; // height of bottom margin. (frame_height - top_margin - bottom_margin) is original video height. - int left_margin; - int right_margin; - int use_margins; // 0 - place all subtitles inside original frame - // 1 - use margins for placing toptitles and subtitles - double aspect; // frame aspect ratio, d_width / d_height. - ass_hinting_t hinting; - - char* default_font; - char* default_family; -} ass_settings_t; - -// a rendered event -typedef struct event_images_s { - ass_image_t* imgs; - int top, height; - int detect_collisions; - int shift_direction; - ass_event_t* event; -} event_images_t; - -struct ass_renderer_s { - ass_library_t* library; - FT_Library ftlibrary; - fc_instance_t* fontconfig_priv; - ass_settings_t settings; - int render_id; - ass_synth_priv_t* synth_priv; - - ass_image_t* images_root; // rendering result is stored here - ass_image_t* prev_images_root; +#include "ass_drawing.h" +#include "ass_render.h" +#include "ass_parse.h" - event_images_t* eimg; // temporary buffer for sorting rendered events - int eimg_size; // allocated buffer size -}; - -typedef enum {EF_NONE = 0, EF_KARAOKE, EF_KARAOKE_KF, EF_KARAOKE_KO} effect_t; +#define MAX_GLYPHS_INITIAL 1024 +#define MAX_LINES_INITIAL 64 +#define SUBPIXEL_MASK 63 +#define SUBPIXEL_ACCURACY 7 // d6 mask for subpixel accuracy adjustment +#define GLYPH_CACHE_MAX 1000 +#define BITMAP_CACHE_MAX_SIZE 50 * 1048576 -// describes a glyph -// glyph_info_t and text_info_t are used for text centering and word-wrapping operations -typedef struct glyph_info_s { - unsigned symbol; - FT_Glyph glyph; - FT_Glyph outline_glyph; - bitmap_t* bm; // glyph bitmap - bitmap_t* bm_o; // outline bitmap - bitmap_t* bm_s; // shadow bitmap - FT_BBox bbox; - FT_Vector pos; - char linebreak; // the first (leading) glyph of some line ? - uint32_t c[4]; // colors - FT_Vector advance; // 26.6 - effect_t effect_type; - int effect_timing; // time duration of current karaoke word - // after process_karaoke_effects: distance in pixels from the glyph origin. - // part of the glyph to the left of it is displayed in a different color. - int effect_skip_timing; // delay after the end of last karaoke word - int asc, desc; // font max ascender and descender -// int height; - int be; // blur edges - double blur; // gaussian blur - double shadow; - double frx, fry, frz; // rotation - - bitmap_hash_key_t hash_key; -} glyph_info_t; - -typedef struct line_info_s { - int asc, desc; -} line_info_t; - -typedef struct text_info_s { - glyph_info_t* glyphs; - int length; - line_info_t lines[MAX_LINES]; - int n_lines; - int height; -} text_info_t; - +static void ass_lazy_track_init(ASS_Renderer *render_priv) +{ + ASS_Track *track = render_priv->track; -// Renderer state. -// Values like current font face, color, screen position, clipping and so on are stored here. -typedef struct render_context_s { - ass_event_t* event; - ass_style_t* style; - - ass_font_t* font; - char* font_path; - double font_size; - - FT_Stroker stroker; - int alignment; // alignment overrides go here; if zero, style value will be used - double frx, fry, frz; - enum { EVENT_NORMAL, // "normal" top-, sub- or mid- title - EVENT_POSITIONED, // happens after pos(,), margins are ignored - EVENT_HSCROLL, // "Banner" transition effect, text_width is unlimited - EVENT_VSCROLL // "Scroll up", "Scroll down" transition effects - } evt_type; - double pos_x, pos_y; // position - double org_x, org_y; // origin - char have_origin; // origin is explicitly defined; if 0, get_base_point() is used - double scale_x, scale_y; - double hspacing; // distance between letters, in pixels - double border; // outline width - uint32_t c[4]; // colors(Primary, Secondary, so on) in RGBA - int clip_x0, clip_y0, clip_x1, clip_y1; - char detect_collisions; - uint32_t fade; // alpha from \fad - char be; // blur edges - double blur; // gaussian blur - double shadow; - int drawing_mode; // not implemented; when != 0 text is discarded, except for style override tags - - effect_t effect_type; - int effect_timing; - int effect_skip_timing; - - enum { SCROLL_LR, // left-to-right - SCROLL_RL, - SCROLL_TB, // top-to-bottom - SCROLL_BT - } scroll_direction; // for EVENT_HSCROLL, EVENT_VSCROLL - int scroll_shift; - - // face properties - char* family; - unsigned bold; - unsigned italic; - int treat_family_as_pattern; - -} render_context_t; - -// frame-global data -typedef struct frame_context_s { - ass_renderer_t* ass_priv; - int width, height; // screen dimensions - int orig_height; // frame height ( = screen height - margins ) - int orig_width; // frame width ( = screen width - margins ) - int orig_height_nocrop; // frame height ( = screen height - margins + cropheight) - int orig_width_nocrop; // frame width ( = screen width - margins + cropwidth) - ass_track_t* track; - long long time; // frame's timestamp, ms - double font_scale; - double font_scale_x; // x scale applied to all glyphs to preserve text aspect ratio - double border_scale; -} frame_context_t; - -static ass_renderer_t* ass_renderer; -static ass_settings_t* global_settings; -static text_info_t text_info; -static render_context_t render_context; -static frame_context_t frame_context; - -struct render_priv_s { - int top, height; - int render_id; -}; - -static void ass_lazy_track_init(void) -{ - ass_track_t* track = frame_context.track; - if (track->PlayResX && track->PlayResY) - return; - if (!track->PlayResX && !track->PlayResY) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NeitherPlayResXNorPlayResYDefined); - track->PlayResX = 384; - track->PlayResY = 288; - } else { - double orig_aspect = (global_settings->aspect * frame_context.height * frame_context.orig_width) / - frame_context.orig_height / frame_context.width; - if (!track->PlayResY && track->PlayResX == 1280) { - track->PlayResY = 1024; - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY); - } else if (!track->PlayResY) { - track->PlayResY = track->PlayResX / orig_aspect + .5; - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY); - } else if (!track->PlayResX && track->PlayResY == 1024) { - track->PlayResX = 1280; - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX); - } else if (!track->PlayResX) { - track->PlayResX = track->PlayResY * orig_aspect + .5; - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX); - } - } + if (track->PlayResX && track->PlayResY) + return; + if (!track->PlayResX && !track->PlayResY) { + ass_msg(render_priv->library, MSGL_WARN, + "Neither PlayResX nor PlayResY defined. Assuming 384x288"); + track->PlayResX = 384; + track->PlayResY = 288; + } else { + if (!track->PlayResY && track->PlayResX == 1280) { + track->PlayResY = 1024; + ass_msg(render_priv->library, MSGL_WARN, + "PlayResY undefined, setting to %d", track->PlayResY); + } else if (!track->PlayResY) { + track->PlayResY = track->PlayResX * 3 / 4; + ass_msg(render_priv->library, MSGL_WARN, + "PlayResY undefined, setting to %d", track->PlayResY); + } else if (!track->PlayResX && track->PlayResY == 1024) { + track->PlayResX = 1280; + ass_msg(render_priv->library, MSGL_WARN, + "PlayResX undefined, setting to %d", track->PlayResX); + } else if (!track->PlayResX) { + track->PlayResX = track->PlayResY * 4 / 3; + ass_msg(render_priv->library, MSGL_WARN, + "PlayResX undefined, setting to %d", track->PlayResX); + } + } } -ass_renderer_t* ass_renderer_init(ass_library_t* library) +ASS_Renderer *ass_renderer_init(ASS_Library *library) { - int error; - FT_Library ft; - ass_renderer_t* priv = 0; - int vmajor, vminor, vpatch; - - memset(&render_context, 0, sizeof(render_context)); - memset(&frame_context, 0, sizeof(frame_context)); - memset(&text_info, 0, sizeof(text_info)); + int error; + FT_Library ft; + ASS_Renderer *priv = 0; + int vmajor, vminor, vpatch; - error = FT_Init_FreeType( &ft ); - if ( error ) { - mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FT_Init_FreeTypeFailed); - goto ass_init_exit; - } + error = FT_Init_FreeType(&ft); + if (error) { + ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType"); + goto ass_init_exit; + } - FT_Library_Version(ft, &vmajor, &vminor, &vpatch); - mp_msg(MSGT_ASS, MSGL_V, "FreeType library version: %d.%d.%d\n", - vmajor, vminor, vpatch); - mp_msg(MSGT_ASS, MSGL_V, "FreeType headers version: %d.%d.%d\n", - FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); + FT_Library_Version(ft, &vmajor, &vminor, &vpatch); + ass_msg(library, MSGL_V, "FreeType library version: %d.%d.%d", + vmajor, vminor, vpatch); + ass_msg(library, MSGL_V, "FreeType headers version: %d.%d.%d", + FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); + + priv = calloc(1, sizeof(ASS_Renderer)); + if (!priv) { + FT_Done_FreeType(ft); + goto ass_init_exit; + } - priv = calloc(1, sizeof(ass_renderer_t)); - if (!priv) { - FT_Done_FreeType(ft); - goto ass_init_exit; - } + priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS); + + priv->library = library; + priv->ftlibrary = ft; + // images_root and related stuff is zero-filled in calloc - priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS); - - priv->library = library; - priv->ftlibrary = ft; - // images_root and related stuff is zero-filled in calloc + priv->cache.font_cache = ass_font_cache_init(library); + priv->cache.bitmap_cache = ass_bitmap_cache_init(library); + priv->cache.composite_cache = ass_composite_cache_init(library); + priv->cache.glyph_cache = ass_glyph_cache_init(library); + priv->cache.glyph_max = GLYPH_CACHE_MAX; + priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE; - ass_font_cache_init(); - ass_bitmap_cache_init(); - ass_composite_cache_init(); - ass_glyph_cache_init(); + priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL; + priv->text_info.max_lines = MAX_LINES_INITIAL; + priv->text_info.glyphs = + calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo)); + priv->text_info.lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo)); - text_info.glyphs = calloc(MAX_GLYPHS, sizeof(glyph_info_t)); + ass_init_exit: + if (priv) + ass_msg(library, MSGL_INFO, "Init"); + else + ass_msg(library, MSGL_ERR, "Init failed"); -ass_init_exit: - if (priv) mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_Init); - else mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_InitFailed); - - return priv; + return priv; } -void ass_renderer_done(ass_renderer_t* priv) +void ass_set_cache_limits(ASS_Renderer *render_priv, int glyph_max, + int bitmap_max) +{ + render_priv->cache.glyph_max = glyph_max ? glyph_max : GLYPH_CACHE_MAX; + render_priv->cache.bitmap_max_size = bitmap_max ? 1048576 * bitmap_max : + BITMAP_CACHE_MAX_SIZE; +} + +static void free_list_clear(ASS_Renderer *render_priv) +{ + if (render_priv->free_head) { + FreeList *item = render_priv->free_head; + while(item) { + FreeList *oi = item; + free(item->object); + item = item->next; + free(oi); + } + render_priv->free_head = NULL; + } +} + +static void ass_free_images(ASS_Image *img); + +void ass_renderer_done(ASS_Renderer *render_priv) { - ass_font_cache_done(); - ass_bitmap_cache_done(); - ass_composite_cache_done(); - ass_glyph_cache_done(); - if (render_context.stroker) { - FT_Stroker_Done(render_context.stroker); - render_context.stroker = 0; - } - if (priv && priv->ftlibrary) FT_Done_FreeType(priv->ftlibrary); - if (priv && priv->fontconfig_priv) fontconfig_done(priv->fontconfig_priv); - if (priv && priv->synth_priv) ass_synth_done(priv->synth_priv); - if (priv && priv->eimg) free(priv->eimg); - if (priv) free(priv); - if (text_info.glyphs) free(text_info.glyphs); + ass_font_cache_done(render_priv->cache.font_cache); + ass_bitmap_cache_done(render_priv->cache.bitmap_cache); + ass_composite_cache_done(render_priv->cache.composite_cache); + ass_glyph_cache_done(render_priv->cache.glyph_cache); + + ass_free_images(render_priv->images_root); + ass_free_images(render_priv->prev_images_root); + + if (render_priv->state.stroker) { + FT_Stroker_Done(render_priv->state.stroker); + render_priv->state.stroker = 0; + } + if (render_priv && render_priv->ftlibrary) + FT_Done_FreeType(render_priv->ftlibrary); + if (render_priv && render_priv->fontconfig_priv) + fontconfig_done(render_priv->fontconfig_priv); + if (render_priv && render_priv->synth_priv) + ass_synth_done(render_priv->synth_priv); + if (render_priv && render_priv->eimg) + free(render_priv->eimg); + free(render_priv->text_info.glyphs); + free(render_priv->text_info.lines); + + free(render_priv->settings.default_font); + free(render_priv->settings.default_family); + + free_list_clear(render_priv); + free(render_priv); } /** - * \brief Create a new ass_image_t - * Parameters are the same as ass_image_t fields. + * \brief Create a new ASS_Image + * Parameters are the same as ASS_Image fields. */ -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) +static ASS_Image *my_draw_bitmap(unsigned char *bitmap, int bitmap_w, + int bitmap_h, int stride, int dst_x, + int dst_y, uint32_t color) { - ass_image_t* img = calloc(1, sizeof(ass_image_t)); + ASS_Image *img = calloc(1, sizeof(ASS_Image)); + + img->w = bitmap_w; + img->h = bitmap_h; + img->stride = stride; + img->bitmap = bitmap; + img->color = color; + img->dst_x = dst_x; + img->dst_y = dst_y; + + return img; +} + +static double x2scr_pos(ASS_Renderer *render_priv, double x); +static double y2scr_pos(ASS_Renderer *render_priv, double y); + +/* + * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping + * + * Inverse clipping with the following strategy: + * - find rectangle from (x0, y0) to (cx0, y1) + * - find rectangle from (cx0, y0) to (cx1, cy0) + * - find rectangle from (cx0, cy1) to (cx1, y1) + * - find rectangle from (cx1, y0) to (x1, y1) + * These rectangles can be invalid and in this case are discarded. + * Afterwards, they are clipped against the screen coordinates. + * In an additional pass, the rectangles need to be split up left/right for + * karaoke effects. This can result in a lot of bitmaps (6 to be exact). + */ +static ASS_Image **render_glyph_i(ASS_Renderer *render_priv, + Bitmap *bm, int dst_x, int dst_y, + uint32_t color, uint32_t color2, int brk, + ASS_Image **tail) +{ + int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy; + Rect r[4]; + ASS_Image *img; + + dst_x += bm->left; + dst_y += bm->top; + + // we still need to clip against screen boundaries + zx = x2scr_pos(render_priv, 0); + zy = y2scr_pos(render_priv, 0); + sx = x2scr_pos(render_priv, render_priv->track->PlayResX); + sy = y2scr_pos(render_priv, render_priv->track->PlayResY); - assert(dst_x >= 0); - assert(dst_y >= 0); - assert(dst_x + bitmap_w <= frame_context.width); - assert(dst_y + bitmap_h <= frame_context.height); + x0 = 0; + y0 = 0; + x1 = bm->w; + y1 = bm->h; + cx0 = render_priv->state.clip_x0 - dst_x; + cy0 = render_priv->state.clip_y0 - dst_y; + cx1 = render_priv->state.clip_x1 - dst_x; + cy1 = render_priv->state.clip_y1 - dst_y; + + // calculate rectangles and discard invalid ones while we're at it. + i = 0; + r[i].x0 = x0; + r[i].y0 = y0; + r[i].x1 = (cx0 > x1) ? x1 : cx0; + r[i].y1 = y1; + if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; + r[i].x0 = (cx0 < 0) ? x0 : cx0; + r[i].y0 = y0; + r[i].x1 = (cx1 > x1) ? x1 : cx1; + r[i].y1 = (cy0 > y1) ? y1 : cy0; + if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; + r[i].x0 = (cx0 < 0) ? x0 : cx0; + r[i].y0 = (cy1 < 0) ? y0 : cy1; + r[i].x1 = (cx1 > x1) ? x1 : cx1; + r[i].y1 = y1; + if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; + r[i].x0 = (cx1 < 0) ? x0 : cx1; + r[i].y0 = y0; + r[i].x1 = x1; + r[i].y1 = y1; + if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; - img->w = bitmap_w; - img->h = bitmap_h; - img->stride = stride; - img->bitmap = bitmap; - img->color = color; - img->dst_x = dst_x; - img->dst_y = dst_y; + // clip each rectangle to screen coordinates + for (j = 0; j < i; j++) { + r[j].x0 = (r[j].x0 + dst_x < zx) ? zx - dst_x : r[j].x0; + r[j].y0 = (r[j].y0 + dst_y < zy) ? zy - dst_y : r[j].y0; + r[j].x1 = (r[j].x1 + dst_x > sx) ? sx - dst_x : r[j].x1; + r[j].y1 = (r[j].y1 + dst_y > sy) ? sy - dst_y : r[j].y1; + } - return img; + // draw the rectangles + for (j = 0; j < i; j++) { + int lbrk = brk; + // kick out rectangles that are invalid now + if (r[j].x1 <= r[j].x0 || r[j].y1 <= r[j].y0) + continue; + // split up into left and right for karaoke, if needed + if (lbrk > r[j].x0) { + if (lbrk > r[j].x1) lbrk = r[j].x1; + img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + r[j].x0, + lbrk - r[j].x0, r[j].y1 - r[j].y0, + bm->w, dst_x + r[j].x0, dst_y + r[j].y0, color); + *tail = img; + tail = &img->next; + } + if (lbrk < r[j].x1) { + if (lbrk < r[j].x0) lbrk = r[j].x0; + img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + lbrk, + r[j].x1 - lbrk, r[j].y1 - r[j].y0, + bm->w, dst_x + lbrk, dst_y + r[j].y0, color2); + *tail = img; + tail = &img->next; + } + } + + return tail; } /** - * \brief convert bitmap glyph into ass_image_t struct(s) + * \brief convert bitmap glyph into ASS_Image struct(s) * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY * \param dst_x bitmap x coordinate in video frame * \param dst_y bitmap y coordinate in video frame @@ -345,1156 +323,871 @@ * \return pointer to the new list tail * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion. */ -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) +static ASS_Image ** +render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y, + uint32_t color, uint32_t color2, int brk, ASS_Image **tail) { - // brk is relative to dst_x - // color = color left of brk - // color2 = color right of brk - int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap - int tmp; - ass_image_t* img; + // Inverse clipping in use? + if (render_priv->state.clip_mode) + return render_glyph_i(render_priv, bm, dst_x, dst_y, color, color2, + brk, tail); - const int clip_x0 = render_context.clip_x0; - const int clip_y0 = render_context.clip_y0; - const int clip_x1 = render_context.clip_x1; - const int clip_y1 = render_context.clip_y1; + // brk is relative to dst_x + // color = color left of brk + // color2 = color right of brk + int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap + int clip_x0, clip_y0, clip_x1, clip_y1; + int tmp; + ASS_Image *img; - dst_x += bm->left; - dst_y += bm->top; - brk -= bm->left; + dst_x += bm->left; + dst_y += bm->top; + brk -= bm->left; - b_x0 = 0; - b_y0 = 0; - b_x1 = bm->w; - b_y1 = bm->h; + // clipping + clip_x0 = FFMINMAX(render_priv->state.clip_x0, 0, render_priv->width); + clip_y0 = FFMINMAX(render_priv->state.clip_y0, 0, render_priv->height); + clip_x1 = FFMINMAX(render_priv->state.clip_x1, 0, render_priv->width); + clip_y1 = FFMINMAX(render_priv->state.clip_y1, 0, render_priv->height); + b_x0 = 0; + b_y0 = 0; + b_x1 = bm->w; + b_y1 = bm->h; - tmp = dst_x - clip_x0; - if (tmp < 0) { - mp_msg(MSGT_ASS, MSGL_DBG2, "clip left\n"); - b_x0 = - tmp; - } - tmp = dst_y - clip_y0; - if (tmp < 0) { - mp_msg(MSGT_ASS, MSGL_DBG2, "clip top\n"); - b_y0 = - tmp; - } - tmp = clip_x1 - dst_x - bm->w; - if (tmp < 0) { - mp_msg(MSGT_ASS, MSGL_DBG2, "clip right\n"); - b_x1 = bm->w + tmp; - } - tmp = clip_y1 - dst_y - bm->h; - if (tmp < 0) { - mp_msg(MSGT_ASS, MSGL_DBG2, "clip bottom\n"); - b_y1 = bm->h + tmp; - } + tmp = dst_x - clip_x0; + if (tmp < 0) { + ass_msg(render_priv->library, MSGL_DBG2, "clip left"); + b_x0 = -tmp; + } + tmp = dst_y - clip_y0; + if (tmp < 0) { + ass_msg(render_priv->library, MSGL_DBG2, "clip top"); + b_y0 = -tmp; + } + tmp = clip_x1 - dst_x - bm->w; + if (tmp < 0) { + ass_msg(render_priv->library, MSGL_DBG2, "clip right"); + b_x1 = bm->w + tmp; + } + tmp = clip_y1 - dst_y - bm->h; + if (tmp < 0) { + ass_msg(render_priv->library, MSGL_DBG2, "clip bottom"); + b_y1 = bm->h + tmp; + } - if ((b_y0 >= b_y1) || (b_x0 >= b_x1)) - return tail; + if ((b_y0 >= b_y1) || (b_x0 >= b_x1)) + return tail; - if (brk > b_x0) { // draw left part - if (brk > b_x1) brk = b_x1; - img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0, - brk - b_x0, b_y1 - b_y0, bm->w, - dst_x + b_x0, dst_y + b_y0, color); - *tail = img; - tail = &img->next; - } - if (brk < b_x1) { // draw right part - if (brk < b_x0) brk = b_x0; - img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk, - b_x1 - brk, b_y1 - b_y0, bm->w, - dst_x + brk, dst_y + b_y0, color2); - *tail = img; - tail = &img->next; - } - return tail; + if (brk > b_x0) { // draw left part + if (brk > b_x1) + brk = b_x1; + img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0, + brk - b_x0, b_y1 - b_y0, bm->w, + dst_x + b_x0, dst_y + b_y0, color); + *tail = img; + tail = &img->next; + } + if (brk < b_x1) { // draw right part + if (brk < b_x0) + brk = b_x0; + img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk, + b_x1 - brk, b_y1 - b_y0, bm->w, + dst_x + brk, dst_y + b_y0, color2); + *tail = img; + tail = &img->next; + } + return tail; } /** - * \brief Replaces the bitmap buffer in ass_image_t with its copy. - * - * @param img Image to operate on. - * @return Address of the old buffer. + * \brief Replace the bitmap buffer in ASS_Image with a copy + * \param img ASS_Image to operate on + * \return pointer to old bitmap buffer */ -static unsigned char* clone_bitmap_data(ass_image_t* img) +static unsigned char *clone_bitmap_buffer(ASS_Image *img) { - unsigned char* old_bitmap = img->bitmap; - int size = img->stride * (img->h - 1) + img->w; - img->bitmap = malloc(size); - memcpy(img->bitmap, old_bitmap, size); - return old_bitmap; + unsigned char *old_bitmap = img->bitmap; + int size = img->stride * (img->h - 1) + img->w; + img->bitmap = malloc(size); + memcpy(img->bitmap, old_bitmap, size); + return old_bitmap; } /** * \brief Calculate overlapping area of two consecutive bitmaps and in case they - * overlap, composite them together + * overlap, blend them together * Mainly useful for translucent glyphs and especially borders, to avoid the * luminance adding up where they overlap (which looks ugly) */ -static void render_overlap(ass_image_t** last_tail, ass_image_t** tail, bitmap_hash_key_t *last_hash, bitmap_hash_key_t* hash) { - int left, top, bottom, right; - int old_left, old_top, w, h, cur_left, cur_top; - int x, y, opos, cpos; - char m; - composite_hash_key_t hk; - composite_hash_val_t *hv; - composite_hash_key_t *nhk; - int ax = (*last_tail)->dst_x; - int ay = (*last_tail)->dst_y; - int aw = (*last_tail)->w; - int as = (*last_tail)->stride; - int ah = (*last_tail)->h; - int bx = (*tail)->dst_x; - int by = (*tail)->dst_y; - int bw = (*tail)->w; - int bs = (*tail)->stride; - int bh = (*tail)->h; - unsigned char* a; - unsigned char* b; +static void +render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail, + ASS_Image **tail) +{ + int left, top, bottom, right; + int old_left, old_top, w, h, cur_left, cur_top; + int x, y, opos, cpos; + char m; + CompositeHashKey hk; + CompositeHashValue *hv; + CompositeHashValue chv; + int ax = (*last_tail)->dst_x; + int ay = (*last_tail)->dst_y; + int aw = (*last_tail)->w; + int as = (*last_tail)->stride; + int ah = (*last_tail)->h; + int bx = (*tail)->dst_x; + int by = (*tail)->dst_y; + int bw = (*tail)->w; + int bs = (*tail)->stride; + int bh = (*tail)->h; + unsigned char *a; + unsigned char *b; - if ((*last_tail)->bitmap == (*tail)->bitmap) - return; + if ((*last_tail)->bitmap == (*tail)->bitmap) + return; - if ((*last_tail)->color != (*tail)->color) - return; + if ((*last_tail)->color != (*tail)->color) + return; - // Calculate overlap coordinates - left = (ax > bx) ? ax : bx; - top = (ay > by) ? ay : by; - right = ((ax+aw) < (bx+bw)) ? (ax+aw) : (bx+bw); - bottom = ((ay+ah) < (by+bh)) ? (ay+ah) : (by+bh); - if ((right <= left) || (bottom <= top)) - return; - old_left = left-ax; - old_top = top-ay; - w = right-left; - h = bottom-top; - cur_left = left-bx; - cur_top = top-by; + // Calculate overlap coordinates + left = (ax > bx) ? ax : bx; + top = (ay > by) ? ay : by; + right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw); + bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh); + if ((right <= left) || (bottom <= top)) + return; + old_left = left - ax; + old_top = top - ay; + w = right - left; + h = bottom - top; + cur_left = left - bx; + cur_top = top - by; - // Query cache - memset(&hk, 0, sizeof(hk)); - memcpy(&hk.a, last_hash, sizeof(*last_hash)); - memcpy(&hk.b, hash, sizeof(*hash)); - hk.aw = aw; - hk.ah = ah; - hk.bw = bw; - hk.bh = bh; - hk.ax = ax; - hk.ay = ay; - hk.bx = bx; - hk.by = by; - hv = cache_find_composite(&hk); - if (hv) { - (*last_tail)->bitmap = hv->a; - (*tail)->bitmap = hv->b; - return; - } + // Query cache + memset(&hk, 0, sizeof(hk)); + hk.a = (*last_tail)->bitmap; + hk.b = (*tail)->bitmap; + hk.aw = aw; + hk.ah = ah; + hk.bw = bw; + hk.bh = bh; + hk.ax = ax; + hk.ay = ay; + hk.bx = bx; + hk.by = by; + hk.as = as; + hk.bs = bs; + hv = cache_find_composite(render_priv->cache.composite_cache, &hk); + if (hv) { + (*last_tail)->bitmap = hv->a; + (*tail)->bitmap = hv->b; + return; + } + // Allocate new bitmaps and copy over data + a = clone_bitmap_buffer(*last_tail); + b = clone_bitmap_buffer(*tail); - // Allocate new bitmaps and copy over data - a = clone_bitmap_data(*last_tail); - b = clone_bitmap_data(*tail); + // Blend overlapping area + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + opos = (old_top + y) * (as) + (old_left + x); + cpos = (cur_top + y) * (bs) + (cur_left + x); + m = FFMIN(a[opos] + b[cpos], 0xff); + (*last_tail)->bitmap[opos] = 0; + (*tail)->bitmap[cpos] = m; + } - // Composite overlapping area - for (y=0; y<h; y++) - for (x=0; x<w; x++) { - opos = (old_top+y)*(as) + (old_left+x); - cpos = (cur_top+y)*(bs) + (cur_left+x); - m = (a[opos] > b[cpos]) ? a[opos] : b[cpos]; - (*last_tail)->bitmap[opos] = 0; - (*tail)->bitmap[cpos] = m; - } + // Insert bitmaps into the cache + chv.a = (*last_tail)->bitmap; + chv.b = (*tail)->bitmap; + cache_add_composite(render_priv->cache.composite_cache, &hk, &chv); +} - // Insert bitmaps into the cache - nhk = calloc(1, sizeof(*nhk)); - memcpy(nhk, &hk, sizeof(*nhk)); - hv = calloc(1, sizeof(*hv)); - hv->a = (*last_tail)->bitmap; - hv->b = (*tail)->bitmap; - cache_add_composite(nhk, hv); +static void free_list_add(ASS_Renderer *render_priv, void *object) +{ + if (!render_priv->free_head) { + render_priv->free_head = calloc(1, sizeof(FreeList)); + render_priv->free_head->object = object; + render_priv->free_tail = render_priv->free_head; + } else { + FreeList *l = calloc(1, sizeof(FreeList)); + l->object = object; + render_priv->free_tail->next = l; + render_priv->free_tail = render_priv->free_tail->next; + } } /** - * \brief Convert text_info_t struct to ass_image_t list - * Splits glyphs in halves when needed (for \kf karaoke). + * Iterate through a list of bitmaps and blend with clip vector, if + * applicable. The blended bitmaps are added to a free list which is freed + * at the start of a new frame. */ -static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y) +static void blend_vector_clip(ASS_Renderer *render_priv, + ASS_Image *head) { - int pen_x, pen_y; - int i; - bitmap_t* bm; - ass_image_t* head; - ass_image_t** tail = &head; - ass_image_t** last_tail = 0; - ass_image_t** here_tail = 0; - bitmap_hash_key_t* last_hash = 0; + FT_Glyph glyph; + FT_BitmapGlyph clip_bm; + ASS_Image *cur; + ASS_Drawing *drawing = render_priv->state.clip_drawing; + int error; + + if (!drawing) + return; - for (i = 0; i < text_info->length; ++i) { - glyph_info_t* info = text_info->glyphs + i; - if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s || (info->shadow == 0)) - continue; + // Rasterize it + FT_Glyph_Copy((FT_Glyph) drawing->glyph, &glyph); + error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1); + if (error) { + ass_msg(render_priv->library, MSGL_V, + "Clip vector rasterization failed: %d. Skipping.", error); + goto blend_vector_exit; + } + clip_bm = (FT_BitmapGlyph) glyph; + clip_bm->top = -clip_bm->top; - pen_x = dst_x + info->pos.x + ROUND(info->shadow * frame_context.border_scale); - pen_y = dst_y + info->pos.y + ROUND(info->shadow * frame_context.border_scale); - bm = info->bm_s; + assert(clip_bm->bitmap.pitch >= 0); - here_tail = tail; - tail = render_glyph(bm, pen_x, pen_y, info->c[3], 0, 1000000, tail); - if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0)) - render_overlap(last_tail, here_tail, last_hash, &info->hash_key); - last_tail = here_tail; - last_hash = &info->hash_key; - } + // Iterate through bitmaps and blend/clip them + for (cur = head; cur; cur = cur->next) { + int left, top, right, bottom, apos, bpos, y, x, w, h; + int ax, ay, aw, ah, as; + int bx, by, bw, bh, bs; + int aleft, atop, bleft, btop; + unsigned char *abuffer, *bbuffer, *nbuffer; - last_tail = 0; - for (i = 0; i < text_info->length; ++i) { - glyph_info_t* info = text_info->glyphs + i; - if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o) - continue; + abuffer = cur->bitmap; + bbuffer = clip_bm->bitmap.buffer; + ax = cur->dst_x; + ay = cur->dst_y; + aw = cur->w; + ah = cur->h; + as = cur->stride; + bx = clip_bm->left; + by = clip_bm->top; + bw = clip_bm->bitmap.width; + bh = clip_bm->bitmap.rows; + bs = clip_bm->bitmap.pitch; - pen_x = dst_x + info->pos.x; - pen_y = dst_y + info->pos.y; - bm = info->bm_o; + // Calculate overlap coordinates + left = (ax > bx) ? ax : bx; + top = (ay > by) ? ay : by; + right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw); + bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh); + aleft = left - ax; + atop = top - ay; + w = right - left; + h = bottom - top; + bleft = left - bx; + btop = top - by; + + if (render_priv->state.clip_drawing_mode) { + // Inverse clip + if (ax + aw < bx || ay + ah < by || ax > bx + bw || + ay > by + bh) { + continue; + } + + // Allocate new buffer and add to free list + nbuffer = malloc(as * ah); + free_list_add(render_priv, nbuffer); - if ((info->effect_type == EF_KARAOKE_KO) && (info->effect_timing <= info->bbox.xMax)) { - // do nothing - } else { - here_tail = tail; - tail = render_glyph(bm, pen_x, pen_y, info->c[2], 0, 1000000, tail); - if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0)) - render_overlap(last_tail, here_tail, last_hash, &info->hash_key); - last_tail = here_tail; - last_hash = &info->hash_key; - } - } - for (i = 0; i < text_info->length; ++i) { - glyph_info_t* info = text_info->glyphs + i; - if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm) - continue; + // Blend together + memcpy(nbuffer, abuffer, as * (ah - 1) + aw); + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + apos = (atop + y) * as + aleft + x; + bpos = (btop + y) * bs + bleft + x; + nbuffer[apos] = FFMAX(0, abuffer[apos] - bbuffer[bpos]); + } + } else { + // Regular clip + if (ax + aw < bx || ay + ah < by || ax > bx + bw || + ay > by + bh) { + cur->w = cur->h = 0; + continue; + } - pen_x = dst_x + info->pos.x; - pen_y = dst_y + info->pos.y; - bm = info->bm; + // Allocate new buffer and add to free list + nbuffer = calloc(as, ah); + free_list_add(render_priv, nbuffer); - if ((info->effect_type == EF_KARAOKE) || (info->effect_type == EF_KARAOKE_KO)) { - if (info->effect_timing > info->bbox.xMax) - tail = render_glyph(bm, pen_x, pen_y, info->c[0], 0, 1000000, tail); - else - tail = render_glyph(bm, pen_x, pen_y, info->c[1], 0, 1000000, tail); - } else if (info->effect_type == EF_KARAOKE_KF) { - tail = render_glyph(bm, pen_x, pen_y, info->c[0], info->c[1], info->effect_timing, tail); - } else - tail = render_glyph(bm, pen_x, pen_y, info->c[0], 0, 1000000, tail); - } + // Blend together + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + apos = (atop + y) * as + aleft + x; + bpos = (btop + y) * bs + bleft + x; + nbuffer[apos] = (abuffer[apos] * bbuffer[bpos] + 255) >> 8; + } + } + cur->bitmap = nbuffer; + } - *tail = 0; - return head; + // Free clip vector and its bitmap, we don't need it anymore + FT_Done_Glyph(glyph); +blend_vector_exit: + ass_drawing_free(render_priv->state.clip_drawing); + render_priv->state.clip_drawing = 0; } /** - * \brief Mapping between script and screen coordinates + * \brief Convert TextInfo struct to ASS_Image list + * Splits glyphs in halves when needed (for \kf karaoke). */ -static int x2scr(double x) { - return x*frame_context.orig_width_nocrop / frame_context.track->PlayResX + - FFMAX(global_settings->left_margin, 0); -} -static double x2scr_pos(double x) { - return x*frame_context.orig_width / frame_context.track->PlayResX + - global_settings->left_margin; +static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x, + int dst_y) +{ + int pen_x, pen_y; + int i; + Bitmap *bm; + ASS_Image *head; + ASS_Image **tail = &head; + ASS_Image **last_tail = 0; + ASS_Image **here_tail = 0; + TextInfo *text_info = &render_priv->text_info; + + for (i = 0; i < text_info->length; ++i) { + GlyphInfo *info = text_info->glyphs + i; + if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s + || (info->shadow_x == 0 && info->shadow_y == 0) || info->skip) + continue; + + pen_x = + dst_x + (info->pos.x >> 6) + + (int) (info->shadow_x * render_priv->border_scale); + pen_y = + dst_y + (info->pos.y >> 6) + + (int) (info->shadow_y * render_priv->border_scale); + bm = info->bm_s; + + here_tail = tail; + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0, + 1000000, tail); + if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0)) + render_overlap(render_priv, last_tail, here_tail); + + last_tail = here_tail; + } + + last_tail = 0; + for (i = 0; i < text_info->length; ++i) { + GlyphInfo *info = text_info->glyphs + i; + if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o + || info->skip) + continue; + + pen_x = dst_x + (info->pos.x >> 6); + pen_y = dst_y + (info->pos.y >> 6); + bm = info->bm_o; + + if ((info->effect_type == EF_KARAOKE_KO) + && (info->effect_timing <= (info->bbox.xMax >> 6))) { + // do nothing + } else { + here_tail = tail; + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[2], + 0, 1000000, tail); + if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0)) + render_overlap(render_priv, last_tail, here_tail); + + last_tail = here_tail; + } + } + + for (i = 0; i < text_info->length; ++i) { + GlyphInfo *info = text_info->glyphs + i; + if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm + || info->skip) + continue; + + pen_x = dst_x + (info->pos.x >> 6); + pen_y = dst_y + (info->pos.y >> 6); + bm = info->bm; + + if ((info->effect_type == EF_KARAOKE) + || (info->effect_type == EF_KARAOKE_KO)) { + if (info->effect_timing > (info->bbox.xMax >> 6)) + tail = + render_glyph(render_priv, bm, pen_x, pen_y, + info->c[0], 0, 1000000, tail); + else + tail = + render_glyph(render_priv, bm, pen_x, pen_y, + info->c[1], 0, 1000000, tail); + } else if (info->effect_type == EF_KARAOKE_KF) { + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[0], + info->c[1], info->effect_timing, tail); + } else + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[0], + 0, 1000000, tail); + } + + *tail = 0; + blend_vector_clip(render_priv, head); + + return head; } /** * \brief Mapping between script and screen coordinates */ -static double y2scr(double y) { - return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY + - FFMAX(global_settings->top_margin, 0); -} -static double y2scr_pos(double y) { - return y * frame_context.orig_height / frame_context.track->PlayResY + - global_settings->top_margin; -} - -// the same for toptitles -static int y2scr_top(double y) { - if (global_settings->use_margins) - return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY; - else - return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY + - FFMAX(global_settings->top_margin, 0); -} -// the same for subtitles -static int y2scr_sub(double y) { - if (global_settings->use_margins) - return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY + - FFMAX(global_settings->top_margin, 0) + - FFMAX(global_settings->bottom_margin, 0); - else - return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY + - FFMAX(global_settings->top_margin, 0); -} - -static void compute_string_bbox( text_info_t* info, FT_BBox *abbox ) { - FT_BBox bbox; - int i; - - if (text_info.length > 0) { - bbox.xMin = 32000; - bbox.xMax = -32000; - bbox.yMin = - d6_to_int(text_info.lines[0].asc) + text_info.glyphs[0].pos.y; - bbox.yMax = d6_to_int(text_info.height - text_info.lines[0].asc) + text_info.glyphs[0].pos.y; - - for (i = 0; i < text_info.length; ++i) { - int s = text_info.glyphs[i].pos.x; - int e = s + d6_to_int(text_info.glyphs[i].advance.x); - bbox.xMin = FFMIN(bbox.xMin, s); - bbox.xMax = FFMAX(bbox.xMax, e); - } - } else - bbox.xMin = bbox.xMax = bbox.yMin = bbox.yMax = 0; - - /* return string bbox */ - *abbox = bbox; -} - - -/** - * \brief Check if starting part of (*p) matches sample. If true, shift p to the first symbol after the matching part. - */ -static inline int mystrcmp(char** p, const char* sample) { - int len = strlen(sample); - if (strncmp(*p, sample, len) == 0) { - (*p) += len; - return 1; - } else - return 0; -} - -static void change_font_size(double sz) -{ - double size = sz * frame_context.font_scale; - - if (size < 1) - size = 1; - else if (size > frame_context.height * 2) - size = frame_context.height * 2; - - ass_font_set_size(render_context.font, size); - - render_context.font_size = sz; -} - -/** - * \brief Change current font, using setting from render_context. - */ -static void update_font(void) +static double x2scr(ASS_Renderer *render_priv, double x) { - unsigned val; - ass_renderer_t* priv = frame_context.ass_priv; - ass_font_desc_t desc; - desc.family = strdup(render_context.family); - desc.treat_family_as_pattern = render_context.treat_family_as_pattern; - - val = render_context.bold; - // 0 = normal, 1 = bold, >1 = exact weight - if (val == 0) val = 80; // normal - else if (val == 1) val = 200; // bold - desc.bold = val; - - val = render_context.italic; - if (val == 0) val = 0; // normal - else if (val == 1) val = 110; //italic - desc.italic = val; - - render_context.font = ass_font_new(priv->library, priv->ftlibrary, priv->fontconfig_priv, &desc); - free(desc.family); - - if (render_context.font) - change_font_size(render_context.font_size); + return x * render_priv->orig_width_nocrop / + render_priv->track->PlayResX + + FFMAX(render_priv->settings.left_margin, 0); } - -/** - * \brief Change border width - * negative value resets border to style value - */ -static void change_border(double border) +static double x2scr_pos(ASS_Renderer *render_priv, double x) { - int b; - if (!render_context.font) return; - - if (border < 0) { - if (render_context.style->BorderStyle == 1) - border = render_context.style->Outline; - else - border = 1.; - } - render_context.border = border; - - b = 64 * border * frame_context.border_scale; - if (b > 0) { - if (!render_context.stroker) { - int error; -#if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1)) - error = FT_Stroker_New( ass_renderer->ftlibrary, &render_context.stroker ); -#else // < 2.2 - error = FT_Stroker_New( render_context.font->faces[0]->memory, &render_context.stroker ); -#endif - if (error) { - mp_msg(MSGT_ASS, MSGL_V, "failed to get stroker\n"); - render_context.stroker = 0; - } - } - if (render_context.stroker) - FT_Stroker_Set( render_context.stroker, b, - FT_STROKER_LINECAP_ROUND, - FT_STROKER_LINEJOIN_ROUND, - 0 ); - } else { - FT_Stroker_Done(render_context.stroker); - render_context.stroker = 0; - } -} - -#define _r(c) ((c)>>24) -#define _g(c) (((c)>>16)&0xFF) -#define _b(c) (((c)>>8)&0xFF) -#define _a(c) ((c)&0xFF) - -/** - * \brief Calculate a weighted average of two colors - * calculates c1*(1-a) + c2*a, but separately for each component except alpha - */ -static void change_color(uint32_t* var, uint32_t new, double pwr) -{ - (*var)= ((uint32_t)(_r(*var) * (1 - pwr) + _r(new) * pwr) << 24) + - ((uint32_t)(_g(*var) * (1 - pwr) + _g(new) * pwr) << 16) + - ((uint32_t)(_b(*var) * (1 - pwr) + _b(new) * pwr) << 8) + - _a(*var); -} - -// like change_color, but for alpha component only -static void change_alpha(uint32_t* var, uint32_t new, double pwr) -{ - *var = (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) + (_a(*var) * (1 - pwr) + _a(new) * pwr); -} - -/** - * \brief Multiply two alpha values - * \param a first value - * \param b second value - * \return result of multiplication - * Parameters and result are limited by 0xFF. - */ -static uint32_t mult_alpha(uint32_t a, uint32_t b) -{ - return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF; + return x * render_priv->orig_width / render_priv->track->PlayResX + + render_priv->settings.left_margin; } /** - * \brief Calculate alpha value by piecewise linear function - * Used for \fad, \fade implementation. + * \brief Mapping between script and screen coordinates */ -static unsigned interpolate_alpha(long long now, - long long t1, long long t2, long long t3, long long t4, - unsigned a1, unsigned a2, unsigned a3) +static double y2scr(ASS_Renderer *render_priv, double y) +{ + return y * render_priv->orig_height_nocrop / + render_priv->track->PlayResY + + FFMAX(render_priv->settings.top_margin, 0); +} +static double y2scr_pos(ASS_Renderer *render_priv, double y) { - unsigned a; - double cf; - if (now <= t1) { - a = a1; - } else if (now >= t4) { - a = a3; - } else if (now < t2) { // and > t1 - cf = ((double)(now - t1)) / (t2 - t1); - a = a1 * (1 - cf) + a2 * cf; - } else if (now > t3) { - cf = ((double)(now - t3)) / (t4 - t3); - a = a2 * (1 - cf) + a3 * cf; - } else { // t2 <= now <= t3 - a = a2; - } + return y * render_priv->orig_height / render_priv->track->PlayResY + + render_priv->settings.top_margin; +} - return a; +// the same for toptitles +static double y2scr_top(ASS_Renderer *render_priv, double y) +{ + if (render_priv->settings.use_margins) + return y * render_priv->orig_height_nocrop / + render_priv->track->PlayResY; + else + return y * render_priv->orig_height_nocrop / + render_priv->track->PlayResY + + FFMAX(render_priv->settings.top_margin, 0); } -static void reset_render_context(void); - -/** - * \brief Parse style override tag. - * \param p string to parse - * \param pwr multiplier for some tag effects (comes from \t tags) - */ -static char* parse_tag(char* p, double pwr) { -#define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;} -#define skip(x) if (*p == (x)) ++p; else { return p; } - - skip_to('\\'); - skip('\\'); - if ((*p == '}') || (*p == 0)) - return p; - - // New tags introduced in vsfilter 2.39 - if (mystrcmp(&p, "xbord")) { - double val; - if (mystrtod(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\xbord%.2f\n", val); - } else if (mystrcmp(&p, "ybord")) { - double val; - if (mystrtod(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\ybord%.2f\n", val); - } else if (mystrcmp(&p, "xshad")) { - int val; - if (mystrtoi(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\xshad%d\n", val); - } else if (mystrcmp(&p, "yshad")) { - int val; - if (mystrtoi(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\yshad%d\n", val); - } else if (mystrcmp(&p, "fax")) { - int val; - if (mystrtoi(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\fax%d\n", val); - } else if (mystrcmp(&p, "fay")) { - int val; - if (mystrtoi(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\fay%d\n", val); - } else if (mystrcmp(&p, "iclip")) { - int x0, y0, x1, y1; - int res = 1; - skip('('); - res &= mystrtoi(&p, &x0); - skip(','); - res &= mystrtoi(&p, &y0); - skip(','); - res &= mystrtoi(&p, &x1); - skip(','); - res &= mystrtoi(&p, &y1); - skip(')'); - mp_msg(MSGT_ASS, MSGL_V, "stub: \\iclip(%d,%d,%d,%d)\n", x0, y0, x1, y1); - } else if (mystrcmp(&p, "blur")) { - double val; - if (mystrtod(&p, &val)) { - val = (val < 0) ? 0 : val; - val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val; - render_context.blur = val; - } else - render_context.blur = 0.0; - // ASS standard tags - } else if (mystrcmp(&p, "fsc")) { - char tp = *p++; - double val; - if (tp == 'x') { - if (mystrtod(&p, &val)) { - val /= 100; - render_context.scale_x = render_context.scale_x * ( 1 - pwr) + val * pwr; - } else - render_context.scale_x = render_context.style->ScaleX; - } else if (tp == 'y') { - if (mystrtod(&p, &val)) { - val /= 100; - render_context.scale_y = render_context.scale_y * ( 1 - pwr) + val * pwr; - } else - render_context.scale_y = render_context.style->ScaleY; - } - } else if (mystrcmp(&p, "fsp")) { - double val; - if (mystrtod(&p, &val)) - render_context.hspacing = render_context.hspacing * ( 1 - pwr ) + val * pwr; - else - render_context.hspacing = render_context.style->Spacing; - } else if (mystrcmp(&p, "fs")) { - double val; - if (mystrtod(&p, &val)) - val = render_context.font_size * ( 1 - pwr ) + val * pwr; - else - val = render_context.style->FontSize; - if (render_context.font) - change_font_size(val); - } else if (mystrcmp(&p, "bord")) { - double val; - if (mystrtod(&p, &val)) - val = render_context.border * ( 1 - pwr ) + val * pwr; - else - val = -1.; // reset to default - change_border(val); - } else if (mystrcmp(&p, "move")) { - double x1, x2, y1, y2; - long long t1, t2, delta_t, t; - double x, y; - double k; - skip('('); - mystrtod(&p, &x1); - skip(','); - mystrtod(&p, &y1); - skip(','); - mystrtod(&p, &x2); - skip(','); - mystrtod(&p, &y2); - if (*p == ',') { - skip(','); - mystrtoll(&p, &t1); - skip(','); - mystrtoll(&p, &t2); - mp_msg(MSGT_ASS, MSGL_DBG2, "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %" PRId64 ")\n", - x1, y1, x2, y2, (int64_t)t1, (int64_t)t2); - } else { - t1 = 0; - t2 = render_context.event->Duration; - mp_msg(MSGT_ASS, MSGL_DBG2, "movement: (%f, %f) -> (%f, %f)\n", x1, y1, x2, y2); - } - skip(')'); - delta_t = t2 - t1; - t = frame_context.time - render_context.event->Start; - if (t < t1) - k = 0.; - else if (t > t2) - k = 1.; - else k = ((double)(t - t1)) / delta_t; - x = k * (x2 - x1) + x1; - y = k * (y2 - y1) + y1; - if (render_context.evt_type != EVENT_POSITIONED) { - render_context.pos_x = x; - render_context.pos_y = y; - render_context.detect_collisions = 0; - render_context.evt_type = EVENT_POSITIONED; - } - } else if (mystrcmp(&p, "frx")) { - double val; - if (mystrtod(&p, &val)) { - val *= M_PI / 180; - render_context.frx = val * pwr + render_context.frx * (1-pwr); - } else - render_context.frx = 0.; - } else if (mystrcmp(&p, "fry")) { - double val; - if (mystrtod(&p, &val)) { - val *= M_PI / 180; - render_context.fry = val * pwr + render_context.fry * (1-pwr); - } else - render_context.fry = 0.; - } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) { - double val; - if (mystrtod(&p, &val)) { - val *= M_PI / 180; - render_context.frz = val * pwr + render_context.frz * (1-pwr); - } else - render_context.frz = M_PI * render_context.style->Angle / 180.; - } else if (mystrcmp(&p, "fn")) { - char* start = p; - char* family; - skip_to('\\'); - if (p > start) { - family = malloc(p - start + 1); - strncpy(family, start, p - start); - family[p - start] = '\0'; - } else - family = strdup(render_context.style->FontName); - if (render_context.family) - free(render_context.family); - render_context.family = family; - update_font(); - } else if (mystrcmp(&p, "alpha")) { - uint32_t val; - int i; - if (strtocolor(&p, &val)) { - unsigned char a = val >> 24; - for (i = 0; i < 4; ++i) - change_alpha(&render_context.c[i], a, pwr); - } else { - change_alpha(&render_context.c[0], render_context.style->PrimaryColour, pwr); - change_alpha(&render_context.c[1], render_context.style->SecondaryColour, pwr); - change_alpha(&render_context.c[2], render_context.style->OutlineColour, pwr); - change_alpha(&render_context.c[3], render_context.style->BackColour, pwr); - } - // FIXME: simplify - } else if (mystrcmp(&p, "an")) { - int val; - if (mystrtoi(&p, &val) && val) { - int v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment - mp_msg(MSGT_ASS, MSGL_DBG2, "an %d\n", val); - if (v != 0) v = 3 - v; - val = ((val - 1) % 3) + 1; // horizontal alignment - val += v*4; - mp_msg(MSGT_ASS, MSGL_DBG2, "align %d\n", val); - render_context.alignment = val; - } else - render_context.alignment = render_context.style->Alignment; - } else if (mystrcmp(&p, "a")) { - int val; - if (mystrtoi(&p, &val) && val) - render_context.alignment = val; - else - render_context.alignment = render_context.style->Alignment; - } else if (mystrcmp(&p, "pos")) { - double v1, v2; - skip('('); - mystrtod(&p, &v1); - skip(','); - mystrtod(&p, &v2); - skip(')'); - mp_msg(MSGT_ASS, MSGL_DBG2, "pos(%f, %f)\n", v1, v2); - if (render_context.evt_type != EVENT_POSITIONED) { - render_context.evt_type = EVENT_POSITIONED; - render_context.detect_collisions = 0; - render_context.pos_x = v1; - render_context.pos_y = v2; - } - } else if (mystrcmp(&p, "fad")) { - int a1, a2, a3; - long long t1, t2, t3, t4; - if (*p == 'e') ++p; // either \fad or \fade - skip('('); - mystrtoi(&p, &a1); - skip(','); - mystrtoi(&p, &a2); - if (*p == ')') { - // 2-argument version (\fad, according to specs) - // a1 and a2 are fade-in and fade-out durations - t1 = 0; - t4 = render_context.event->Duration; - t2 = a1; - t3 = t4 - a2; - a1 = 0xFF; - a2 = 0; - a3 = 0xFF; - } else { - // 6-argument version (\fade) - // a1 and a2 (and a3) are opacity values - skip(','); - mystrtoi(&p, &a3); - skip(','); - mystrtoll(&p, &t1); - skip(','); - mystrtoll(&p, &t2); - skip(','); - mystrtoll(&p, &t3); - skip(','); - mystrtoll(&p, &t4); - } - skip(')'); - render_context.fade = interpolate_alpha(frame_context.time - render_context.event->Start, t1, t2, t3, t4, a1, a2, a3); - } else if (mystrcmp(&p, "org")) { - int v1, v2; - skip('('); - mystrtoi(&p, &v1); - skip(','); - mystrtoi(&p, &v2); - skip(')'); - mp_msg(MSGT_ASS, MSGL_DBG2, "org(%d, %d)\n", v1, v2); - // render_context.evt_type = EVENT_POSITIONED; - if (!render_context.have_origin) { - render_context.org_x = v1; - render_context.org_y = v2; - render_context.have_origin = 1; - render_context.detect_collisions = 0; - } - } else if (mystrcmp(&p, "t")) { - double v[3]; - int v1, v2; - double v3; - int cnt; - long long t1, t2, t, delta_t; - double k; - skip('('); - for (cnt = 0; cnt < 3; ++cnt) { - if (*p == '\\') - break; - v[cnt] = strtod(p, &p); - skip(','); - } - if (cnt == 3) { - v1 = v[0]; v2 = v[1]; v3 = v[2]; - } else if (cnt == 2) { - v1 = v[0]; v2 = v[1]; v3 = 1.; - } else if (cnt == 1) { - v1 = 0; v2 = render_context.event->Duration; v3 = v[0]; - } else { // cnt == 0 - v1 = 0; v2 = render_context.event->Duration; v3 = 1.; - } - render_context.detect_collisions = 0; - t1 = v1; - t2 = v2; - delta_t = v2 - v1; - if (v3 < 0.) - v3 = 0.; - t = frame_context.time - render_context.event->Start; // FIXME: move to render_context - if (t <= t1) - k = 0.; - else if (t >= t2) - k = 1.; - else { - assert(delta_t != 0.); - k = pow(((double)(t - t1)) / delta_t, v3); - } - while (*p == '\\') - p = parse_tag(p, k); // maybe k*pwr ? no, specs forbid nested \t's - skip_to(')'); // in case there is some unknown tag or a comment - skip(')'); - } else if (mystrcmp(&p, "clip")) { - int x0, y0, x1, y1; - int res = 1; - skip('('); - res &= mystrtoi(&p, &x0); - skip(','); - res &= mystrtoi(&p, &y0); - skip(','); - res &= mystrtoi(&p, &x1); - skip(','); - res &= mystrtoi(&p, &y1); - skip(')'); - if (res) { - render_context.clip_x0 = render_context.clip_x0 * (1-pwr) + x0 * pwr; - render_context.clip_x1 = render_context.clip_x1 * (1-pwr) + x1 * pwr; - render_context.clip_y0 = render_context.clip_y0 * (1-pwr) + y0 * pwr; - render_context.clip_y1 = render_context.clip_y1 * (1-pwr) + y1 * pwr; - } else { - render_context.clip_x0 = 0; - render_context.clip_y0 = 0; - render_context.clip_x1 = frame_context.track->PlayResX; - render_context.clip_y1 = frame_context.track->PlayResY; - } - } else if (mystrcmp(&p, "c")) { - uint32_t val; - if (!strtocolor(&p, &val)) - val = render_context.style->PrimaryColour; - mp_msg(MSGT_ASS, MSGL_DBG2, "color: %X\n", val); - change_color(&render_context.c[0], val, pwr); - } else if ((*p >= '1') && (*p <= '4') && (++p) && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) { - char n = *(p-2); - int cidx = n - '1'; - char cmd = *(p-1); - uint32_t val; - assert((n >= '1') && (n <= '4')); - if (!strtocolor(&p, &val)) - switch(n) { - case '1': val = render_context.style->PrimaryColour; break; - case '2': val = render_context.style->SecondaryColour; break; - case '3': val = render_context.style->OutlineColour; break; - case '4': val = render_context.style->BackColour; break; - default : val = 0; break; // impossible due to assert; avoid compilation warning - } - switch (cmd) { - case 'c': change_color(render_context.c + cidx, val, pwr); break; - case 'a': change_alpha(render_context.c + cidx, val >> 24, pwr); break; - default: mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadCommand, n, cmd); break; - } - mp_msg(MSGT_ASS, MSGL_DBG2, "single c/a at %f: %c%c = %X \n", pwr, n, cmd, render_context.c[cidx]); - } else if (mystrcmp(&p, "r")) { - reset_render_context(); - } else if (mystrcmp(&p, "be")) { - int val; - if (mystrtoi(&p, &val)) { - // Clamp to a safe upper limit, since high values need excessive CPU - val = (val < 0) ? 0 : val; - val = (val > MAX_BE) ? MAX_BE : val; - render_context.be = val; - } else - render_context.be = 0; - } else if (mystrcmp(&p, "b")) { - int b; - if (mystrtoi(&p, &b)) { - if (pwr >= .5) - render_context.bold = b; - } else - render_context.bold = render_context.style->Bold; - update_font(); - } else if (mystrcmp(&p, "i")) { - int i; - if (mystrtoi(&p, &i)) { - if (pwr >= .5) - render_context.italic = i; - } else - render_context.italic = render_context.style->Italic; - update_font(); - } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) { - int val = 0; - mystrtoi(&p, &val); - render_context.effect_type = EF_KARAOKE_KF; - if (render_context.effect_timing) - render_context.effect_skip_timing += render_context.effect_timing; - render_context.effect_timing = val * 10; - } else if (mystrcmp(&p, "ko")) { - int val = 0; - mystrtoi(&p, &val); - render_context.effect_type = EF_KARAOKE_KO; - if (render_context.effect_timing) - render_context.effect_skip_timing += render_context.effect_timing; - render_context.effect_timing = val * 10; - } else if (mystrcmp(&p, "k")) { - int val = 0; - mystrtoi(&p, &val); - render_context.effect_type = EF_KARAOKE; - if (render_context.effect_timing) - render_context.effect_skip_timing += render_context.effect_timing; - render_context.effect_timing = val * 10; - } else if (mystrcmp(&p, "shad")) { - int val; - if (mystrtoi(&p, &val)) - render_context.shadow = val; - else - render_context.shadow = render_context.style->Shadow; - } else if (mystrcmp(&p, "pbo")) { - int val = 0; - mystrtoi(&p, &val); // ignored - } else if (mystrcmp(&p, "p")) { - int val; - if (!mystrtoi(&p, &val)) - val = 0; - render_context.drawing_mode = !!val; - } - - return p; - -#undef skip -#undef skip_to +// the same for subtitles +static double y2scr_sub(ASS_Renderer *render_priv, double y) +{ + if (render_priv->settings.use_margins) + return y * render_priv->orig_height_nocrop / + render_priv->track->PlayResY + + FFMAX(render_priv->settings.top_margin, + 0) + FFMAX(render_priv->settings.bottom_margin, 0); + else + return y * render_priv->orig_height_nocrop / + render_priv->track->PlayResY + + FFMAX(render_priv->settings.top_margin, 0); } -/** - * \brief Get next ucs4 char from string, parsing and executing style overrides - * \param str string pointer - * \return ucs4 code of the next char - * On return str points to the unparsed part of the string - */ -static unsigned get_next_char(char** str) +static void compute_string_bbox(TextInfo *info, DBBox *bbox) { - char* p = *str; - unsigned chr; - if (*p == '{') { // '\0' goes here - p++; - while (1) { - p = parse_tag(p, 1.); - if (*p == '}') { // end of tag - p++; - if (*p == '{') { - p++; - continue; - } else - break; - } else if (*p != '\\') - mp_msg(MSGT_ASS, MSGL_V, "Unable to parse: \"%s\" \n", p); - if (*p == 0) - break; - } - } - if (*p == '\t') { - ++p; - *str = p; - return ' '; - } - if (*p == '\\') { - if ((*(p+1) == 'N') || ((*(p+1) == 'n') && (frame_context.track->WrapStyle == 2))) { - p += 2; - *str = p; - return '\n'; - } else if ((*(p+1) == 'n') || (*(p+1) == 'h')) { - p += 2; - *str = p; - return ' '; - } - } - chr = utf8_get_char((const char **)&p); - *str = p; - return chr; -} - -static void apply_transition_effects(ass_event_t* event) -{ - int v[4]; - int cnt; - char* p = event->Effect; - - if (!p || !*p) return; + int i; - cnt = 0; - while (cnt < 4 && (p = strchr(p, ';'))) { - v[cnt++] = atoi(++p); - } - - if (strncmp(event->Effect, "Banner;", 7) == 0) { - int delay; - if (cnt < 1) { - mp_msg(MSGT_ASS, MSGL_V, "Error parsing effect: %s \n", event->Effect); - return; - } - if (cnt >= 2 && v[1] == 0) // right-to-left - render_context.scroll_direction = SCROLL_RL; - else // left-to-right - render_context.scroll_direction = SCROLL_LR; - - delay = v[0]; - if (delay == 0) delay = 1; // ? - render_context.scroll_shift = (frame_context.time - render_context.event->Start) / delay; - render_context.evt_type = EVENT_HSCROLL; - return; - } + if (info->length > 0) { + bbox->xMin = 32000; + bbox->xMax = -32000; + bbox->yMin = -1 * info->lines[0].asc + d6_to_double(info->glyphs[0].pos.y); + bbox->yMax = info->height - info->lines[0].asc + + d6_to_double(info->glyphs[0].pos.y); - if (strncmp(event->Effect, "Scroll up;", 10) == 0) { - render_context.scroll_direction = SCROLL_BT; - } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) { - render_context.scroll_direction = SCROLL_TB; - } else { - mp_msg(MSGT_ASS, MSGL_V, "Unknown transition effect: %s \n", event->Effect); - return; - } - // parse scroll up/down parameters - { - int delay; - int y0, y1; - if (cnt < 3) { - mp_msg(MSGT_ASS, MSGL_V, "Error parsing effect: %s \n", event->Effect); - return; - } - delay = v[2]; - if (delay == 0) delay = 1; // ? - render_context.scroll_shift = (frame_context.time - render_context.event->Start) / delay; - if (v[0] < v[1]) { - y0 = v[0]; y1 = v[1]; - } else { - y0 = v[1]; y1 = v[0]; - } - if (y1 == 0) - y1 = frame_context.track->PlayResY; // y0=y1=0 means fullscreen scrolling - render_context.clip_y0 = y0; - render_context.clip_y1 = y1; - render_context.evt_type = EVENT_VSCROLL; - render_context.detect_collisions = 0; - } - + for (i = 0; i < info->length; ++i) { + if (info->glyphs[i].skip) continue; + double s = d6_to_double(info->glyphs[i].pos.x); + double e = s + d6_to_double(info->glyphs[i].advance.x); + bbox->xMin = FFMIN(bbox->xMin, s); + bbox->xMax = FFMAX(bbox->xMax, e); + } + } else + bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.; } /** * \brief partially reset render_context to style values * Works like {\r}: resets some style overrides */ -static void reset_render_context(void) +void reset_render_context(ASS_Renderer *render_priv) { - render_context.c[0] = render_context.style->PrimaryColour; - render_context.c[1] = render_context.style->SecondaryColour; - render_context.c[2] = render_context.style->OutlineColour; - render_context.c[3] = render_context.style->BackColour; - render_context.font_size = render_context.style->FontSize; + render_priv->state.c[0] = render_priv->state.style->PrimaryColour; + render_priv->state.c[1] = render_priv->state.style->SecondaryColour; + render_priv->state.c[2] = render_priv->state.style->OutlineColour; + render_priv->state.c[3] = render_priv->state.style->BackColour; + render_priv->state.flags = + (render_priv->state.style->Underline ? DECO_UNDERLINE : 0) | + (render_priv->state.style->StrikeOut ? DECO_STRIKETHROUGH : 0); + render_priv->state.font_size = render_priv->state.style->FontSize; + + free(render_priv->state.family); + render_priv->state.family = NULL; + render_priv->state.family = strdup(render_priv->state.style->FontName); + render_priv->state.treat_family_as_pattern = + render_priv->state.style->treat_fontname_as_pattern; + render_priv->state.bold = render_priv->state.style->Bold; + render_priv->state.italic = render_priv->state.style->Italic; + update_font(render_priv); + + change_border(render_priv, -1., -1.); + render_priv->state.scale_x = render_priv->state.style->ScaleX; + render_priv->state.scale_y = render_priv->state.style->ScaleY; + render_priv->state.hspacing = render_priv->state.style->Spacing; + render_priv->state.be = 0; + render_priv->state.blur = 0.0; + render_priv->state.shadow_x = render_priv->state.style->Shadow; + render_priv->state.shadow_y = render_priv->state.style->Shadow; + render_priv->state.frx = render_priv->state.fry = 0.; + render_priv->state.frz = M_PI * render_priv->state.style->Angle / 180.; + render_priv->state.fax = render_priv->state.fay = 0.; + render_priv->state.wrap_style = render_priv->track->WrapStyle; + + // FIXME: does not reset unsupported attributes. +} + +/** + * \brief Start new event. Reset render_priv->state. + */ +static void +init_render_context(ASS_Renderer *render_priv, ASS_Event *event) +{ + render_priv->state.event = event; + render_priv->state.style = render_priv->track->styles + event->Style; + + reset_render_context(render_priv); - if (render_context.family) - free(render_context.family); - render_context.family = strdup(render_context.style->FontName); - render_context.treat_family_as_pattern = render_context.style->treat_fontname_as_pattern; - render_context.bold = render_context.style->Bold; - render_context.italic = render_context.style->Italic; - update_font(); + render_priv->state.evt_type = EVENT_NORMAL; + render_priv->state.alignment = render_priv->state.style->Alignment; + render_priv->state.pos_x = 0; + render_priv->state.pos_y = 0; + render_priv->state.org_x = 0; + render_priv->state.org_y = 0; + render_priv->state.have_origin = 0; + render_priv->state.clip_x0 = 0; + render_priv->state.clip_y0 = 0; + render_priv->state.clip_x1 = render_priv->track->PlayResX; + render_priv->state.clip_y1 = render_priv->track->PlayResY; + render_priv->state.clip_mode = 0; + render_priv->state.detect_collisions = 1; + render_priv->state.fade = 0; + render_priv->state.drawing_mode = 0; + render_priv->state.effect_type = EF_NONE; + render_priv->state.effect_timing = 0; + render_priv->state.effect_skip_timing = 0; + render_priv->state.drawing = + ass_drawing_new(render_priv->fontconfig_priv, + render_priv->state.font, + render_priv->settings.hinting, + render_priv->ftlibrary); - change_border(-1.); - render_context.scale_x = render_context.style->ScaleX; - render_context.scale_y = render_context.style->ScaleY; - render_context.hspacing = render_context.style->Spacing; - render_context.be = 0; - render_context.blur = 0.0; - render_context.shadow = render_context.style->Shadow; - render_context.frx = render_context.fry = 0.; - render_context.frz = M_PI * render_context.style->Angle / 180.; + apply_transition_effects(render_priv, event); +} + +static void free_render_context(ASS_Renderer *render_priv) +{ + free(render_priv->state.family); + ass_drawing_free(render_priv->state.drawing); + + render_priv->state.family = NULL; + render_priv->state.drawing = NULL; +} - // FIXME: does not reset unsupported attributes. +// Calculate the cbox of a series of points +static void +get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end) +{ + box->xMin = box->yMin = INT_MAX; + box->xMax = box->yMax = INT_MIN; + int i; + + for (i = start; i < end; i++) { + box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin; + box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax; + box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin; + box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax; + } } /** - * \brief Start new event. Reset render_context. - */ -static void init_render_context(ass_event_t* event) + * \brief Fix-up stroker result for huge borders by removing the contours from + * the outline that are harmful. +*/ +static void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x, + int border_y) { - render_context.event = event; - render_context.style = frame_context.track->styles + event->Style; + int nc = glyph->outline.n_contours; + int begin, stop; + char modified = 0; + char *valid_cont; + int start = 0; + int end = -1; + FT_BBox *boxes = calloc(nc, sizeof(FT_BBox)); + int i, j; - reset_render_context(); + // Create a list of cboxes of the contours + for (i = 0; i < nc; i++) { + start = end + 1; + end = glyph->outline.contours[i]; + get_contour_cbox(&boxes[i], glyph->outline.points, start, end); + } - render_context.evt_type = EVENT_NORMAL; - render_context.alignment = render_context.style->Alignment; - render_context.pos_x = 0; - render_context.pos_y = 0; - render_context.org_x = 0; - render_context.org_y = 0; - render_context.have_origin = 0; - render_context.clip_x0 = 0; - render_context.clip_y0 = 0; - render_context.clip_x1 = frame_context.track->PlayResX; - render_context.clip_y1 = frame_context.track->PlayResY; - render_context.detect_collisions = 1; - render_context.fade = 0; - render_context.drawing_mode = 0; - render_context.effect_type = EF_NONE; - render_context.effect_timing = 0; - render_context.effect_skip_timing = 0; + // if a) contour's cbox is contained in another contours cbox + // b) contour's height or width is smaller than the border*2 + // the contour can be safely removed. + valid_cont = calloc(1, nc); + for (i = 0; i < nc; i++) { + valid_cont[i] = 1; + for (j = 0; j < nc; j++) { + if (i == j) + continue; + if (boxes[i].xMin >= boxes[j].xMin && + boxes[i].xMax <= boxes[j].xMax && + boxes[i].yMin >= boxes[j].yMin && + boxes[i].yMax <= boxes[j].yMax) { + int width = boxes[i].xMax - boxes[i].xMin; + int height = boxes[i].yMax - boxes[i].yMin; + if (width < border_x * 2 || height < border_y * 2) { + valid_cont[i] = 0; + modified = 1; + break; + } + } + } + } - apply_transition_effects(event); + // Zero-out contours that can be removed; much simpler than copying + if (modified) { + for (i = 0; i < nc; i++) { + if (valid_cont[i]) + continue; + begin = (i == 0) ? 0 : glyph->outline.contours[i - 1] + 1; + stop = glyph->outline.contours[i]; + for (j = begin; j <= stop; j++) { + glyph->outline.points[j].x = 0; + glyph->outline.points[j].y = 0; + glyph->outline.tags[j] = 0; + } + } + } + + free(boxes); + free(valid_cont); } -static void free_render_context(void) +/* + * Replace the outline of a glyph by a contour which makes up a simple + * opaque rectangle. + */ +static void draw_opaque_box(ASS_Renderer *render_priv, uint32_t ch, + FT_Glyph glyph, int sx, int sy) { + int asc = 0, desc = 0; + int i; + int adv = d16_to_d6(glyph->advance.x); + double scale_y = render_priv->state.scale_y; + double scale_x = render_priv->state.scale_x + * render_priv->font_scale_x; + FT_OutlineGlyph og = (FT_OutlineGlyph) glyph; + FT_Outline *ol; + + // to avoid gaps + sx = FFMAX(64, sx); + sy = FFMAX(64, sy); + + if (ch == -1) { + asc = render_priv->state.drawing->asc; + desc = render_priv->state.drawing->desc; + } else { + ass_font_get_asc_desc(render_priv->state.font, ch, &asc, &desc); + asc *= scale_y; + desc *= scale_y; + } + + // Emulate the WTFish behavior of VSFilter, i.e. double-scale + // the sizes of the opaque box. + adv += double_to_d6(render_priv->state.hspacing * render_priv->font_scale + * scale_x); + adv *= scale_x; + sx *= scale_x; + sy *= scale_y; + desc *= scale_y; + desc += asc * (scale_y - 1.0); + + FT_Vector points[4] = { + { .x = -sx, .y = asc + sy }, + { .x = adv + sx, .y = asc + sy }, + { .x = adv + sx, .y = -desc - sy }, + { .x = -sx, .y = -desc - sy }, + }; + + FT_Outline_Done(render_priv->ftlibrary, &og->outline); + FT_Outline_New(render_priv->ftlibrary, 4, 1, &og->outline); + + ol = &og->outline; + ol->n_points = ol->n_contours = 0; + for (i = 0; i < 4; i++) { + ol->points[ol->n_points] = points[i]; + ol->tags[ol->n_points++] = 1; + } + ol->contours[ol->n_contours++] = ol->n_points - 1; +} + +/* + * Stroke an outline glyph in x/y direction. Applies various fixups to get + * around limitations of the FreeType stroker. + */ +static void stroke_outline_glyph(ASS_Renderer *render_priv, + FT_OutlineGlyph *glyph, int sx, int sy) +{ + if (sx <= 0 && sy <= 0) + return; + + fix_freetype_stroker(*glyph, sx, sy); + + // Borders are equal; use the regular stroker + if (sx == sy && render_priv->state.stroker) { + int error; + error = FT_Glyph_StrokeBorder((FT_Glyph *) glyph, + render_priv->state.stroker, 0, 1); + if (error) + ass_msg(render_priv->library, MSGL_WARN, + "FT_Glyph_Stroke error: %d", error); + + // "Stroke" with the outline emboldener in two passes. + // The outlines look uglier, but the emboldening never adds any points + } else { + int i; + FT_Outline *ol = &(*glyph)->outline; + FT_Outline nol; + FT_Outline_New(render_priv->ftlibrary, ol->n_points, + ol->n_contours, &nol); + FT_Outline_Copy(ol, &nol); + + FT_Outline_Embolden(ol, sx * 2); + FT_Outline_Translate(ol, -sx, -sx); + FT_Outline_Embolden(&nol, sy * 2); + FT_Outline_Translate(&nol, -sy, -sy); + + for (i = 0; i < ol->n_points; i++) + ol->points[i].y = nol.points[i].y; + + FT_Outline_Done(render_priv->ftlibrary, &nol); + } } /** * \brief Get normal and outline (border) glyphs * \param symbol ucs4 char * \param info out: struct filled with extracted data - * \param advance subpixel shift vector used for cache lookup * Tries to get both glyphs from cache. * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker, * and add them to cache. * The glyphs are returned in info->glyph and info->outline_glyph */ -static void get_outline_glyph(int symbol, glyph_info_t* info, FT_Vector* advance) +static void +get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info, + ASS_Drawing *drawing) { - int error; - glyph_hash_val_t* val; - glyph_hash_key_t key; - memset(&key, 0, sizeof(key)); - key.font = render_context.font; - key.size = render_context.font_size; - key.ch = symbol; - key.scale_x = (render_context.scale_x * 0xFFFF); - key.scale_y = (render_context.scale_y * 0xFFFF); - key.advance = *advance; - key.bold = render_context.bold; - key.italic = render_context.italic; - key.outline = render_context.border * 0xFFFF; + GlyphHashValue *val; + GlyphHashKey key; + memset(&key, 0, sizeof(key)); - memset(info, 0, sizeof(glyph_info_t)); + if (drawing->hash) { + key.scale_x = double_to_d16(render_priv->state.scale_x); + key.scale_y = double_to_d16(render_priv->state.scale_y); + key.outline.x = render_priv->state.border_x * 0xFFFF; + key.outline.y = render_priv->state.border_y * 0xFFFF; + key.border_style = render_priv->state.style->BorderStyle; + key.drawing_hash = drawing->hash; + } else { + key.font = render_priv->state.font; + key.size = render_priv->state.font_size; + key.ch = symbol; + key.bold = render_priv->state.bold; + key.italic = render_priv->state.italic; + key.scale_x = double_to_d16(render_priv->state.scale_x); + key.scale_y = double_to_d16(render_priv->state.scale_y); + key.outline.x = render_priv->state.border_x * 0xFFFF; + key.outline.y = render_priv->state.border_y * 0xFFFF; + key.flags = render_priv->state.flags; + key.border_style = render_priv->state.style->BorderStyle; + } + memset(info, 0, sizeof(GlyphInfo)); - val = cache_find_glyph(&key); - if (val) { - FT_Glyph_Copy(val->glyph, &info->glyph); - if (val->outline_glyph) - FT_Glyph_Copy(val->outline_glyph, &info->outline_glyph); - info->bbox = val->bbox_scaled; - info->advance.x = val->advance.x; - info->advance.y = val->advance.y; - } else { - glyph_hash_val_t v; - info->glyph = ass_font_get_glyph(frame_context.ass_priv->fontconfig_priv, render_context.font, symbol, global_settings->hinting); - if (!info->glyph) - return; - info->advance.x = d16_to_d6(info->glyph->advance.x); - info->advance.y = d16_to_d6(info->glyph->advance.y); - FT_Glyph_Get_CBox( info->glyph, FT_GLYPH_BBOX_PIXELS, &info->bbox); + val = cache_find_glyph(render_priv->cache.glyph_cache, &key); + if (val) { + FT_Glyph_Copy(val->glyph, &info->glyph); + if (val->outline_glyph) + FT_Glyph_Copy(val->outline_glyph, &info->outline_glyph); + info->bbox = val->bbox_scaled; + info->advance.x = val->advance.x; + info->advance.y = val->advance.y; + if (drawing->hash) { + drawing->asc = val->asc; + drawing->desc = val->desc; + } + } else { + GlyphHashValue v; + if (drawing->hash) { + if(!ass_drawing_parse(drawing, 0)) + return; + FT_Glyph_Copy((FT_Glyph) drawing->glyph, &info->glyph); + } else { + info->glyph = + ass_font_get_glyph(render_priv->fontconfig_priv, + render_priv->state.font, symbol, + render_priv->settings.hinting, + render_priv->state.flags); + } + if (!info->glyph) + return; + info->advance.x = d16_to_d6(info->glyph->advance.x); + info->advance.y = d16_to_d6(info->glyph->advance.y); + FT_Glyph_Get_CBox(info->glyph, FT_GLYPH_BBOX_SUBPIXELS, &info->bbox); - if (render_context.stroker) { - info->outline_glyph = info->glyph; - error = FT_Glyph_StrokeBorder( &(info->outline_glyph), render_context.stroker, 0 , 0 ); // don't destroy original - if (error) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FT_Glyph_Stroke_Error, error); - } - } + if (render_priv->state.style->BorderStyle == 3 && + (render_priv->state.border_x > 0|| + render_priv->state.border_y > 0)) { + FT_Glyph_Copy(info->glyph, &info->outline_glyph); + draw_opaque_box(render_priv, symbol, info->outline_glyph, + double_to_d6(render_priv->state.border_x * + render_priv->border_scale), + double_to_d6(render_priv->state.border_y * + render_priv->border_scale)); + } else if (render_priv->state.border_x > 0 || + render_priv->state.border_y > 0) { - memset(&v, 0, sizeof(v)); - FT_Glyph_Copy(info->glyph, &v.glyph); - if (info->outline_glyph) - FT_Glyph_Copy(info->outline_glyph, &v.outline_glyph); - v.advance = info->advance; - v.bbox_scaled = info->bbox; - cache_add_glyph(&key, &v); - } + FT_Glyph_Copy(info->glyph, &info->outline_glyph); + stroke_outline_glyph(render_priv, + (FT_OutlineGlyph *) &info->outline_glyph, + double_to_d6(render_priv->state.border_x * + render_priv->border_scale), + double_to_d6(render_priv->state.border_y * + render_priv->border_scale)); + } + + memset(&v, 0, sizeof(v)); + FT_Glyph_Copy(info->glyph, &v.glyph); + if (info->outline_glyph) + FT_Glyph_Copy(info->outline_glyph, &v.outline_glyph); + v.advance = info->advance; + v.bbox_scaled = info->bbox; + if (drawing->hash) { + v.asc = drawing->asc; + v.desc = drawing->desc; + } + cache_add_glyph(render_priv->cache.glyph_cache, &key, &v); + } } -static void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz); +static void transform_3d(FT_Vector shift, FT_Glyph *glyph, + FT_Glyph *glyph2, double frx, double fry, + double frz, double fax, double fay, double scale, + int yshift); /** * \brief Get bitmaps for a glyph @@ -1504,50 +1197,74 @@ * After that, bitmaps are added to the cache. * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow). */ -static void get_bitmap_glyph(glyph_info_t* info) +static void +get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info) { - bitmap_hash_val_t* val; - bitmap_hash_key_t* key = &info->hash_key; + BitmapHashValue *val; + BitmapHashKey *key = &info->hash_key; + + val = cache_find_bitmap(render_priv->cache.bitmap_cache, key); - val = cache_find_bitmap(key); -/* val = 0; */ + if (val) { + info->bm = val->bm; + info->bm_o = val->bm_o; + info->bm_s = val->bm_s; + } else { + FT_Vector shift; + BitmapHashValue hash_val; + int error; + double fax_scaled, fay_scaled; + info->bm = info->bm_o = info->bm_s = 0; + if (info->glyph && info->symbol != '\n' && info->symbol != 0 + && !info->skip) { + // calculating rotation shift vector (from rotation origin to the glyph basepoint) + shift.x = info->hash_key.shift_x; + shift.y = info->hash_key.shift_y; + fax_scaled = info->fax * render_priv->font_scale_x * + render_priv->state.scale_x; + fay_scaled = info->fay * render_priv->state.scale_y; + // apply rotation + transform_3d(shift, &info->glyph, &info->outline_glyph, + info->frx, info->fry, info->frz, fax_scaled, + fay_scaled, render_priv->font_scale, info->asc); - if (val) { - info->bm = val->bm; - info->bm_o = val->bm_o; - info->bm_s = val->bm_s; - } else { - FT_Vector shift; - bitmap_hash_val_t hash_val; - int error; - info->bm = info->bm_o = info->bm_s = 0; - if (info->glyph && info->symbol != '\n' && info->symbol != 0) { - // calculating rotation shift vector (from rotation origin to the glyph basepoint) - shift.x = int_to_d6(info->hash_key.shift_x); - shift.y = int_to_d6(info->hash_key.shift_y); - // apply rotation - transform_3d(shift, &info->glyph, &info->outline_glyph, info->frx, info->fry, info->frz); + // subpixel shift + if (info->glyph) + FT_Outline_Translate( + &((FT_OutlineGlyph) info->glyph)->outline, + info->hash_key.advance.x, + -info->hash_key.advance.y); + if (info->outline_glyph) + FT_Outline_Translate( + &((FT_OutlineGlyph) info->outline_glyph)->outline, + info->hash_key.advance.x, + -info->hash_key.advance.y); - // render glyph - error = glyph_to_bitmap(ass_renderer->synth_priv, - info->glyph, info->outline_glyph, - &info->bm, &info->bm_o, - &info->bm_s, info->be, info->blur * frame_context.border_scale); - if (error) - info->symbol = 0; + // render glyph + error = glyph_to_bitmap(render_priv->library, + render_priv->synth_priv, + info->glyph, info->outline_glyph, + &info->bm, &info->bm_o, + &info->bm_s, info->be, + info->blur * render_priv->border_scale, + info->hash_key.shadow_offset, + info->hash_key.border_style); + if (error) + info->symbol = 0; - // add bitmaps to cache - hash_val.bm_o = info->bm_o; - hash_val.bm = info->bm; - hash_val.bm_s = info->bm_s; - cache_add_bitmap(&(info->hash_key), &hash_val); - } - } - // deallocate glyphs - if (info->glyph) - FT_Done_Glyph(info->glyph); - if (info->outline_glyph) - FT_Done_Glyph(info->outline_glyph); + // add bitmaps to cache + hash_val.bm_o = info->bm_o; + hash_val.bm = info->bm; + hash_val.bm_s = info->bm_s; + cache_add_bitmap(render_priv->cache.bitmap_cache, + &(info->hash_key), &hash_val); + } + } + // deallocate glyphs + if (info->glyph) + FT_Done_Glyph(info->glyph); + if (info->outline_glyph) + FT_Done_Glyph(info->outline_glyph); } /** @@ -1558,29 +1275,98 @@ * lines[].asc * lines[].desc */ -static void measure_text(void) +static void measure_text(ASS_Renderer *render_priv) +{ + TextInfo *text_info = &render_priv->text_info; + int cur_line = 0; + double max_asc = 0., max_desc = 0.; + GlyphInfo *last = NULL; + int i; + int empty_line = 1; + text_info->height = 0.; + for (i = 0; i < text_info->length + 1; ++i) { + if ((i == text_info->length) || text_info->glyphs[i].linebreak) { + if (empty_line && cur_line > 0 && last && i < text_info->length) { + max_asc = d6_to_double(last->asc) / 2.0; + max_desc = d6_to_double(last->desc) / 2.0; + } + text_info->lines[cur_line].asc = max_asc; + text_info->lines[cur_line].desc = max_desc; + text_info->height += max_asc + max_desc; + cur_line++; + max_asc = max_desc = 0.; + empty_line = 1; + } else + empty_line = 0; + if (i < text_info->length) { + GlyphInfo *cur = text_info->glyphs + i; + if (d6_to_double(cur->asc) > max_asc) + max_asc = d6_to_double(cur->asc); + if (d6_to_double(cur->desc) > max_desc) + max_desc = d6_to_double(cur->desc); + if (cur->symbol != '\n' && cur->symbol != 0) + last = cur; + } + } + text_info->height += + (text_info->n_lines - + 1) * render_priv->settings.line_spacing; +} + +/** + * Mark extra whitespace for later removal. + */ +#define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \ + && !x->linebreak) +static void trim_whitespace(ASS_Renderer *render_priv) { - int cur_line = 0, max_asc = 0, max_desc = 0; - int i; - text_info.height = 0; - for (i = 0; i < text_info.length + 1; ++i) { - if ((i == text_info.length) || text_info.glyphs[i].linebreak) { - text_info.lines[cur_line].asc = max_asc; - text_info.lines[cur_line].desc = max_desc; - text_info.height += max_asc + max_desc; - cur_line ++; - max_asc = max_desc = 0; - } - if (i < text_info.length) { - glyph_info_t* cur = text_info.glyphs + i; - if (cur->asc > max_asc) - max_asc = cur->asc; - if (cur->desc > max_desc) - max_desc = cur->desc; - } - } - text_info.height += (text_info.n_lines - 1) * double_to_d6(global_settings->line_spacing); + int i, j; + GlyphInfo *cur; + TextInfo *ti = &render_priv->text_info; + + // Mark trailing spaces + i = ti->length - 1; + cur = ti->glyphs + i; + while (i && IS_WHITESPACE(cur)) { + cur->skip++; + cur = ti->glyphs + --i; + } + + // Mark leading whitespace + i = 0; + cur = ti->glyphs; + while (i < ti->length && IS_WHITESPACE(cur)) { + cur->skip++; + cur = ti->glyphs + ++i; + } + + // Mark all extraneous whitespace inbetween + for (i = 0; i < ti->length; ++i) { + cur = ti->glyphs + i; + if (cur->linebreak) { + // Mark whitespace before + j = i - 1; + cur = ti->glyphs + j; + while (j && IS_WHITESPACE(cur)) { + cur->skip++; + cur = ti->glyphs + --j; + } + // A break itself can contain a whitespace, too + cur = ti->glyphs + i; + if (cur->symbol == ' ') + cur->skip++; + // Mark whitespace after + j = i + 1; + cur = ti->glyphs + j; + while (j < ti->length && IS_WHITESPACE(cur)) { + cur->skip++; + cur = ti->glyphs + ++j; + } + i = j - 1; + } + } } +#undef IS_WHITESPACE /** * \brief rearrange text between lines @@ -1590,132 +1376,167 @@ * 2. Try moving words from the end of a line to the beginning of the next one while it reduces * the difference in lengths between this two lines. * The result may not be optimal, but usually is good enough. + * + * FIXME: implement style 0 and 3 correctly, add support for style 1 */ -static void wrap_lines_smart(int max_text_width) +static void +wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width) { - int i, j; - glyph_info_t *cur, *s1, *e1, *s2, *s3, *w; - int last_space; - int break_type; - int exit; - int pen_shift_x; - int pen_shift_y; - int cur_line; + int i; + GlyphInfo *cur, *s1, *e1, *s2, *s3, *w; + int last_space; + int break_type; + int exit; + double pen_shift_x; + double pen_shift_y; + int cur_line; + TextInfo *text_info = &render_priv->text_info; - last_space = -1; - text_info.n_lines = 1; - break_type = 0; - s1 = text_info.glyphs; // current line start - for (i = 0; i < text_info.length; ++i) { - int break_at, s_offset, len; - cur = text_info.glyphs + i; - break_at = -1; - s_offset = s1->bbox.xMin + s1->pos.x; - len = (cur->bbox.xMax + cur->pos.x) - s_offset; + last_space = -1; + text_info->n_lines = 1; + break_type = 0; + s1 = text_info->glyphs; // current line start + for (i = 0; i < text_info->length; ++i) { + int break_at; + double s_offset, len; + cur = text_info->glyphs + i; + break_at = -1; + s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x); + len = d6_to_double(cur->bbox.xMax + cur->pos.x) - s_offset; - if (cur->symbol == '\n') { - break_type = 2; - break_at = i; - mp_msg(MSGT_ASS, MSGL_DBG2, "forced line break at %d\n", break_at); - } + if (cur->symbol == '\n') { + break_type = 2; + break_at = i; + ass_msg(render_priv->library, MSGL_DBG2, + "forced line break at %d", break_at); + } - if ((len >= max_text_width) && (frame_context.track->WrapStyle != 2)) { - break_type = 1; - break_at = last_space; - if (break_at == -1) - break_at = i - 1; - if (break_at == -1) - break_at = 0; - mp_msg(MSGT_ASS, MSGL_DBG2, "overfill at %d\n", i); - mp_msg(MSGT_ASS, MSGL_DBG2, "line break at %d\n", break_at); - } + if ((len >= max_text_width) + && (render_priv->state.wrap_style != 2)) { + break_type = 1; + break_at = last_space; + if (break_at == -1) + break_at = i - 1; + if (break_at == -1) + break_at = 0; + ass_msg(render_priv->library, MSGL_DBG2, "overfill at %d", i); + ass_msg(render_priv->library, MSGL_DBG2, "line break at %d", + break_at); + } - if (break_at != -1) { - // need to use one more line - // marking break_at+1 as start of a new line - int lead = break_at + 1; // the first symbol of the new line - if (text_info.n_lines >= MAX_LINES) { - // to many lines ! - // no more linebreaks - for (j = lead; j < text_info.length; ++j) - text_info.glyphs[j].linebreak = 0; - break; - } - if (lead < text_info.length) - text_info.glyphs[lead].linebreak = break_type; - last_space = -1; - s1 = text_info.glyphs + lead; - s_offset = s1->bbox.xMin + s1->pos.x; - text_info.n_lines ++; - } + if (break_at != -1) { + // need to use one more line + // marking break_at+1 as start of a new line + int lead = break_at + 1; // the first symbol of the new line + if (text_info->n_lines >= text_info->max_lines) { + // Raise maximum number of lines + text_info->max_lines *= 2; + text_info->lines = realloc(text_info->lines, + sizeof(LineInfo) * + text_info->max_lines); + } + if (lead < text_info->length) + text_info->glyphs[lead].linebreak = break_type; + last_space = -1; + s1 = text_info->glyphs + lead; + s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x); + text_info->n_lines++; + } - if (cur->symbol == ' ') - last_space = i; + if (cur->symbol == ' ') + last_space = i; - // make sure the hard linebreak is not forgotten when - // there was a new soft linebreak just inserted - if (cur->symbol == '\n' && break_type == 1) - i--; - } + // make sure the hard linebreak is not forgotten when + // there was a new soft linebreak just inserted + if (cur->symbol == '\n' && break_type == 1) + i--; + } #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y)) - exit = 0; - while (!exit) { - exit = 1; - w = s3 = text_info.glyphs; - s1 = s2 = 0; - for (i = 0; i <= text_info.length; ++i) { - cur = text_info.glyphs + i; - if ((i == text_info.length) || cur->linebreak) { - s1 = s2; - s2 = s3; - s3 = cur; - if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft' - int l1, l2, l1_new, l2_new; + exit = 0; + while (!exit && render_priv->state.wrap_style != 1) { + exit = 1; + w = s3 = text_info->glyphs; + s1 = s2 = 0; + for (i = 0; i <= text_info->length; ++i) { + cur = text_info->glyphs + i; + if ((i == text_info->length) || cur->linebreak) { + s1 = s2; + s2 = s3; + s3 = cur; + if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft' + double l1, l2, l1_new, l2_new; - w = s2; - do { --w; } while ((w > s1) && (w->symbol == ' ')); - while ((w > s1) && (w->symbol != ' ')) { --w; } - e1 = w; - while ((e1 > s1) && (e1->symbol == ' ')) { --e1; } - if (w->symbol == ' ') ++w; - - l1 = ((s2-1)->bbox.xMax + (s2-1)->pos.x) - (s1->bbox.xMin + s1->pos.x); - l2 = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (s2->bbox.xMin + s2->pos.x); - l1_new = (e1->bbox.xMax + e1->pos.x) - (s1->bbox.xMin + s1->pos.x); - l2_new = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (w->bbox.xMin + w->pos.x); + w = s2; + do { + --w; + } while ((w > s1) && (w->symbol == ' ')); + while ((w > s1) && (w->symbol != ' ')) { + --w; + } + e1 = w; + while ((e1 > s1) && (e1->symbol == ' ')) { + --e1; + } + if (w->symbol == ' ') + ++w; - if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) { - w->linebreak = 1; - s2->linebreak = 0; - exit = 0; - } - } - } - if (i == text_info.length) - break; - } + l1 = d6_to_double(((s2 - 1)->bbox.xMax + (s2 - 1)->pos.x) - + (s1->bbox.xMin + s1->pos.x)); + l2 = d6_to_double(((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) - + (s2->bbox.xMin + s2->pos.x)); + l1_new = d6_to_double( + (e1->bbox.xMax + e1->pos.x) - + (s1->bbox.xMin + s1->pos.x)); + l2_new = d6_to_double( + ((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) - + (w->bbox.xMin + w->pos.x)); - } - assert(text_info.n_lines >= 1); + if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) { + w->linebreak = 1; + s2->linebreak = 0; + exit = 0; + } + } + } + if (i == text_info->length) + break; + } + + } + assert(text_info->n_lines >= 1); #undef DIFF - measure_text(); + measure_text(render_priv); + trim_whitespace(render_priv); + + pen_shift_x = 0.; + pen_shift_y = 0.; + cur_line = 1; + + i = 0; + cur = text_info->glyphs + i; + while (i < text_info->length && cur->skip) + cur = text_info->glyphs + ++i; + pen_shift_x = d6_to_double(-cur->pos.x); - pen_shift_x = 0; - pen_shift_y = 0; - cur_line = 1; - for (i = 0; i < text_info.length; ++i) { - cur = text_info.glyphs + i; - if (cur->linebreak) { - int height = text_info.lines[cur_line - 1].desc + text_info.lines[cur_line].asc; - cur_line ++; - pen_shift_x = - cur->pos.x; - pen_shift_y += d6_to_int(height + double_to_d6(global_settings->line_spacing)); - 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); - } - cur->pos.x += pen_shift_x; - cur->pos.y += pen_shift_y; - } + for (i = 0; i < text_info->length; ++i) { + cur = text_info->glyphs + i; + if (cur->linebreak) { + while (i < text_info->length && cur->skip && cur->symbol != '\n') + cur = text_info->glyphs + ++i; + double height = + text_info->lines[cur_line - 1].desc + + text_info->lines[cur_line].asc; + cur_line++; + pen_shift_x = d6_to_double(-cur->pos.x); + pen_shift_y += height + render_priv->settings.line_spacing; + ass_msg(render_priv->library, MSGL_DBG2, + "shifting from %d to %d by (%f, %f)", i, + text_info->length - 1, pen_shift_x, pen_shift_y); + } + cur->pos.x += double_to_d6(pen_shift_x); + cur->pos.y += double_to_d6(pen_shift_y); + } } /** @@ -1729,60 +1550,63 @@ * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts * (left part is filled with PrimaryColour, right one - with SecondaryColour). */ -static void process_karaoke_effects(void) +static void process_karaoke_effects(ASS_Renderer *render_priv) { - glyph_info_t *cur, *cur2; - glyph_info_t *s1, *e1; // start and end of the current word - glyph_info_t *s2; // start of the next word - int i; - int timing; // current timing - int tm_start, tm_end; // timings at start and end of the current word - int tm_current; - double dt; - int x; - int x_start, x_end; + GlyphInfo *cur, *cur2; + GlyphInfo *s1, *e1; // start and end of the current word + GlyphInfo *s2; // start of the next word + int i; + int timing; // current timing + int tm_start, tm_end; // timings at start and end of the current word + int tm_current; + double dt; + int x; + int x_start, x_end; - tm_current = frame_context.time - render_context.event->Start; - timing = 0; - s1 = s2 = 0; - for (i = 0; i <= text_info.length; ++i) { - cur = text_info.glyphs + i; - if ((i == text_info.length) || (cur->effect_type != EF_NONE)) { - s1 = s2; - s2 = cur; - if (s1) { - e1 = s2 - 1; - tm_start = timing + s1->effect_skip_timing; - tm_end = tm_start + s1->effect_timing; - timing = tm_end; - x_start = 1000000; - x_end = -1000000; - for (cur2 = s1; cur2 <= e1; ++cur2) { - x_start = FFMIN(x_start, cur2->bbox.xMin + cur2->pos.x); - x_end = FFMAX(x_end, cur2->bbox.xMax + cur2->pos.x); - } + tm_current = render_priv->time - render_priv->state.event->Start; + timing = 0; + s1 = s2 = 0; + for (i = 0; i <= render_priv->text_info.length; ++i) { + cur = render_priv->text_info.glyphs + i; + if ((i == render_priv->text_info.length) + || (cur->effect_type != EF_NONE)) { + s1 = s2; + s2 = cur; + if (s1) { + e1 = s2 - 1; + tm_start = timing + s1->effect_skip_timing; + tm_end = tm_start + s1->effect_timing; + timing = tm_end; + x_start = 1000000; + x_end = -1000000; + for (cur2 = s1; cur2 <= e1; ++cur2) { + x_start = FFMIN(x_start, d6_to_int(cur2->bbox.xMin + cur2->pos.x)); + x_end = FFMAX(x_end, d6_to_int(cur2->bbox.xMax + cur2->pos.x)); + } - dt = (tm_current - tm_start); - if ((s1->effect_type == EF_KARAOKE) || (s1->effect_type == EF_KARAOKE_KO)) { - if (dt > 0) - x = x_end + 1; - else - x = x_start; - } else if (s1->effect_type == EF_KARAOKE_KF) { - dt /= (tm_end - tm_start); - x = x_start + (x_end - x_start) * dt; - } else { - mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_UnknownEffectType_InternalError); - continue; - } + dt = (tm_current - tm_start); + if ((s1->effect_type == EF_KARAOKE) + || (s1->effect_type == EF_KARAOKE_KO)) { + if (dt > 0) + x = x_end + 1; + else + x = x_start; + } else if (s1->effect_type == EF_KARAOKE_KF) { + dt /= (tm_end - tm_start); + x = x_start + (x_end - x_start) * dt; + } else { + ass_msg(render_priv->library, MSGL_ERR, + "Unknown effect type"); + continue; + } - for (cur2 = s1; cur2 <= e1; ++cur2) { - cur2->effect_type = s1->effect_type; - cur2->effect_timing = x - cur2->pos.x; - } - } - } - } + for (cur2 = s1; cur2 <= e1; ++cur2) { + cur2->effect_type = s1->effect_type; + cur2->effect_timing = x - d6_to_int(cur2->pos.x); + } + } + } + } } /** @@ -1791,34 +1615,34 @@ * \param alignment alignment * \param bx, by out: base point coordinates */ -static void get_base_point(FT_BBox bbox, int alignment, int* bx, int* by) +static void get_base_point(DBBox *bbox, int alignment, double *bx, double *by) { - const int halign = alignment & 3; - const int valign = alignment & 12; - if (bx) - switch(halign) { - case HALIGN_LEFT: - *bx = bbox.xMin; - break; - case HALIGN_CENTER: - *bx = (bbox.xMax + bbox.xMin) / 2; - break; - case HALIGN_RIGHT: - *bx = bbox.xMax; - break; - } - if (by) - switch(valign) { - case VALIGN_TOP: - *by = bbox.yMin; - break; - case VALIGN_CENTER: - *by = (bbox.yMax + bbox.yMin) / 2; - break; - case VALIGN_SUB: - *by = bbox.yMax; - break; - } + const int halign = alignment & 3; + const int valign = alignment & 12; + if (bx) + switch (halign) { + case HALIGN_LEFT: + *bx = bbox->xMin; + break; + case HALIGN_CENTER: + *bx = (bbox->xMax + bbox->xMin) / 2.0; + break; + case HALIGN_RIGHT: + *bx = bbox->xMax; + break; + } + if (by) + switch (valign) { + case VALIGN_TOP: + *by = bbox->yMin; + break; + case VALIGN_CENTER: + *by = (bbox->yMax + bbox->yMin) / 2.0; + break; + case VALIGN_SUB: + *by = bbox->yMax; + break; + } } /** @@ -1826,42 +1650,47 @@ * Applies rotations given by frx, fry and frz and projects the points back * onto the screen plane. */ -static void transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry, double frz) { - double sx = sin(frx); - double sy = sin(fry); - double sz = sin(frz); - double cx = cos(frx); - double cy = cos(fry); - double cz = cos(frz); - FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline; - FT_Vector* p = outline->points; - double x, y, z, xx, yy, zz; - int i; - - for (i=0; i<outline->n_points; i++) { - x = p[i].x + shift.x; - y = p[i].y + shift.y; - z = 0.; +static void +transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry, + double frz, double fax, double fay, double scale, + int yshift) +{ + double sx = sin(frx); + double sy = sin(fry); + double sz = sin(frz); + double cx = cos(frx); + double cy = cos(fry); + double cz = cos(frz); + FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline; + FT_Vector *p = outline->points; + double x, y, z, xx, yy, zz; + int i, dist; - xx = x*cz + y*sz; - yy = -(x*sz - y*cz); - zz = z; + dist = 20000 * scale; + for (i = 0; i < outline->n_points; i++) { + x = (double) p[i].x + shift.x + (fax * (yshift - p[i].y)); + y = (double) p[i].y + shift.y + (-fay * p[i].x); + z = 0.; - x = xx; - y = yy*cx + zz*sx; - z = yy*sx - zz*cx; + xx = x * cz + y * sz; + yy = -(x * sz - y * cz); + zz = z; - xx = x*cy + z*sy; - yy = y; - zz = x*sy - z*cy; + x = xx; + y = yy * cx + zz * sx; + z = yy * sx - zz * cx; - zz = FFMAX(zz, -19000); + xx = x * cy + z * sy; + yy = y; + zz = x * sy - z * cy; - x = (xx * 20000) / (zz + 20000); - y = (yy * 20000) / (zz + 20000); - p[i].x = x - shift.x + 0.5; - p[i].y = y - shift.y + 0.5; - } + zz = FFMAX(zz, 1000 - dist); + + x = (xx * dist) / (zz + dist); + y = (yy * dist) / (zz + dist); + p[i].x = x - shift.x + 0.5; + p[i].y = y - shift.y + 0.5; + } } /** @@ -1874,17 +1703,22 @@ * \param frz z-axis rotation angle * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it. */ -static void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz) +static void +transform_3d(FT_Vector shift, FT_Glyph *glyph, FT_Glyph *glyph2, + double frx, double fry, double frz, double fax, double fay, + double scale, int yshift) { - frx = - frx; - frz = - frz; - if (frx != 0. || fry != 0. || frz != 0.) { - if (glyph && *glyph) - transform_3d_points(shift, *glyph, frx, fry, frz); + frx = -frx; + frz = -frz; + if (frx != 0. || fry != 0. || frz != 0. || fax != 0. || fay != 0.) { + if (glyph && *glyph) + transform_3d_points(shift, *glyph, frx, fry, frz, + fax, fay, scale, yshift); - if (glyph2 && *glyph2) - transform_3d_points(shift, *glyph2, frx, fry, frz); - } + if (glyph2 && *glyph2) + transform_3d_points(shift, *glyph2, frx, fry, frz, + fax, fay, scale, yshift); + } } @@ -1892,622 +1726,836 @@ * \brief Main ass rendering function, glues everything together * \param event event to render * \param event_images struct containing resulting images, will also be initialized - * Process event, appending resulting ass_image_t's to images_root. + * Process event, appending resulting ASS_Image's to images_root. */ -static int ass_render_event(ass_event_t* event, event_images_t* event_images) +static int +ass_render_event(ASS_Renderer *render_priv, ASS_Event *event, + EventImages *event_images) { - char* p; - FT_UInt previous; - FT_UInt num_glyphs; - FT_Vector pen; - unsigned code; - FT_BBox bbox; - int i, j; - FT_Vector shift; - int MarginL, MarginR, MarginV; - int last_break; - int alignment, halign, valign; - int device_x = 0, device_y = 0; + char *p; + FT_UInt previous; + FT_UInt num_glyphs; + FT_Vector pen; + unsigned code; + DBBox bbox; + int i, j; + int MarginL, MarginR, MarginV; + int last_break; + int alignment, halign, valign; + int kern = render_priv->track->Kerning; + double device_x = 0; + double device_y = 0; + TextInfo *text_info = &render_priv->text_info; + ASS_Drawing *drawing; - if (event->Style >= frame_context.track->n_styles) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleFound); - return 1; - } - if (!event->Text) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EmptyEvent); - return 1; - } + if (event->Style >= render_priv->track->n_styles) { + ass_msg(render_priv->library, MSGL_WARN, "No style found"); + return 1; + } + if (!event->Text) { + ass_msg(render_priv->library, MSGL_WARN, "Empty event"); + return 1; + } - init_render_context(event); + init_render_context(render_priv, event); - text_info.length = 0; - pen.x = 0; - pen.y = 0; - previous = 0; - num_glyphs = 0; - p = event->Text; - // Event parsing. - while (1) { - // get next char, executing style override - // this affects render_context - do { - code = get_next_char(&p); - } while (code && render_context.drawing_mode); // skip everything in drawing mode + drawing = render_priv->state.drawing; + text_info->length = 0; + pen.x = 0; + pen.y = 0; + previous = 0; + num_glyphs = 0; + p = event->Text; + // Event parsing. + while (1) { + // get next char, executing style override + // this affects render_context + do { + code = get_next_char(render_priv, &p); + if (render_priv->state.drawing_mode && code) + ass_drawing_add_char(drawing, (char) code); + } while (code && render_priv->state.drawing_mode); // skip everything in drawing mode - // face could have been changed in get_next_char - if (!render_context.font) { - free_render_context(); - return 1; - } - - if (code == 0) - break; + // Parse drawing + if (drawing->i) { + drawing->scale_x = render_priv->state.scale_x * + render_priv->font_scale_x * + render_priv->font_scale; + drawing->scale_y = render_priv->state.scale_y * + render_priv->font_scale; + ass_drawing_hash(drawing); + p--; + code = -1; + } - if (text_info.length >= MAX_GLYPHS) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_MAX_GLYPHS_Reached, - (int)(event - frame_context.track->events), event->Start, event->Duration, event->Text); - break; - } + // face could have been changed in get_next_char + if (!render_priv->state.font) { + free_render_context(render_priv); + return 1; + } - if ( previous && code ) { - FT_Vector delta; - delta = ass_font_get_kerning(render_context.font, previous, code); - pen.x += delta.x * render_context.scale_x; - pen.y += delta.y * render_context.scale_y; - } + if (code == 0) + break; + + if (text_info->length >= text_info->max_glyphs) { + // Raise maximum number of glyphs + text_info->max_glyphs *= 2; + text_info->glyphs = + realloc(text_info->glyphs, + sizeof(GlyphInfo) * text_info->max_glyphs); + } - shift.x = pen.x & SUBPIXEL_MASK; - shift.y = pen.y & SUBPIXEL_MASK; + // Add kerning to pen + if (kern && previous && code && !drawing->hash) { + FT_Vector delta; + delta = + ass_font_get_kerning(render_priv->state.font, previous, + code); + pen.x += delta.x * render_priv->state.scale_x + * render_priv->font_scale_x; + pen.y += delta.y * render_priv->state.scale_y + * render_priv->font_scale_x; + } - if (render_context.evt_type == EVENT_POSITIONED) { - shift.x += double_to_d6(x2scr_pos(render_context.pos_x)) & SUBPIXEL_MASK; - shift.y -= double_to_d6(y2scr_pos(render_context.pos_y)) & SUBPIXEL_MASK; - } + ass_font_set_transform(render_priv->state.font, + render_priv->state.scale_x * + render_priv->font_scale_x, + render_priv->state.scale_y, NULL); - ass_font_set_transform(render_context.font, - render_context.scale_x * frame_context.font_scale_x, - render_context.scale_y, - &shift ); + get_outline_glyph(render_priv, code, + text_info->glyphs + text_info->length, drawing); - get_outline_glyph(code, text_info.glyphs + text_info.length, &shift); - - text_info.glyphs[text_info.length].pos.x = pen.x >> 6; - text_info.glyphs[text_info.length].pos.y = pen.y >> 6; + // Add additional space after italic to non-italic style changes + if (text_info->length && + text_info->glyphs[text_info->length - 1].hash_key.italic && + !render_priv->state.italic) { + int back = text_info->length - 1; + GlyphInfo *og = &text_info->glyphs[back]; + while (back && og->bbox.xMax - og->bbox.xMin == 0 + && og->hash_key.italic) + og = &text_info->glyphs[--back]; + if (og->bbox.xMax > og->advance.x) { + // The FreeType oblique slants by 6/16 + pen.x += og->bbox.yMax * 0.375; + } + } - pen.x += text_info.glyphs[text_info.length].advance.x; - pen.x += double_to_d6(render_context.hspacing); - pen.y += text_info.glyphs[text_info.length].advance.y; + text_info->glyphs[text_info->length].pos.x = pen.x; + text_info->glyphs[text_info->length].pos.y = pen.y; - previous = code; + pen.x += text_info->glyphs[text_info->length].advance.x; + pen.x += double_to_d6(render_priv->state.hspacing * + render_priv->font_scale + * render_priv->state.scale_x); + pen.y += text_info->glyphs[text_info->length].advance.y; + pen.y += (render_priv->state.fay * render_priv->state.scale_y) * + text_info->glyphs[text_info->length].advance.x; + + previous = code; - text_info.glyphs[text_info.length].symbol = code; - text_info.glyphs[text_info.length].linebreak = 0; - for (i = 0; i < 4; ++i) { - uint32_t clr = render_context.c[i]; - change_alpha(&clr, mult_alpha(_a(clr), render_context.fade), 1.); - text_info.glyphs[text_info.length].c[i] = clr; - } - text_info.glyphs[text_info.length].effect_type = render_context.effect_type; - text_info.glyphs[text_info.length].effect_timing = render_context.effect_timing; - text_info.glyphs[text_info.length].effect_skip_timing = render_context.effect_skip_timing; - text_info.glyphs[text_info.length].be = render_context.be; - text_info.glyphs[text_info.length].blur = render_context.blur; - text_info.glyphs[text_info.length].shadow = render_context.shadow; - text_info.glyphs[text_info.length].frx = render_context.frx; - text_info.glyphs[text_info.length].fry = render_context.fry; - text_info.glyphs[text_info.length].frz = render_context.frz; - ass_font_get_asc_desc(render_context.font, code, - &text_info.glyphs[text_info.length].asc, - &text_info.glyphs[text_info.length].desc); - text_info.glyphs[text_info.length].asc *= render_context.scale_y; - text_info.glyphs[text_info.length].desc *= render_context.scale_y; + text_info->glyphs[text_info->length].symbol = code; + text_info->glyphs[text_info->length].linebreak = 0; + for (i = 0; i < 4; ++i) { + uint32_t clr = render_priv->state.c[i]; + change_alpha(&clr, + mult_alpha(_a(clr), render_priv->state.fade), 1.); + text_info->glyphs[text_info->length].c[i] = clr; + } + text_info->glyphs[text_info->length].effect_type = + render_priv->state.effect_type; + text_info->glyphs[text_info->length].effect_timing = + render_priv->state.effect_timing; + text_info->glyphs[text_info->length].effect_skip_timing = + render_priv->state.effect_skip_timing; + text_info->glyphs[text_info->length].be = render_priv->state.be; + text_info->glyphs[text_info->length].blur = render_priv->state.blur; + text_info->glyphs[text_info->length].shadow_x = + render_priv->state.shadow_x; + text_info->glyphs[text_info->length].shadow_y = + render_priv->state.shadow_y; + text_info->glyphs[text_info->length].frx = render_priv->state.frx; + text_info->glyphs[text_info->length].fry = render_priv->state.fry; + text_info->glyphs[text_info->length].frz = render_priv->state.frz; + text_info->glyphs[text_info->length].fax = render_priv->state.fax; + text_info->glyphs[text_info->length].fay = render_priv->state.fay; + if (drawing->hash) { + text_info->glyphs[text_info->length].asc = drawing->asc; + text_info->glyphs[text_info->length].desc = drawing->desc; + } else { + ass_font_get_asc_desc(render_priv->state.font, code, + &text_info->glyphs[text_info->length].asc, + &text_info->glyphs[text_info->length].desc); - // fill bitmap_hash_key - text_info.glyphs[text_info.length].hash_key.font = render_context.font; - text_info.glyphs[text_info.length].hash_key.size = render_context.font_size; - text_info.glyphs[text_info.length].hash_key.outline = render_context.border * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.scale_x = render_context.scale_x * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.scale_y = render_context.scale_y * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.frx = render_context.frx * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.fry = render_context.fry * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.frz = render_context.frz * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.bold = render_context.bold; - text_info.glyphs[text_info.length].hash_key.italic = render_context.italic; - text_info.glyphs[text_info.length].hash_key.ch = code; - text_info.glyphs[text_info.length].hash_key.advance = shift; - text_info.glyphs[text_info.length].hash_key.be = render_context.be; - text_info.glyphs[text_info.length].hash_key.blur = render_context.blur; - - text_info.length++; + text_info->glyphs[text_info->length].asc *= + render_priv->state.scale_y; + text_info->glyphs[text_info->length].desc *= + render_priv->state.scale_y; + } - render_context.effect_type = EF_NONE; - render_context.effect_timing = 0; - render_context.effect_skip_timing = 0; - } - - if (text_info.length == 0) { - // no valid symbols in the event; this can be smth like {comment} - free_render_context(); - return 1; - } + // fill bitmap_hash_key + if (!drawing->hash) { + text_info->glyphs[text_info->length].hash_key.font = + render_priv->state.font; + text_info->glyphs[text_info->length].hash_key.size = + render_priv->state.font_size; + text_info->glyphs[text_info->length].hash_key.bold = + render_priv->state.bold; + text_info->glyphs[text_info->length].hash_key.italic = + render_priv->state.italic; + } else + text_info->glyphs[text_info->length].hash_key.drawing_hash = + drawing->hash; + text_info->glyphs[text_info->length].hash_key.ch = code; + text_info->glyphs[text_info->length].hash_key.outline.x = + double_to_d16(render_priv->state.border_x); + text_info->glyphs[text_info->length].hash_key.outline.y = + double_to_d16(render_priv->state.border_y); + text_info->glyphs[text_info->length].hash_key.scale_x = + double_to_d16(render_priv->state.scale_x); + text_info->glyphs[text_info->length].hash_key.scale_y = + double_to_d16(render_priv->state.scale_y); + text_info->glyphs[text_info->length].hash_key.frx = + rot_key(render_priv->state.frx); + text_info->glyphs[text_info->length].hash_key.fry = + rot_key(render_priv->state.fry); + text_info->glyphs[text_info->length].hash_key.frz = + rot_key(render_priv->state.frz); + text_info->glyphs[text_info->length].hash_key.fax = + double_to_d16(render_priv->state.fax); + text_info->glyphs[text_info->length].hash_key.fay = + double_to_d16(render_priv->state.fay); + text_info->glyphs[text_info->length].hash_key.advance.x = pen.x; + text_info->glyphs[text_info->length].hash_key.advance.y = pen.y; + text_info->glyphs[text_info->length].hash_key.be = + render_priv->state.be; + text_info->glyphs[text_info->length].hash_key.blur = + render_priv->state.blur; + text_info->glyphs[text_info->length].hash_key.border_style = + render_priv->state.style->BorderStyle; + text_info->glyphs[text_info->length].hash_key.shadow_offset.x = + double_to_d6( + render_priv->state.shadow_x * render_priv->border_scale - + (int) (render_priv->state.shadow_x * + render_priv->border_scale)); + text_info->glyphs[text_info->length].hash_key.shadow_offset.y = + double_to_d6( + render_priv->state.shadow_y * render_priv->border_scale - + (int) (render_priv->state.shadow_y * + render_priv->border_scale)); + text_info->glyphs[text_info->length].hash_key.flags = + render_priv->state.flags; - // depends on glyph x coordinates being monotonous, so it should be done before line wrap - process_karaoke_effects(); + text_info->length++; - // alignments - alignment = render_context.alignment; - halign = alignment & 3; - valign = alignment & 12; - - MarginL = (event->MarginL) ? event->MarginL : render_context.style->MarginL; - MarginR = (event->MarginR) ? event->MarginR : render_context.style->MarginR; - MarginV = (event->MarginV) ? event->MarginV : render_context.style->MarginV; - - if (render_context.evt_type != EVENT_HSCROLL) { - int max_text_width; + render_priv->state.effect_type = EF_NONE; + render_priv->state.effect_timing = 0; + render_priv->state.effect_skip_timing = 0; - // calculate max length of a line - max_text_width = x2scr(frame_context.track->PlayResX - MarginR) - x2scr(MarginL); - - // rearrange text in several lines - wrap_lines_smart(max_text_width); + if (drawing->hash) { + ass_drawing_free(drawing); + drawing = render_priv->state.drawing = + ass_drawing_new(render_priv->fontconfig_priv, + render_priv->state.font, + render_priv->settings.hinting, + render_priv->ftlibrary); + } + } - // align text - last_break = -1; - for (i = 1; i < text_info.length + 1; ++i) { // (text_info.length + 1) is the end of the last line - if ((i == text_info.length) || text_info.glyphs[i].linebreak) { - int width, shift = 0; - glyph_info_t* first_glyph = text_info.glyphs + last_break + 1; - glyph_info_t* last_glyph = text_info.glyphs + i - 1; - while ((last_glyph > first_glyph) && ((last_glyph->symbol == '\n') || (last_glyph->symbol == 0))) - last_glyph --; + if (text_info->length == 0) { + // no valid symbols in the event; this can be smth like {comment} + free_render_context(render_priv); + return 1; + } + // depends on glyph x coordinates being monotonous, so it should be done before line wrap + process_karaoke_effects(render_priv); - width = last_glyph->pos.x + d6_to_int(last_glyph->advance.x) - first_glyph->pos.x; - if (halign == HALIGN_LEFT) { // left aligned, no action - shift = 0; - } else if (halign == HALIGN_RIGHT) { // right aligned - shift = max_text_width - width; - } else if (halign == HALIGN_CENTER) { // centered - shift = (max_text_width - width) / 2; - } - for (j = last_break + 1; j < i; ++j) { - text_info.glyphs[j].pos.x += shift; - } - last_break = i - 1; - } - } - } else { // render_context.evt_type == EVENT_HSCROLL - measure_text(); - } + // alignments + alignment = render_priv->state.alignment; + halign = alignment & 3; + valign = alignment & 12; + + MarginL = + (event->MarginL) ? event->MarginL : render_priv->state.style-> + MarginL; + MarginR = + (event->MarginR) ? event->MarginR : render_priv->state.style-> + MarginR; + MarginV = + (event->MarginV) ? event->MarginV : render_priv->state.style-> + MarginV; + + if (render_priv->state.evt_type != EVENT_HSCROLL) { + double max_text_width; + + // calculate max length of a line + max_text_width = + x2scr(render_priv, + render_priv->track->PlayResX - MarginR) - + x2scr(render_priv, MarginL); - // determing text bounding box - compute_string_bbox(&text_info, &bbox); - - // determine device coordinates for text + // rearrange text in several lines + wrap_lines_smart(render_priv, max_text_width); - // x coordinate for everything except positioned events - if (render_context.evt_type == EVENT_NORMAL || - render_context.evt_type == EVENT_VSCROLL) { - device_x = x2scr(MarginL); - } else if (render_context.evt_type == EVENT_HSCROLL) { - if (render_context.scroll_direction == SCROLL_RL) - device_x = x2scr(frame_context.track->PlayResX - render_context.scroll_shift); - else if (render_context.scroll_direction == SCROLL_LR) - device_x = x2scr(render_context.scroll_shift) - (bbox.xMax - bbox.xMin); - } + // align text + last_break = -1; + for (i = 1; i < text_info->length + 1; ++i) { // (text_info->length + 1) is the end of the last line + if ((i == text_info->length) + || text_info->glyphs[i].linebreak) { + double width, shift = 0; + GlyphInfo *first_glyph = + text_info->glyphs + last_break + 1; + GlyphInfo *last_glyph = text_info->glyphs + i - 1; + + while (first_glyph < last_glyph && first_glyph->skip) + first_glyph++; + + while ((last_glyph > first_glyph) + && ((last_glyph->symbol == '\n') + || (last_glyph->symbol == 0) + || (last_glyph->skip))) + last_glyph--; - // y coordinate for everything except positioned events - if (render_context.evt_type == EVENT_NORMAL || - render_context.evt_type == EVENT_HSCROLL) { - if (valign == VALIGN_TOP) { // toptitle - device_y = y2scr_top(MarginV) + d6_to_int(text_info.lines[0].asc); - } else if (valign == VALIGN_CENTER) { // midtitle - int scr_y = y2scr(frame_context.track->PlayResY / 2); - device_y = scr_y - (bbox.yMax - bbox.yMin) / 2; - } else { // subtitle - int scr_y; - if (valign != VALIGN_SUB) - mp_msg(MSGT_ASS, MSGL_V, "Invalid valign, supposing 0 (subtitle)\n"); - scr_y = y2scr_sub(frame_context.track->PlayResY - MarginV); - device_y = scr_y; - device_y -= d6_to_int(text_info.height); - device_y += d6_to_int(text_info.lines[0].asc); - } - } else if (render_context.evt_type == EVENT_VSCROLL) { - if (render_context.scroll_direction == SCROLL_TB) - device_y = y2scr(render_context.clip_y0 + render_context.scroll_shift) - (bbox.yMax - bbox.yMin); - else if (render_context.scroll_direction == SCROLL_BT) - device_y = y2scr(render_context.clip_y1 - render_context.scroll_shift); - } + width = d6_to_double( + last_glyph->pos.x + last_glyph->advance.x - + first_glyph->pos.x); + if (halign == HALIGN_LEFT) { // left aligned, no action + shift = 0; + } else if (halign == HALIGN_RIGHT) { // right aligned + shift = max_text_width - width; + } else if (halign == HALIGN_CENTER) { // centered + shift = (max_text_width - width) / 2.0; + } + for (j = last_break + 1; j < i; ++j) { + text_info->glyphs[j].pos.x += double_to_d6(shift); + } + last_break = i - 1; + } + } + } else { // render_priv->state.evt_type == EVENT_HSCROLL + measure_text(render_priv); + } + + // determing text bounding box + compute_string_bbox(text_info, &bbox); + + // determine device coordinates for text - // positioned events are totally different - if (render_context.evt_type == EVENT_POSITIONED) { - int base_x = 0; - int base_y = 0; - mp_msg(MSGT_ASS, MSGL_DBG2, "positioned event at %f, %f\n", render_context.pos_x, render_context.pos_y); - get_base_point(bbox, alignment, &base_x, &base_y); - device_x = x2scr_pos(render_context.pos_x) - base_x; - device_y = y2scr_pos(render_context.pos_y) - base_y; - } - - // fix clip coordinates (they depend on alignment) - if (render_context.evt_type == EVENT_NORMAL || - render_context.evt_type == EVENT_HSCROLL || - render_context.evt_type == EVENT_VSCROLL) { - render_context.clip_x0 = x2scr(render_context.clip_x0); - render_context.clip_x1 = x2scr(render_context.clip_x1); - if (valign == VALIGN_TOP) { - render_context.clip_y0 = y2scr_top(render_context.clip_y0); - render_context.clip_y1 = y2scr_top(render_context.clip_y1); - } else if (valign == VALIGN_CENTER) { - render_context.clip_y0 = y2scr(render_context.clip_y0); - render_context.clip_y1 = y2scr(render_context.clip_y1); - } else if (valign == VALIGN_SUB) { - render_context.clip_y0 = y2scr_sub(render_context.clip_y0); - render_context.clip_y1 = y2scr_sub(render_context.clip_y1); - } - } else if (render_context.evt_type == EVENT_POSITIONED) { - render_context.clip_x0 = x2scr_pos(render_context.clip_x0); - render_context.clip_x1 = x2scr_pos(render_context.clip_x1); - render_context.clip_y0 = y2scr_pos(render_context.clip_y0); - render_context.clip_y1 = y2scr_pos(render_context.clip_y1); - } - - render_context.clip_x0 = FFMIN(FFMAX(render_context.clip_x0, 0), frame_context.width); - render_context.clip_x1 = FFMIN(FFMAX(render_context.clip_x1, 0), frame_context.width); - render_context.clip_y0 = FFMIN(FFMAX(render_context.clip_y0, 0), frame_context.height); - render_context.clip_y1 = FFMIN(FFMAX(render_context.clip_y1, 0), frame_context.height); + // x coordinate for everything except positioned events + if (render_priv->state.evt_type == EVENT_NORMAL || + render_priv->state.evt_type == EVENT_VSCROLL) { + device_x = x2scr(render_priv, MarginL); + } else if (render_priv->state.evt_type == EVENT_HSCROLL) { + if (render_priv->state.scroll_direction == SCROLL_RL) + device_x = + x2scr(render_priv, + render_priv->track->PlayResX - + render_priv->state.scroll_shift); + else if (render_priv->state.scroll_direction == SCROLL_LR) + device_x = + x2scr(render_priv, + render_priv->state.scroll_shift) - (bbox.xMax - + bbox.xMin); + } + // y coordinate for everything except positioned events + if (render_priv->state.evt_type == EVENT_NORMAL || + render_priv->state.evt_type == EVENT_HSCROLL) { + if (valign == VALIGN_TOP) { // toptitle + device_y = + y2scr_top(render_priv, + MarginV) + text_info->lines[0].asc; + } else if (valign == VALIGN_CENTER) { // midtitle + double scr_y = + y2scr(render_priv, render_priv->track->PlayResY / 2.0); + device_y = scr_y - (bbox.yMax + bbox.yMin) / 2.0; + } else { // subtitle + double scr_y; + if (valign != VALIGN_SUB) + ass_msg(render_priv->library, MSGL_V, + "Invalid valign, supposing 0 (subtitle)"); + scr_y = + y2scr_sub(render_priv, + render_priv->track->PlayResY - MarginV); + device_y = scr_y; + device_y -= text_info->height; + device_y += text_info->lines[0].asc; + } + } else if (render_priv->state.evt_type == EVENT_VSCROLL) { + if (render_priv->state.scroll_direction == SCROLL_TB) + device_y = + y2scr(render_priv, + render_priv->state.clip_y0 + + render_priv->state.scroll_shift) - (bbox.yMax - + bbox.yMin); + else if (render_priv->state.scroll_direction == SCROLL_BT) + device_y = + y2scr(render_priv, + render_priv->state.clip_y1 - + render_priv->state.scroll_shift); + } + // positioned events are totally different + if (render_priv->state.evt_type == EVENT_POSITIONED) { + double base_x = 0; + double base_y = 0; + ass_msg(render_priv->library, MSGL_DBG2, "positioned event at %f, %f", + render_priv->state.pos_x, render_priv->state.pos_y); + get_base_point(&bbox, alignment, &base_x, &base_y); + device_x = + x2scr_pos(render_priv, render_priv->state.pos_x) - base_x; + device_y = + y2scr_pos(render_priv, render_priv->state.pos_y) - base_y; + } + // fix clip coordinates (they depend on alignment) + if (render_priv->state.evt_type == EVENT_NORMAL || + render_priv->state.evt_type == EVENT_HSCROLL || + render_priv->state.evt_type == EVENT_VSCROLL) { + render_priv->state.clip_x0 = + x2scr(render_priv, render_priv->state.clip_x0); + render_priv->state.clip_x1 = + x2scr(render_priv, render_priv->state.clip_x1); + if (valign == VALIGN_TOP) { + render_priv->state.clip_y0 = + y2scr_top(render_priv, render_priv->state.clip_y0); + render_priv->state.clip_y1 = + y2scr_top(render_priv, render_priv->state.clip_y1); + } else if (valign == VALIGN_CENTER) { + render_priv->state.clip_y0 = + y2scr(render_priv, render_priv->state.clip_y0); + render_priv->state.clip_y1 = + y2scr(render_priv, render_priv->state.clip_y1); + } else if (valign == VALIGN_SUB) { + render_priv->state.clip_y0 = + y2scr_sub(render_priv, render_priv->state.clip_y0); + render_priv->state.clip_y1 = + y2scr_sub(render_priv, render_priv->state.clip_y1); + } + } else if (render_priv->state.evt_type == EVENT_POSITIONED) { + render_priv->state.clip_x0 = + x2scr_pos(render_priv, render_priv->state.clip_x0); + render_priv->state.clip_x1 = + x2scr_pos(render_priv, render_priv->state.clip_x1); + render_priv->state.clip_y0 = + y2scr_pos(render_priv, render_priv->state.clip_y0); + render_priv->state.clip_y1 = + y2scr_pos(render_priv, render_priv->state.clip_y1); + } + // calculate rotation parameters + { + DVector center; - // calculate rotation parameters - { - FT_Vector center; + if (render_priv->state.have_origin) { + center.x = x2scr(render_priv, render_priv->state.org_x); + center.y = y2scr(render_priv, render_priv->state.org_y); + } else { + double bx = 0., by = 0.; + get_base_point(&bbox, alignment, &bx, &by); + center.x = device_x + bx; + center.y = device_y + by; + } + + for (i = 0; i < text_info->length; ++i) { + GlyphInfo *info = text_info->glyphs + i; - if (render_context.have_origin) { - center.x = x2scr(render_context.org_x); - center.y = y2scr(render_context.org_y); - } else { - int bx = 0, by = 0; - get_base_point(bbox, alignment, &bx, &by); - center.x = device_x + bx; - center.y = device_y + by; - } - - for (i = 0; i < text_info.length; ++i) { - glyph_info_t* info = text_info.glyphs + i; + if (info->hash_key.frx || info->hash_key.fry + || info->hash_key.frz || info->hash_key.fax + || info->hash_key.fay) { + info->hash_key.shift_x = info->pos.x + double_to_d6(device_x - center.x); + info->hash_key.shift_y = + -(info->pos.y + double_to_d6(device_y - center.y)); + } else { + info->hash_key.shift_x = 0; + info->hash_key.shift_y = 0; + } + } + } - if (info->hash_key.frx || info->hash_key.fry || info->hash_key.frz) { - info->hash_key.shift_x = info->pos.x + device_x - center.x; - info->hash_key.shift_y = - (info->pos.y + device_y - center.y); - } else { - info->hash_key.shift_x = 0; - info->hash_key.shift_y = 0; - } - } - } + // convert glyphs to bitmaps + for (i = 0; i < text_info->length; ++i) { + GlyphInfo *g = text_info->glyphs + i; + g->hash_key.advance.x = + double_to_d6(device_x - (int) device_x + + d6_to_double(g->pos.x & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY; + g->hash_key.advance.y = + double_to_d6(device_y - (int) device_y + + d6_to_double(g->pos.y & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY; + get_bitmap_glyph(render_priv, text_info->glyphs + i); + } - // convert glyphs to bitmaps - for (i = 0; i < text_info.length; ++i) - get_bitmap_glyph(text_info.glyphs + i); + memset(event_images, 0, sizeof(*event_images)); + event_images->top = device_y - text_info->lines[0].asc; + event_images->height = text_info->height; + event_images->left = device_x + bbox.xMin + 0.5; + event_images->width = bbox.xMax - bbox.xMin + 0.5; + event_images->detect_collisions = render_priv->state.detect_collisions; + event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1; + event_images->event = event; + event_images->imgs = render_text(render_priv, (int) device_x, (int) device_y); - memset(event_images, 0, sizeof(*event_images)); - event_images->top = device_y - d6_to_int(text_info.lines[0].asc); - event_images->height = d6_to_int(text_info.height); - event_images->detect_collisions = render_context.detect_collisions; - event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1; - event_images->event = event; - event_images->imgs = render_text(&text_info, device_x, device_y); + free_render_context(render_priv); - free_render_context(); - - return 0; + return 0; } /** * \brief deallocate image list * \param img list pointer */ -void ass_free_images(ass_image_t* img) +static void ass_free_images(ASS_Image *img) { - while (img) { - ass_image_t* next = img->next; - free(img); - img = next; - } + while (img) { + ASS_Image *next = img->next; + free(img); + img = next; + } } -static void ass_reconfigure(ass_renderer_t* priv) +static void ass_reconfigure(ASS_Renderer *priv) { - priv->render_id = ++last_render_id; - ass_glyph_cache_reset(); - ass_bitmap_cache_reset(); - ass_composite_cache_reset(); - ass_free_images(priv->prev_images_root); - priv->prev_images_root = 0; + priv->render_id++; + priv->cache.glyph_cache = + ass_glyph_cache_reset(priv->cache.glyph_cache); + priv->cache.bitmap_cache = + ass_bitmap_cache_reset(priv->cache.bitmap_cache); + priv->cache.composite_cache = + ass_composite_cache_reset(priv->cache.composite_cache); + ass_free_images(priv->prev_images_root); + priv->prev_images_root = 0; } -void ass_set_frame_size(ass_renderer_t* priv, int w, int h) +void ass_set_frame_size(ASS_Renderer *priv, int w, int h) { - if (priv->settings.frame_width != w || priv->settings.frame_height != h) { - priv->settings.frame_width = w; - priv->settings.frame_height = h; - if (priv->settings.aspect == 0.) - priv->settings.aspect = ((double)w) / h; - ass_reconfigure(priv); - } + if (priv->settings.frame_width != w || priv->settings.frame_height != h) { + priv->settings.frame_width = w; + priv->settings.frame_height = h; + if (priv->settings.aspect == 0.) { + priv->settings.aspect = ((double) w) / h; + priv->settings.storage_aspect = ((double) w) / h; + } + ass_reconfigure(priv); + } } -void ass_set_margins(ass_renderer_t* priv, int t, int b, int l, int r) +void ass_set_margins(ASS_Renderer *priv, int t, int b, int l, int r) { - if (priv->settings.left_margin != l || - priv->settings.right_margin != r || - priv->settings.top_margin != t || - priv->settings.bottom_margin != b) { - priv->settings.left_margin = l; - priv->settings.right_margin = r; - priv->settings.top_margin = t; - priv->settings.bottom_margin = b; - ass_reconfigure(priv); - } -} - -void ass_set_use_margins(ass_renderer_t* priv, int use) -{ - priv->settings.use_margins = use; + if (priv->settings.left_margin != l || + priv->settings.right_margin != r || + priv->settings.top_margin != t + || priv->settings.bottom_margin != b) { + priv->settings.left_margin = l; + priv->settings.right_margin = r; + priv->settings.top_margin = t; + priv->settings.bottom_margin = b; + ass_reconfigure(priv); + } } -void ass_set_aspect_ratio(ass_renderer_t* priv, double ar) +void ass_set_use_margins(ASS_Renderer *priv, int use) { - if (priv->settings.aspect != ar) { - priv->settings.aspect = ar; - ass_reconfigure(priv); - } + priv->settings.use_margins = use; } -void ass_set_font_scale(ass_renderer_t* priv, double font_scale) +void ass_set_aspect_ratio(ASS_Renderer *priv, double dar, double sar) { - if (priv->settings.font_size_coeff != font_scale) { - priv->settings.font_size_coeff = font_scale; - ass_reconfigure(priv); - } + if (priv->settings.aspect != dar || priv->settings.storage_aspect != sar) { + priv->settings.aspect = dar; + priv->settings.storage_aspect = sar; + ass_reconfigure(priv); + } } -void ass_set_hinting(ass_renderer_t* priv, ass_hinting_t ht) +void ass_set_font_scale(ASS_Renderer *priv, double font_scale) { - if (priv->settings.hinting != ht) { - priv->settings.hinting = ht; - ass_reconfigure(priv); - } + if (priv->settings.font_size_coeff != font_scale) { + priv->settings.font_size_coeff = font_scale; + ass_reconfigure(priv); + } } -void ass_set_line_spacing(ass_renderer_t* priv, double line_spacing) +void ass_set_hinting(ASS_Renderer *priv, ASS_Hinting ht) { - priv->settings.line_spacing = line_spacing; + if (priv->settings.hinting != ht) { + priv->settings.hinting = ht; + ass_reconfigure(priv); + } +} + +void ass_set_line_spacing(ASS_Renderer *priv, double line_spacing) +{ + priv->settings.line_spacing = line_spacing; } -static int ass_set_fonts_(ass_renderer_t* priv, const char* default_font, const char* default_family, int fc) +void ass_set_fonts(ASS_Renderer *priv, const char *default_font, + const char *default_family, int fc, const char *config, + int update) { - if (priv->settings.default_font) - free(priv->settings.default_font); - if (priv->settings.default_family) - free(priv->settings.default_family); + free(priv->settings.default_font); + free(priv->settings.default_family); + priv->settings.default_font = default_font ? strdup(default_font) : 0; + priv->settings.default_family = + default_family ? strdup(default_family) : 0; - priv->settings.default_font = default_font ? strdup(default_font) : 0; - priv->settings.default_family = default_family ? strdup(default_family) : 0; - - if (priv->fontconfig_priv) - fontconfig_done(priv->fontconfig_priv); - priv->fontconfig_priv = fontconfig_init(priv->library, priv->ftlibrary, default_family, default_font, fc); - - return !!priv->fontconfig_priv; + if (priv->fontconfig_priv) + fontconfig_done(priv->fontconfig_priv); + priv->fontconfig_priv = + fontconfig_init(priv->library, priv->ftlibrary, default_family, + default_font, fc, config, update); } -int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family) +int ass_fonts_update(ASS_Renderer *render_priv) { - return ass_set_fonts_(priv, default_font, default_family, 1); -} - -int ass_set_fonts_nofc(ass_renderer_t* priv, const char* default_font, const char* default_family) -{ - return ass_set_fonts_(priv, default_font, default_family, 0); + return fontconfig_update(render_priv->fontconfig_priv); } /** * \brief Start a new frame */ -static int ass_start_frame(ass_renderer_t *priv, ass_track_t* track, long long now) +static int +ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track, + long long now) { - ass_renderer = priv; - global_settings = &priv->settings; + ASS_Settings *settings_priv = &render_priv->settings; + CacheStore *cache = &render_priv->cache; + + if (!render_priv->settings.frame_width + && !render_priv->settings.frame_height) + return 1; // library not initialized + + if (render_priv->library != track->library) + return 1; + + free_list_clear(render_priv); + + if (track->n_events == 0) + return 1; // nothing to do - if (!priv->settings.frame_width && !priv->settings.frame_height) - return 1; // library not initialized - - if (track->n_events == 0) - return 1; // nothing to do + render_priv->width = settings_priv->frame_width; + render_priv->height = settings_priv->frame_height; + render_priv->orig_width = + settings_priv->frame_width - settings_priv->left_margin - + settings_priv->right_margin; + render_priv->orig_height = + settings_priv->frame_height - settings_priv->top_margin - + settings_priv->bottom_margin; + render_priv->orig_width_nocrop = + settings_priv->frame_width - FFMAX(settings_priv->left_margin, + 0) - + FFMAX(settings_priv->right_margin, 0); + render_priv->orig_height_nocrop = + settings_priv->frame_height - FFMAX(settings_priv->top_margin, + 0) - + FFMAX(settings_priv->bottom_margin, 0); + render_priv->track = track; + render_priv->time = now; - frame_context.ass_priv = priv; - frame_context.width = global_settings->frame_width; - frame_context.height = global_settings->frame_height; - frame_context.orig_width = global_settings->frame_width - global_settings->left_margin - global_settings->right_margin; - frame_context.orig_height = global_settings->frame_height - global_settings->top_margin - global_settings->bottom_margin; - frame_context.orig_width_nocrop = global_settings->frame_width - - FFMAX(global_settings->left_margin, 0) - - FFMAX(global_settings->right_margin, 0); - frame_context.orig_height_nocrop = global_settings->frame_height - - FFMAX(global_settings->top_margin, 0) - - FFMAX(global_settings->bottom_margin, 0); - frame_context.track = track; - frame_context.time = now; + ass_lazy_track_init(render_priv); + + render_priv->font_scale = settings_priv->font_size_coeff * + render_priv->orig_height / render_priv->track->PlayResY; + if (render_priv->track->ScaledBorderAndShadow) + render_priv->border_scale = + ((double) render_priv->orig_height) / + render_priv->track->PlayResY; + else + render_priv->border_scale = 1.; + + // PAR correction + render_priv->font_scale_x = render_priv->settings.aspect / + render_priv->settings.storage_aspect; + + render_priv->prev_images_root = render_priv->images_root; + render_priv->images_root = 0; - ass_lazy_track_init(); + if (cache->bitmap_cache->cache_size > cache->bitmap_max_size) { + ass_msg(render_priv->library, MSGL_V, + "Hitting hard bitmap cache limit (was: %ld bytes), " + "resetting.", (long) cache->bitmap_cache->cache_size); + cache->bitmap_cache = ass_bitmap_cache_reset(cache->bitmap_cache); + cache->composite_cache = ass_composite_cache_reset( + cache->composite_cache); + ass_free_images(render_priv->prev_images_root); + render_priv->prev_images_root = 0; + } - frame_context.font_scale = global_settings->font_size_coeff * - frame_context.orig_height / frame_context.track->PlayResY; - if (frame_context.track->ScaledBorderAndShadow) - frame_context.border_scale = ((double)frame_context.orig_height) / frame_context.track->PlayResY; - else - frame_context.border_scale = 1.; + if (cache->glyph_cache->count > cache->glyph_max) { + ass_msg(render_priv->library, MSGL_V, + "Hitting hard glyph cache limit (was: %ld glyphs), resetting.", + (long) cache->glyph_cache->count); + cache->glyph_cache = ass_glyph_cache_reset(cache->glyph_cache); + } - frame_context.font_scale_x = 1.; - - priv->prev_images_root = priv->images_root; - priv->images_root = 0; - - return 0; + return 0; } -static int cmp_event_layer(const void* p1, const void* p2) +static int cmp_event_layer(const void *p1, const void *p2) { - ass_event_t* e1 = ((event_images_t*)p1)->event; - ass_event_t* e2 = ((event_images_t*)p2)->event; - if (e1->Layer < e2->Layer) - return -1; - if (e1->Layer > e2->Layer) - return 1; - if (e1->ReadOrder < e2->ReadOrder) - return -1; - if (e1->ReadOrder > e2->ReadOrder) - return 1; - return 0; + ASS_Event *e1 = ((EventImages *) p1)->event; + ASS_Event *e2 = ((EventImages *) p2)->event; + if (e1->Layer < e2->Layer) + return -1; + if (e1->Layer > e2->Layer) + return 1; + if (e1->ReadOrder < e2->ReadOrder) + return -1; + if (e1->ReadOrder > e2->ReadOrder) + return 1; + return 0; } -#define MAX_EVENTS 100 - -static render_priv_t* get_render_priv(ass_event_t* event) +static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv, + ASS_Event *event) { - if (!event->render_priv) - event->render_priv = calloc(1, sizeof(render_priv_t)); - // FIXME: check render_id - if (ass_renderer->render_id != event->render_priv->render_id) { - memset(event->render_priv, 0, sizeof(render_priv_t)); - event->render_priv->render_id = ass_renderer->render_id; - } - return event->render_priv; + if (!event->render_priv) + event->render_priv = calloc(1, sizeof(ASS_RenderPriv)); + if (render_priv->render_id != event->render_priv->render_id) { + memset(event->render_priv, 0, sizeof(ASS_RenderPriv)); + event->render_priv->render_id = render_priv->render_id; + } + + return event->render_priv; } -typedef struct segment_s { - int a, b; // top and height -} segment_t; - -static int overlap(segment_t* s1, segment_t* s2) +static int overlap(Segment *s1, Segment *s2) { - if (s1->a >= s2->b || s2->a >= s1->b) - return 0; - return 1; + if (s1->a >= s2->b || s2->a >= s1->b || + s1->ha >= s2->hb || s2->ha >= s1->hb) + return 0; + return 1; } -static int cmp_segment(const void* p1, const void* p2) +static int cmp_segment(const void *p1, const void *p2) { - return ((segment_t*)p1)->a - ((segment_t*)p2)->a; + return ((Segment *) p1)->a - ((Segment *) p2)->a; } -static void shift_event(event_images_t* ei, int shift) +static void +shift_event(ASS_Renderer *render_priv, EventImages *ei, int shift) { - ass_image_t* cur = ei->imgs; - while (cur) { - cur->dst_y += shift; - // clip top and bottom - if (cur->dst_y < 0) { - int clip = - cur->dst_y; - cur->h -= clip; - cur->bitmap += clip * cur->stride; - cur->dst_y = 0; - } - if (cur->dst_y + cur->h >= frame_context.height) { - int clip = cur->dst_y + cur->h - frame_context.height; - cur->h -= clip; - } - if (cur->h <= 0) { - cur->h = 0; - cur->dst_y = 0; - } - cur = cur->next; - } - ei->top += shift; + ASS_Image *cur = ei->imgs; + while (cur) { + cur->dst_y += shift; + // clip top and bottom + if (cur->dst_y < 0) { + int clip = -cur->dst_y; + cur->h -= clip; + cur->bitmap += clip * cur->stride; + cur->dst_y = 0; + } + if (cur->dst_y + cur->h >= render_priv->height) { + int clip = cur->dst_y + cur->h - render_priv->height; + cur->h -= clip; + } + if (cur->h <= 0) { + cur->h = 0; + cur->dst_y = 0; + } + cur = cur->next; + } + ei->top += shift; } // dir: 1 - move down // -1 - move up -static int fit_segment(segment_t* s, segment_t* fixed, int* cnt, int dir) +static int fit_segment(Segment *s, Segment *fixed, int *cnt, int dir) { - int i; - int shift = 0; + int i; + int shift = 0; - if (dir == 1) // move down - for (i = 0; i < *cnt; ++i) { - if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b) - continue; - shift = fixed[i].b - s->a; - } - else // dir == -1, move up - for (i = *cnt-1; i >= 0; --i) { - if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b) - continue; - shift = fixed[i].a - s->b; - } + if (dir == 1) // move down + for (i = 0; i < *cnt; ++i) { + if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b || + s->hb <= fixed[i].ha || s->ha >= fixed[i].hb) + continue; + shift = fixed[i].b - s->a; + } else // dir == -1, move up + for (i = *cnt - 1; i >= 0; --i) { + if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b || + s->hb <= fixed[i].ha || s->ha >= fixed[i].hb) + continue; + shift = fixed[i].a - s->b; + } - fixed[*cnt].a = s->a + shift; - fixed[*cnt].b = s->b + shift; - (*cnt)++; - qsort(fixed, *cnt, sizeof(segment_t), cmp_segment); + fixed[*cnt].a = s->a + shift; + fixed[*cnt].b = s->b + shift; + fixed[*cnt].ha = s->ha; + fixed[*cnt].hb = s->hb; + (*cnt)++; + qsort(fixed, *cnt, sizeof(Segment), cmp_segment); - return shift; + return shift; } -static void fix_collisions(event_images_t* imgs, int cnt) +static void +fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt) { - segment_t used[MAX_EVENTS]; - int cnt_used = 0; - int i, j; + Segment *used = malloc(cnt * sizeof(*used)); + int cnt_used = 0; + int i, j; - // fill used[] with fixed events - for (i = 0; i < cnt; ++i) { - render_priv_t* priv; - if (!imgs[i].detect_collisions) continue; - priv = get_render_priv(imgs[i].event); - if (priv->height > 0) { // it's a fixed event - segment_t s; - s.a = priv->top; - s.b = priv->top + priv->height; - if (priv->height != imgs[i].height) { // no, it's not - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventHeightHasChanged); - priv->top = 0; - priv->height = 0; - } - for (j = 0; j < cnt_used; ++j) - if (overlap(&s, used + j)) { // no, it's not - priv->top = 0; - priv->height = 0; - } - if (priv->height > 0) { // still a fixed event - used[cnt_used].a = priv->top; - used[cnt_used].b = priv->top + priv->height; - cnt_used ++; - shift_event(imgs + i, priv->top - imgs[i].top); - } - } - } - qsort(used, cnt_used, sizeof(segment_t), cmp_segment); + // fill used[] with fixed events + for (i = 0; i < cnt; ++i) { + ASS_RenderPriv *priv; + if (!imgs[i].detect_collisions) + continue; + priv = get_render_priv(render_priv, imgs[i].event); + if (priv->height > 0) { // it's a fixed event + Segment s; + s.a = priv->top; + s.b = priv->top + priv->height; + s.ha = priv->left; + s.hb = priv->left + priv->width; + if (priv->height != imgs[i].height) { // no, it's not + ass_msg(render_priv->library, MSGL_WARN, + "Warning! Event height has changed"); + priv->top = 0; + priv->height = 0; + priv->left = 0; + priv->width = 0; + } + for (j = 0; j < cnt_used; ++j) + if (overlap(&s, used + j)) { // no, it's not + priv->top = 0; + priv->height = 0; + priv->left = 0; + priv->width = 0; + } + if (priv->height > 0) { // still a fixed event + used[cnt_used].a = priv->top; + used[cnt_used].b = priv->top + priv->height; + used[cnt_used].ha = priv->left; + used[cnt_used].hb = priv->left + priv->width; + cnt_used++; + shift_event(render_priv, imgs + i, priv->top - imgs[i].top); + } + } + } + qsort(used, cnt_used, sizeof(Segment), cmp_segment); - // try to fit other events in free spaces - for (i = 0; i < cnt; ++i) { - render_priv_t* priv; - if (!imgs[i].detect_collisions) continue; - priv = get_render_priv(imgs[i].event); - if (priv->height == 0) { // not a fixed event - int shift; - segment_t s; - s.a = imgs[i].top; - s.b = imgs[i].top + imgs[i].height; - shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction); - if (shift) shift_event(imgs + i, shift); - // make it fixed - priv->top = imgs[i].top; - priv->height = imgs[i].height; - } + // try to fit other events in free spaces + for (i = 0; i < cnt; ++i) { + ASS_RenderPriv *priv; + if (!imgs[i].detect_collisions) + continue; + priv = get_render_priv(render_priv, imgs[i].event); + if (priv->height == 0) { // not a fixed event + int shift; + Segment s; + s.a = imgs[i].top; + s.b = imgs[i].top + imgs[i].height; + s.ha = imgs[i].left; + s.hb = imgs[i].left + imgs[i].width; + shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction); + if (shift) + shift_event(render_priv, imgs + i, shift); + // make it fixed + priv->top = imgs[i].top; + priv->height = imgs[i].height; + priv->left = imgs[i].left; + priv->width = imgs[i].width; + } - } + } + + free(used); } /** @@ -2516,17 +2564,23 @@ * \param i2 second image * \return 0 if identical, 1 if different positions, 2 if different content */ -int ass_image_compare(ass_image_t *i1, ass_image_t *i2) +static int ass_image_compare(ASS_Image *i1, ASS_Image *i2) { - if (i1->w != i2->w) return 2; - if (i1->h != i2->h) return 2; - if (i1->stride != i2->stride) return 2; - if (i1->color != i2->color) return 2; - if (i1->bitmap != i2->bitmap) - return 2; - if (i1->dst_x != i2->dst_x) return 1; - if (i1->dst_y != i2->dst_y) return 1; - return 0; + if (i1->w != i2->w) + return 2; + if (i1->h != i2->h) + return 2; + if (i1->stride != i2->stride) + return 2; + if (i1->color != i2->color) + return 2; + if (i1->bitmap != i2->bitmap) + return 2; + if (i1->dst_x != i2->dst_x) + return 1; + if (i1->dst_y != i2->dst_y) + return 1; + return 0; } /** @@ -2534,35 +2588,36 @@ * \param priv library handle * \return 0 if identical, 1 if different positions, 2 if different content */ -int ass_detect_change(ass_renderer_t *priv) +static int ass_detect_change(ASS_Renderer *priv) { - ass_image_t* img, *img2; - int diff; + ASS_Image *img, *img2; + int diff; - img = priv->prev_images_root; - img2 = priv->images_root; - diff = 0; - while (img && diff < 2) { - ass_image_t* next, *next2; - next = img->next; - if (img2) { - int d = ass_image_compare(img, img2); - if (d > diff) diff = d; - next2 = img2->next; - } else { - // previous list is shorter - diff = 2; - break; - } - img = next; - img2 = next2; - } + img = priv->prev_images_root; + img2 = priv->images_root; + diff = 0; + while (img && diff < 2) { + ASS_Image *next, *next2; + next = img->next; + if (img2) { + int d = ass_image_compare(img, img2); + if (d > diff) + diff = d; + next2 = img2->next; + } else { + // previous list is shorter + diff = 2; + break; + } + img = next; + img2 = next2; + } - // is the previous list longer? - if (img2) - diff = 2; + // is the previous list longer? + if (img2) + diff = 2; - return diff; + return diff; } /** @@ -2574,62 +2629,66 @@ * 0 if identical, 1 if different positions, 2 if different content. * Can be NULL, in that case no detection is performed. */ -ass_image_t* ass_render_frame(ass_renderer_t *priv, ass_track_t* track, long long now, int* detect_change) +ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track, + long long now, int *detect_change) { - int i, cnt, rc; - event_images_t* last; - ass_image_t** tail; + int i, cnt, rc; + EventImages *last; + ASS_Image **tail; - // init frame - rc = ass_start_frame(priv, track, now); - if (rc != 0) - return 0; + // init frame + rc = ass_start_frame(priv, track, now); + if (rc != 0) + return 0; - // render events separately - cnt = 0; - for (i = 0; i < track->n_events; ++i) { - ass_event_t* event = track->events + i; - if ( (event->Start <= now) && (now < (event->Start + event->Duration)) ) { - if (cnt >= priv->eimg_size) { - priv->eimg_size += 100; - priv->eimg = realloc(priv->eimg, priv->eimg_size * sizeof(event_images_t)); - } - rc = ass_render_event(event, priv->eimg + cnt); - if (!rc) ++cnt; - } - } - - // sort by layer - qsort(priv->eimg, cnt, sizeof(event_images_t), cmp_event_layer); + // render events separately + cnt = 0; + for (i = 0; i < track->n_events; ++i) { + ASS_Event *event = track->events + i; + if ((event->Start <= now) + && (now < (event->Start + event->Duration))) { + if (cnt >= priv->eimg_size) { + priv->eimg_size += 100; + priv->eimg = + realloc(priv->eimg, + priv->eimg_size * sizeof(EventImages)); + } + rc = ass_render_event(priv, event, priv->eimg + cnt); + if (!rc) + ++cnt; + } + } - // call fix_collisions for each group of events with the same layer - last = priv->eimg; - for (i = 1; i < cnt; ++i) - if (last->event->Layer != priv->eimg[i].event->Layer) { - fix_collisions(last, priv->eimg + i - last); - last = priv->eimg + i; - } - if (cnt > 0) - fix_collisions(last, priv->eimg + cnt - last); + // sort by layer + qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer); + + // call fix_collisions for each group of events with the same layer + last = priv->eimg; + for (i = 1; i < cnt; ++i) + if (last->event->Layer != priv->eimg[i].event->Layer) { + fix_collisions(priv, last, priv->eimg + i - last); + last = priv->eimg + i; + } + if (cnt > 0) + fix_collisions(priv, last, priv->eimg + cnt - last); - // concat lists - tail = &ass_renderer->images_root; - for (i = 0; i < cnt; ++i) { - ass_image_t* cur = priv->eimg[i].imgs; - while (cur) { - *tail = cur; - tail = &cur->next; - cur = cur->next; - } - } + // concat lists + tail = &priv->images_root; + for (i = 0; i < cnt; ++i) { + ASS_Image *cur = priv->eimg[i].imgs; + while (cur) { + *tail = cur; + tail = &cur->next; + cur = cur->next; + } + } - if (detect_change) - *detect_change = ass_detect_change(priv); + if (detect_change) + *detect_change = ass_detect_change(priv); - // free the previous image list - ass_free_images(priv->prev_images_root); - priv->prev_images_root = 0; + // free the previous image list + ass_free_images(priv->prev_images_root); + priv->prev_images_root = 0; - return ass_renderer->images_root; + return priv->images_root; } -
