From c329c4e047695b8d4a0ac94c78b0e7690783ed07 Mon Sep 17 00:00:00 2001 From: Amar Takhar Date: Sun, 19 Jul 2009 19:33:29 +0000 Subject: [PATCH] Update to 9ad13b from the libass git repo. (http://greg.geekmind.org/viewgit/) This change also includes the ability to split off fontconfig cache updating into it's own step so dialogues can be used to warn the user about the font cache being updated (it can take several mins on slow machines with lots of fonts). Updates #841 Originally committed to SVN as r3176. --- aegisub/libass/ass.h | 202 ++++++++++++--- aegisub/libass/ass_cache.c | 6 + aegisub/libass/ass_cache.h | 1 + aegisub/libass/ass_drawing.c | 22 +- aegisub/libass/ass_drawing.h | 2 +- aegisub/libass/ass_font.c | 5 +- aegisub/libass/ass_fontconfig.c | 37 ++- aegisub/libass/ass_fontconfig.h | 5 +- aegisub/libass/ass_render.c | 300 ++++++++++++++++++++-- aegisub/libass/ass_types.h | 44 ++-- aegisub/src/subtitles_provider_libass.cpp | 2 +- 11 files changed, 512 insertions(+), 114 deletions(-) diff --git a/aegisub/libass/ass.h b/aegisub/libass/ass.h index 0a03ec2a0..b92960a15 100644 --- a/aegisub/libass/ass.h +++ b/aegisub/libass/ass.h @@ -25,125 +25,239 @@ #include #include "ass_types.h" -/// Libass renderer object. Contents are private. +#define LIBASS_VERSION 0x00907000 + +/* Libass renderer object. Contents are private. */ typedef struct ass_renderer_s ass_renderer_t; -/// a linked list of images produced by ass renderer +/* A linked list of images produced by ass renderer. */ typedef struct ass_image_s { - int w, h; // bitmap width/height - int stride; // bitmap stride + int w, h; // Bitmap width/height + int stride; // Bitmap stride unsigned char *bitmap; // 1bpp stride*h alpha buffer - uint32_t color; // RGBA - int dst_x, dst_y; // bitmap placement inside the video frame + uint32_t color; // Bitmap color and alpha, RGBA + int dst_x, dst_y; // Bitmap placement inside the video frame - struct ass_image_s *next; // linked list + struct ass_image_s *next; // Next image, or NULL } ass_image_t; -/// Hinting type -typedef enum { ASS_HINTING_NONE = 0, +/* Hinting type. */ +typedef enum { + ASS_HINTING_NONE = 0, ASS_HINTING_LIGHT, ASS_HINTING_NORMAL, ASS_HINTING_NATIVE } ass_hinting_t; /** - * \brief initialize the library + * \brief Initialize the library. * \return library handle or NULL if failed */ ass_library_t *ass_library_init(void); /** - * \brief finalize the library + * \brief Finalize the library * \param priv library handle */ void ass_library_done(ass_library_t *); /** - * \brief set private font directory + * \brief Set private font directory. * It is used for saving embedded fonts and also in font lookup. + * + * \param priv library handle + * \param fonts_dir private directory for font extraction */ void ass_set_fonts_dir(ass_library_t *priv, const char *fonts_dir); +/** + * \brief Whether fonts should be extracted from track data. + * \param priv library handle + * \param extract whether to extract fonts + */ void ass_set_extract_fonts(ass_library_t *priv, int extract); +/** + * \brief Register style overrides with a library instance. + * The overrides should have the form [Style.]Param=Value, e.g. + * SomeStyle.Font=Arial + * ScaledBorderAndShadow=yes + * + * \param priv library handle + * \param list NULL-terminated list of strings + */ void ass_set_style_overrides(ass_library_t *priv, char **list); +/** + * \brief Explicitly process style overrides for a track. + * \param track track handle + */ void ass_process_force_style(ass_track_t *track); +/** + * \brief Register a callback for debug/info messages. + * If a callback is registered, it is called for every message emitted by + * libass. The callback receives a format string and a list of arguments, + * to be used for the printf family of functions. Additionally, a log level + * from 0 (FATAL errors) to 7 (verbose DEBUG) is passed. Usually, level 5 + * should be used by applications. + * If no callback is set, all messages level < 5 are printed to stderr, + * prefixed with [ass]. + * + * \param priv library handle + * \param msg_cb pointer to callback function + * \param data additional data, will be passed to callback + */ void ass_set_message_cb(ass_library_t *priv, void (*msg_cb)(int, char *, va_list *, void *), void *data); /** - * \brief initialize the renderer + * \brief Initialize the renderer. * \param priv library handle * \return renderer handle or NULL if failed */ ass_renderer_t *ass_renderer_init(ass_library_t *); /** - * \brief finalize the renderer + * \brief Finalize the renderer. * \param priv renderer handle */ void ass_renderer_done(ass_renderer_t *priv); +/** + * \brief Set the frame size in pixels, including margins. + * \param priv renderer handle + * \param w width + * \param h height + */ void ass_set_frame_size(ass_renderer_t *priv, int w, int h); + +/** + * \brief Set frame margins. These values may be negative if pan-and-scan + * is used. + * \param priv renderer handle + * \param t top margin + * \param b bottom margin + * \param l left margin + * \param r right margin + */ void ass_set_margins(ass_renderer_t *priv, int t, int b, int l, int r); + +/** + * \brief Whether margins should be used for placing regular events. + * \param priv renderer handle + * \param use whether to use the margins + */ void ass_set_use_margins(ass_renderer_t *priv, int use); + +/** + * \brief Set aspect ratio parameters. + * \param priv renderer handle + * \param ar physical aspect ratio + * \param par pixel ratio, e.g. width / height of the video + */ void ass_set_aspect_ratio(ass_renderer_t *priv, double ar, double par); + +/** + * \brief Set a fixed font scaling factor. + * \param priv renderer handle + * \param font_scale scaling factor, default is 1.0 + */ void ass_set_font_scale(ass_renderer_t *priv, double font_scale); + +/** + * \brief Set font hinting method. + * \param priv renderer handle + * \param ht hinting method + */ void ass_set_hinting(ass_renderer_t *priv, ass_hinting_t ht); + +/** + * \brief Set line spacing. Will not be scaled with frame size. + * \param priv renderer handle + * \param line_spacing line spacing in pixels + */ void ass_set_line_spacing(ass_renderer_t *priv, double line_spacing); /** - * \brief set font lookup defaults - * \param fc bool, use fontconfig? - * \param config path to fontconfig configuration file, or NULL. Only matters - * if fontconfig is used + * \brief Set font lookup defaults. + * \param fc whether to use fontconfig + * \param config path to fontconfig configuration file, or NULL. Only relevant + * if fontconfig is used. + * \param update whether fontconfig cache should be built/updated now. Only + * relevant if fontconfig is used. */ -int ass_set_fonts(ass_renderer_t *priv, const char *default_font, - const char *default_family, int fc, const char *config); +void ass_set_fonts(ass_renderer_t *priv, const char *default_font, + const char *default_family, int fc, const char *config, + int update); /** - * \brief render a frame, producing a list of ass_image_t - * \param priv library + * \brief Update/build font cache. This needs to be called if it was + * disabled when ass_set_fonts was set. + * + * \param priv renderer handle + * \return success + */ +int ass_fonts_update(ass_renderer_t *priv); + +/** + * \brief Set hard cache limits. Do not set, or set to zero, for reasonable + * defaults. + * + * \param priv renderer handle + * \param glyph_max maximum number of cached glyphs + * \param bitmap_max_size maximum bitmap cache size (in MB) + */ +void ass_set_cache_limits(ass_renderer_t *priv, int glyph_max, + int bitmap_max_size); + +/** + * \brief Render a frame, producing a list of ass_image_t. + * \param priv renderer handle * \param track subtitle track * \param now video timestamp in milliseconds + * \param detect_change will be set to 1 if a change occured compared + * to the last invocation */ ass_image_t *ass_render_frame(ass_renderer_t *priv, ass_track_t *track, long long now, int *detect_change); -// The following functions operate on track objects and do not need an ass_renderer // +/* + * The following functions operate on track objects and do not need + * an ass_renderer + */ /** - * \brief allocate a new empty track object + * \brief Allocate a new empty track object. + * \param library handle * \return pointer to empty track */ ass_track_t *ass_new_track(ass_library_t *); /** - * \brief deallocate track and all its child objects (styles and events) + * \brief Deallocate track and all its child objects (styles and events). * \param track track to deallocate */ void ass_free_track(ass_track_t *track); /** - * \brief allocate new style + * \brief Allocate new style. * \param track track * \return newly allocated style id */ int ass_alloc_style(ass_track_t *track); /** - * \brief allocate new event + * \brief Allocate new event. * \param track track * \return newly allocated event id */ int ass_alloc_event(ass_track_t *track); /** - * \brief delete a style + * \brief Delete a style. * \param track track * \param sid style id * Deallocates style data. Does not modify track->n_styles. @@ -151,7 +265,7 @@ int ass_alloc_event(ass_track_t *track); void ass_free_style(ass_track_t *track, int sid); /** - * \brief delete an event + * \brief Delete an event. * \param track track * \param eid event id * Deallocates event data. Does not modify track->n_events. @@ -167,7 +281,7 @@ void ass_free_event(ass_track_t *track, int eid); void ass_process_data(ass_track_t *track, char *data, int size); /** - * \brief Parse Codec Private section of subtitle stream + * \brief Parse Codec Private section of subtitle stream. * \param track target track * \param data string to parse * \param size length of data @@ -175,19 +289,22 @@ void ass_process_data(ass_track_t *track, char *data, int size); void ass_process_codec_private(ass_track_t *track, char *data, int size); /** - * \brief Parse a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary). + * \brief Parse a chunk of subtitle stream data. In Matroska, + * this contains exactly 1 event (or a commentary). * \param track track * \param data string to parse * \param size length of data * \param timecode starting time of the event (milliseconds) * \param duration duration of the event (milliseconds) -*/ + */ void ass_process_chunk(ass_track_t *track, char *data, int size, long long timecode, long long duration); /** * \brief Read subtitles from file. + * \param library library handle * \param fname file name + * \param codepage encoding (iconv format) * \return newly allocated track */ ass_track_t *ass_read_file(ass_library_t *library, char *fname, @@ -195,22 +312,25 @@ ass_track_t *ass_read_file(ass_library_t *library, char *fname, /** * \brief Read subtitles from memory. - * \param library libass library object + * \param library library handle * \param buf pointer to subtitles text * \param bufsize size of buffer - * \param codepage recode buffer contents from given codepage + * \param codepage encoding (iconv format) * \return newly allocated track */ ass_track_t *ass_read_memory(ass_library_t *library, char *buf, size_t bufsize, char *codepage); /** - * \brief read styles from file into already initialized track + * \brief Read styles from file into already initialized track. + * \param fname file name + * \param codepage encoding (iconv format) * \return 0 on success */ int ass_read_styles(ass_track_t *track, char *fname, char *codepage); /** * \brief Add a memory font. + * \param library library handle * \param name attachment name * \param data binary font data * \param data_size data size @@ -219,18 +339,20 @@ void ass_add_font(ass_library_t *library, char *name, char *data, int data_size); /** - * \brief Remove all fonts stored in ass_library object + * \brief Remove all fonts stored in an ass_library object. + * \param library library handle */ void ass_clear_fonts(ass_library_t *library); /** - * \brief Calculates timeshift from now to the start of some other subtitle event, depending on movement parameter + * \brief Calculates timeshift from now to the start of some other subtitle + * event, depending on movement parameter. * \param track subtitle track - * \param now current time, ms + * \param now current time in milliseconds * \param movement how many events to skip from the one currently displayed * +2 means "the one after the next", -1 means "previous" - * \return timeshift, ms + * \return timeshift in milliseconds */ long long ass_step_sub(ass_track_t *track, long long now, int movement); -#endif /* LIBASS_ASS_H */ +#endif /* LIBASS_ASS_H */ diff --git a/aegisub/libass/ass_cache.c b/aegisub/libass/ass_cache.c index 4c7a3fa53..524a1c83e 100644 --- a/aegisub/libass/ass_cache.c +++ b/aegisub/libass/ass_cache.c @@ -223,6 +223,12 @@ static void bitmap_hash_dtor(void *key, size_t key_size, void *value, void *cache_add_bitmap(hashmap_t *bitmap_cache, bitmap_hash_key_t *key, bitmap_hash_val_t *val) { + // Note: this is only an approximation + if (val->bm_o) + bitmap_cache->cache_size += val->bm_o->w * val->bm_o->h * 3; + else + bitmap_cache->cache_size += val->bm->w * val->bm->h * 3; + return hashmap_insert(bitmap_cache, key, val); } diff --git a/aegisub/libass/ass_cache.h b/aegisub/libass/ass_cache.h index 83c994386..e31d8cf68 100644 --- a/aegisub/libass/ass_cache.h +++ b/aegisub/libass/ass_cache.h @@ -45,6 +45,7 @@ typedef struct hashmap_s { hashmap_item_dtor_t item_dtor; // a destructor for hashmap key/value pairs hashmap_key_compare_t key_compare; hashmap_hash_t hash; + size_t cache_size; // stats int hit_count; int miss_count; diff --git a/aegisub/libass/ass_drawing.c b/aegisub/libass/ass_drawing.c index f75bbd9ad..0b270c1a1 100644 --- a/aegisub/libass/ass_drawing.c +++ b/aegisub/libass/ass_drawing.c @@ -106,7 +106,7 @@ static void drawing_prepare(ass_drawing_t *drawing) * \brief Finish a drawing. This only sets the horizontal advance according * to the glyph's bbox at the moment. */ -static void drawing_finish(ass_drawing_t *drawing) +static void drawing_finish(ass_drawing_t *drawing, int raw_mode) { int i, offset; FT_BBox bbox; @@ -127,6 +127,13 @@ static void drawing_finish(ass_drawing_t *drawing) printf("contour %d\n", ol->contours[i]); #endif + ass_msg(drawing->library, MSGL_V, + "Parsed drawing with %d points and %d contours", ol->n_points, + ol->n_contours); + + if (raw_mode) + return; + FT_Outline_Get_CBox(&drawing->glyph->outline, &bbox); drawing->glyph->root.advance.x = d6_to_d16(bbox.xMax - bbox.xMin); @@ -138,10 +145,6 @@ static void drawing_finish(ass_drawing_t *drawing) drawing->scale_y); for (i = 0; i < ol->n_points; i++) ol->points[i].y += offset; - - ass_msg(drawing->library, MSGL_V, - "Parsed drawing with %d points and %d contours", ol->n_points, - ol->n_contours); } /* @@ -361,7 +364,7 @@ ass_drawing_t *ass_drawing_new(void *fontconfig_priv, ass_font_t *font, ass_drawing_t* drawing; drawing = calloc(1, sizeof(*drawing)); - drawing->text = malloc(DRAWING_INITIAL_SIZE); + drawing->text = calloc(1, DRAWING_INITIAL_SIZE); drawing->size = DRAWING_INITIAL_SIZE; drawing->ftlibrary = lib; @@ -381,6 +384,7 @@ ass_drawing_t *ass_drawing_new(void *fontconfig_priv, ass_font_t *font, */ void ass_drawing_free(ass_drawing_t* drawing) { + FT_Done_Glyph((FT_Glyph) drawing->glyph); free(drawing->text); free(drawing); } @@ -411,7 +415,7 @@ void ass_drawing_hash(ass_drawing_t* drawing) /* * \brief Convert token list to outline. Calls the line and curve evaluators. */ -FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing) +FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing, int raw_mode) { int started = 0; ass_drawing_token_t *token; @@ -474,9 +478,7 @@ FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing) } } - drawing_finish(drawing); + drawing_finish(drawing, raw_mode); drawing_free_tokens(drawing->tokens); return &drawing->glyph; } - - diff --git a/aegisub/libass/ass_drawing.h b/aegisub/libass/ass_drawing.h index dfd68f01b..25cc4a721 100644 --- a/aegisub/libass/ass_drawing.h +++ b/aegisub/libass/ass_drawing.h @@ -72,6 +72,6 @@ ass_drawing_t *ass_drawing_new(void *fontconfig_priv, ass_font_t *font, void ass_drawing_free(ass_drawing_t* drawing); void ass_drawing_add_char(ass_drawing_t* drawing, char symbol); void ass_drawing_hash(ass_drawing_t* drawing); -FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing); +FT_OutlineGlyph *ass_drawing_parse(ass_drawing_t *drawing, int raw_mode); #endif /* LIBASS_DRAWING_H */ diff --git a/aegisub/libass/ass_font.c b/aegisub/libass/ass_font.c index 34ae292c4..35d34cf49 100644 --- a/aegisub/libass/ass_font.c +++ b/aegisub/libass/ass_font.c @@ -268,8 +268,9 @@ void ass_font_get_asc_desc(ass_font_t *font, uint32_t ch, int *asc, for (i = 0; i < font->n_faces; ++i) { FT_Face face = font->faces[i]; if (FT_Get_Char_Index(face, ch)) { - *asc = face->size->metrics.ascender; - *desc = -face->size->metrics.descender; + int y_scale = face->size->metrics.y_scale; + *asc = FT_MulFix(face->ascender, y_scale); + *desc = FT_MulFix(-face->descender, y_scale); return; } } diff --git a/aegisub/libass/ass_fontconfig.c b/aegisub/libass/ass_fontconfig.c index d8d64e229..4eee1975f 100644 --- a/aegisub/libass/ass_fontconfig.c +++ b/aegisub/libass/ass_fontconfig.c @@ -427,11 +427,15 @@ static void process_fontdata(fc_instance_t *priv, ass_library_t *library, * \param ftlibrary freetype library object * \param family default font family * \param path default font path + * \param fc whether fontconfig should be used + * \param config path to a fontconfig configuration file, or NULL + * \param update whether the fontconfig cache should be built/updated * \return pointer to fontconfig private data */ fc_instance_t *fontconfig_init(ass_library_t *library, FT_Library ftlibrary, const char *family, - const char *path, int fc, const char *config) + const char *path, int fc, const char *config, + int update) { int rc; fc_instance_t *priv = calloc(1, sizeof(fc_instance_t)); @@ -444,20 +448,18 @@ fc_instance_t *fontconfig_init(ass_library_t *library, goto exit; } - if (config) { - priv->config = FcConfigCreate(); - rc = FcConfigParseAndLoad(priv->config, (unsigned char *)config, - FcTrue); + if (!config) + config = (char *) FcConfigFilename(NULL); + + priv->config = FcConfigCreate(); + rc = FcConfigParseAndLoad(priv->config, (unsigned char *) config, FcTrue); + if (rc && update) { FcConfigBuildFonts(priv->config); - FcConfigSetCurrent(priv->config); - } else { - rc = FcInit(); - assert(rc); - priv->config = FcConfigGetCurrent(); } if (!rc || !priv->config) { ass_msg(library, MSGL_FATAL, "%s failed", "FcInitLoadConfigAndFonts"); + FcConfigDestroy(priv->config); goto exit; } @@ -507,13 +509,18 @@ fc_instance_t *fontconfig_init(ass_library_t *library, } priv->family_default = family ? strdup(family) : NULL; - exit: +exit: priv->path_default = path ? strdup(path) : NULL; priv->index_default = 0; return priv; } +int fontconfig_update(fc_instance_t *priv) +{ + return FcConfigBuildFonts(priv->config); +} + #else /* CONFIG_FONTCONFIG */ char *fontconfig_select(fc_instance_t *priv, const char *family, @@ -526,7 +533,8 @@ char *fontconfig_select(fc_instance_t *priv, const char *family, fc_instance_t *fontconfig_init(ass_library_t *library, FT_Library ftlibrary, const char *family, - const char *path, int fc) + const char *path, int fc, const char *config, + int update) { fc_instance_t *priv; @@ -540,6 +548,11 @@ fc_instance_t *fontconfig_init(ass_library_t *library, return priv; } +int fontconfig_update(fc_instance_t *priv) +{ + // Do nothing +} + #endif void fontconfig_done(fc_instance_t *priv) diff --git a/aegisub/libass/ass_fontconfig.h b/aegisub/libass/ass_fontconfig.h index 53441cce6..24c164d9d 100644 --- a/aegisub/libass/ass_fontconfig.h +++ b/aegisub/libass/ass_fontconfig.h @@ -23,6 +23,7 @@ #include #include "ass_types.h" +#include "ass.h" #include #include FT_FREETYPE_H @@ -34,11 +35,13 @@ typedef struct fc_instance_s fc_instance_t; fc_instance_t *fontconfig_init(ass_library_t *library, FT_Library ftlibrary, const char *family, - const char *path, int fc, const char *config); + const char *path, int fc, const char *config, + int update); char *fontconfig_select(ass_library_t *library, fc_instance_t *priv, const char *family, int treat_family_as_pattern, unsigned bold, unsigned italic, int *index, uint32_t code); void fontconfig_done(fc_instance_t *priv); +int fontconfig_update(fc_instance_t *priv); #endif /* LIBASS_FONTCONFIG_H */ diff --git a/aegisub/libass/ass_render.c b/aegisub/libass/ass_render.c index ae65119af..f86c73e95 100644 --- a/aegisub/libass/ass_render.c +++ b/aegisub/libass/ass_render.c @@ -44,6 +44,8 @@ #define MAX_BE 127 #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; static int last_render_id = 0; @@ -59,6 +61,11 @@ typedef struct double_vector_s { double y; } double_vector_t; +typedef struct free_list_s { + void *object; + struct free_list_s *next; +} free_list_t; + typedef struct ass_settings_s { int frame_width; int frame_height; @@ -174,6 +181,8 @@ typedef struct render_context_s { double shadow_y; int drawing_mode; // not implemented; when != 0 text is discarded, except for style override tags ass_drawing_t *drawing; // current drawing + ass_drawing_t *clip_drawing;// clip vector + int clip_drawing_mode; // 0 = regular clip, 1 = inverse clip effect_t effect_type; int effect_timing; @@ -199,6 +208,8 @@ typedef struct cache_store_s { hashmap_t *glyph_cache; hashmap_t *bitmap_cache; hashmap_t *composite_cache; + size_t glyph_max; + size_t bitmap_max_size; } cache_store_t; struct ass_renderer_s { @@ -230,6 +241,9 @@ struct ass_renderer_s { render_context_t state; text_info_t text_info; cache_store_t cache; + + free_list_t *free_head; + free_list_t *free_tail; }; struct render_priv_s { @@ -309,6 +323,8 @@ ass_renderer_t *ass_renderer_init(ass_library_t *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; priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL; priv->text_info.max_lines = MAX_LINES_INITIAL; @@ -325,6 +341,28 @@ ass_renderer_t *ass_renderer_init(ass_library_t *library) return priv; } +void ass_set_cache_limits(ass_renderer_t *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_t *render_priv) +{ + if (render_priv->free_head) { + free_list_t *item = render_priv->free_head; + while(item) { + free_list_t *oi = item; + free(item->object); + item = item->next; + free(oi); + } + render_priv->free_head = NULL; + } +} + void ass_renderer_done(ass_renderer_t *render_priv) { ass_font_cache_done(render_priv->cache.font_cache); @@ -349,6 +387,8 @@ void ass_renderer_done(ass_renderer_t *render_priv) free(render_priv->settings.default_font); free(render_priv->settings.default_family); + + free_list_clear(render_priv); } /** @@ -663,6 +703,125 @@ render_overlap(ass_renderer_t *render_priv, ass_image_t **last_tail, cache_add_composite(render_priv->cache.composite_cache, &hk, &chv); } +static void free_list_add(ass_renderer_t *render_priv, void *object) +{ + if (!render_priv->free_head) { + render_priv->free_head = calloc(1, sizeof(free_list_t)); + render_priv->free_head->object = object; + render_priv->free_tail = render_priv->free_head; + } else { + free_list_t *l = calloc(1, sizeof(free_list_t)); + l->object = object; + render_priv->free_tail->next = l; + render_priv->free_tail = render_priv->free_tail->next; + } +} + +/** + * 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 void blend_vector_clip(ass_renderer_t *render_priv, + ass_image_t *head) +{ + FT_Glyph glyph; + FT_BitmapGlyph clip_bm; + ass_image_t *cur; + ass_drawing_t *drawing = render_priv->state.clip_drawing; + + if (!drawing) + return; + + // Rasterize it + FT_Glyph_Copy((FT_Glyph) drawing->glyph, &glyph); + FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1); + clip_bm = (FT_BitmapGlyph) glyph; + clip_bm->top = -clip_bm->top; + + assert(clip_bm->bitmap.pitch >= 0); + + // 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; + + 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; + + // 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); + + // Blend together + memcpy(nbuffer, abuffer, as * ah); + 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; + } + + // Allocate new buffer and add to free list + nbuffer = calloc(as, ah); + free_list_add(render_priv, nbuffer); + + // 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; + } + + // Free clip vector and its bitmap, we don't need it anymore + FT_Done_Glyph(glyph); + ass_drawing_free(render_priv->state.clip_drawing); + render_priv->state.clip_drawing = 0; +} + /** * \brief Convert text_info_t struct to ass_image_t list * Splits glyphs in halves when needed (for \kf karaoke). @@ -760,6 +919,8 @@ static ass_image_t *render_text(ass_renderer_t *render_priv, int dst_x, } *tail = 0; + blend_vector_clip(render_priv, head); + return head; } @@ -1018,6 +1179,53 @@ interpolate_alpha(long long now, return a; } +#define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;} +#define skip(x) if (*p == (x)) ++p; else { return p; } +#define skipopt(x) if (*p == (x)) { ++p; } + +/** + * Parse a vector clip into an outline, using the proper scaling + * parameters. Translate it to correct for screen borders, if needed. + */ +static char *parse_vector_clip(ass_renderer_t *render_priv, char *p) +{ + int scale = 1; + int res = 0; + ass_drawing_t *drawing; + render_priv->state.clip_drawing = ass_drawing_new( + render_priv->fontconfig_priv, + render_priv->state.font, + render_priv->settings.hinting, + render_priv->ftlibrary); + drawing = render_priv->state.clip_drawing; + skipopt('('); + res = mystrtoi(&p, &scale); + skipopt(',') + if (!res) + scale = 1; + drawing->scale = scale; + drawing->scale_x = render_priv->font_scale_x * render_priv->font_scale; + drawing->scale_y = render_priv->font_scale; + while (*p != ')' && *p != '}' && p != 0) + ass_drawing_add_char(drawing, *p++); + skipopt(')'); + ass_drawing_parse(drawing, 1); + // We need to translate the clip according to screen borders + if (render_priv->settings.left_margin != 0 || + render_priv->settings.top_margin != 0) { + FT_Vector trans = { + .x = int_to_d6(render_priv->settings.left_margin), + .y = -int_to_d6(render_priv->settings.top_margin), + }; + FT_Outline_Translate(&drawing->glyph->outline, trans.x, trans.y); + } + ass_msg(render_priv->library, MSGL_DBG2, + "Parsed vector clip: scale %d, scales (%f, %f) string [%s]\n", + scale, drawing->scale_x, drawing->scale_y, drawing->text); + + return p; +} + static void reset_render_context(ass_renderer_t *); /** @@ -1027,9 +1235,6 @@ static void reset_render_context(ass_renderer_t *); */ static char *parse_tag(ass_renderer_t *render_priv, 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)) @@ -1081,15 +1286,16 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) } else if (mystrcmp(&p, "iclip")) { int x0, y0, x1, y1; int res = 1; - skip('('); + char *start = p; + skipopt('('); res &= mystrtoi(&p, &x0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &x1); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y1); - skip(')'); + skipopt(')'); if (res) { render_priv->state.clip_x0 = render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr; @@ -1100,6 +1306,9 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) render_priv->state.clip_y1 = render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr; render_priv->state.clip_mode = 1; + } else if (!render_priv->state.clip_drawing) { + p = parse_vector_clip(render_priv, start); + render_priv->state.clip_drawing_mode = 1; } else render_priv->state.clip_mode = 0; } else if (mystrcmp(&p, "blur")) { @@ -1398,17 +1607,18 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) skip_to(')'); // in case there is some unknown tag or a comment skip(')'); } else if (mystrcmp(&p, "clip")) { + char *start = p; int x0, y0, x1, y1; int res = 1; - skip('('); + skipopt('('); res &= mystrtoi(&p, &x0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y0); - skip(','); + skipopt(','); res &= mystrtoi(&p, &x1); - skip(','); + skipopt(','); res &= mystrtoi(&p, &y1); - skip(')'); + skipopt(')'); if (res) { render_priv->state.clip_x0 = render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr; @@ -1418,6 +1628,10 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr; render_priv->state.clip_y1 = render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr; + // Might be a vector clip + } else if (!render_priv->state.clip_drawing) { + p = parse_vector_clip(render_priv, start); + render_priv->state.clip_drawing_mode = 0; } else { render_priv->state.clip_x0 = 0; render_priv->state.clip_y0 = 0; @@ -1556,6 +1770,7 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr) return p; #undef skip +#undef skipopt #undef skip_to } @@ -1856,7 +2071,7 @@ static void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x, static void stroke_outline_glyph(ass_renderer_t *render_priv, FT_OutlineGlyph *glyph, int sx, int sy) { - if (sx <= 0 || sy <= 0) + if (sx <= 0 && sy <= 0) return; fix_freetype_stroker(*glyph, sx, sy); @@ -1948,7 +2163,7 @@ get_outline_glyph(ass_renderer_t *render_priv, int symbol, } else { glyph_hash_val_t v; if (drawing->hash) { - ass_drawing_parse(drawing); + ass_drawing_parse(drawing, 0); FT_Glyph_Copy((FT_Glyph) drawing->glyph, &info->glyph); } else { info->glyph = @@ -2078,14 +2293,15 @@ static void measure_text(ass_renderer_t *render_priv) text_info_t *text_info = &render_priv->text_info; int cur_line = 0; double max_asc = 0., max_desc = 0.; + glyph_info_t *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) { - max_asc = text_info->lines[cur_line - 1].asc / 2.0; - max_desc = text_info->lines[cur_line - 1].desc / 2.0; + 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; @@ -2101,6 +2317,8 @@ static void measure_text(ass_renderer_t *render_priv) 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 += @@ -2668,6 +2886,7 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event, } } + if (text_info->length == 0) { // no valid symbols in the event; this can be smth like {comment} free_render_context(render_priv); @@ -2719,7 +2938,7 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event, last_glyph--; width = d6_to_double( - last_glyph->pos.x + last_glyph->advance.x - + last_glyph->pos.x + last_glyph->advance.x - first_glyph->pos.x); if (halign == HALIGN_LEFT) { // left aligned, no action shift = 0; @@ -2769,7 +2988,7 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event, } 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; + device_y = scr_y - (bbox.yMax + bbox.yMin) / 2.0; } else { // subtitle double scr_y; if (valign != VALIGN_SUB) @@ -2877,7 +3096,7 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event, 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 = + 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); @@ -2984,8 +3203,9 @@ void ass_set_line_spacing(ass_renderer_t *priv, double line_spacing) priv->settings.line_spacing = line_spacing; } -int ass_set_fonts(ass_renderer_t *priv, const char *default_font, - const char *default_family, int fc, const char *config) +void ass_set_fonts(ass_renderer_t *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); @@ -3002,9 +3222,12 @@ int ass_set_fonts(ass_renderer_t *priv, const char *default_font, fontconfig_done(priv->fontconfig_priv); priv->fontconfig_priv = fontconfig_init(priv->library, priv->ftlibrary, default_family, - default_font, fc, config); + default_font, fc, config, update); +} - return !!priv->fontconfig_priv; +int ass_fonts_update(ass_renderer_t *render_priv) +{ + return fontconfig_update(render_priv->fontconfig_priv); } /** @@ -3014,15 +3237,18 @@ static int ass_start_frame(ass_renderer_t *render_priv, ass_track_t *track, long long now) { - if (render_priv->library != track->library) - return 1; - ass_settings_t *settings_priv = &render_priv->settings; + cache_store_t *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 @@ -3063,6 +3289,24 @@ ass_start_frame(ass_renderer_t *render_priv, ass_track_t *track, render_priv->prev_images_root = render_priv->images_root; render_priv->images_root = 0; + 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; + } + + 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); + } + return 0; } diff --git a/aegisub/libass/ass_types.h b/aegisub/libass/ass_types.h index cdea14ef4..fb74872e3 100644 --- a/aegisub/libass/ass_types.h +++ b/aegisub/libass/ass_types.h @@ -30,7 +30,7 @@ #define HALIGN_CENTER 2 #define HALIGN_RIGHT 3 -/// ass Style: line +/* ASS Style: line */ typedef struct ass_style_s { char *Name; char *FontName; @@ -54,15 +54,16 @@ typedef struct ass_style_s { int MarginL; int MarginR; int MarginV; -// int AlphaLevel; int Encoding; int treat_fontname_as_pattern; } ass_style_t; typedef struct render_priv_s render_priv_t; -/// ass_event_t corresponds to a single Dialogue line -/// Text is stored as-is, style overrides will be parsed later +/* + * ass_event_t corresponds to a single Dialogue line; + * text is stored as-is, style overrides will be parsed later. + */ typedef struct ass_event_s { long long Start; // ms long long Duration; // ms @@ -81,26 +82,31 @@ typedef struct ass_event_s { } ass_event_t; typedef struct parser_priv_s parser_priv_t; - typedef struct ass_library_s ass_library_t; -/// ass track represent either an external script or a matroska subtitle stream (no real difference between them) -/// it can be used in rendering after the headers are parsed (i.e. events format line read) +/* + * ass track represent either an external script or a matroska subtitle stream + * (no real difference between them); it can be used in rendering after the + * headers are parsed (i.e. events format line read). + */ typedef struct ass_track_s { - int n_styles; // amount used - int max_styles; // amount allocated + int n_styles; // amount used + int max_styles; // amount allocated int n_events; int max_events; - ass_style_t *styles; // array of styles, max_styles length, n_styles used - ass_event_t *events; // the same as styles + ass_style_t *styles; // array of styles, max_styles length, n_styles used + ass_event_t *events; // the same as styles - char *style_format; // style format line (everything after "Format: ") - char *event_format; // event format line + char *style_format; // style format line (everything after "Format: ") + char *event_format; // event format line - enum { TRACK_TYPE_UNKNOWN = - 0, TRACK_TYPE_ASS, TRACK_TYPE_SSA } track_type; + enum { + TRACK_TYPE_UNKNOWN = 0, + TRACK_TYPE_ASS, + TRACK_TYPE_SSA + } track_type; - // script header fields + // Script header fields int PlayResX; int PlayResY; double Timer; @@ -108,11 +114,11 @@ typedef struct ass_track_s { char ScaledBorderAndShadow; - int default_style; // index of default style - char *name; // file name in case of external subs, 0 for streams + int default_style; // index of default style + char *name; // file name in case of external subs, 0 for streams ass_library_t *library; parser_priv_t *parser_priv; } ass_track_t; -#endif /* LIBASS_TYPES_H */ +#endif /* LIBASS_TYPES_H */ diff --git a/aegisub/src/subtitles_provider_libass.cpp b/aegisub/src/subtitles_provider_libass.cpp index 1c1b53d87..966d3c00d 100644 --- a/aegisub/src/subtitles_provider_libass.cpp +++ b/aegisub/src/subtitles_provider_libass.cpp @@ -91,7 +91,7 @@ LibassSubtitlesProvider::LibassSubtitlesProvider() { const char *config_path = NULL; #endif - ass_set_fonts(ass_renderer, NULL, "Sans", 1, config_path); + ass_set_fonts(ass_renderer, NULL, "Sans", 1, config_path, 1); }