/** * \brief Init fontconfig. * \param library libass library object * \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 */ FCInstance *fontconfig_init(ASS_Library *library, FT_Library ftlibrary, const char *family, const char *path, int fc, const char *config, int update) { int rc; FCInstance *priv = calloc(1, sizeof(FCInstance)); const char *dir = library->fonts_dir; int i; if (!fc) { ass_msg(library, MSGL_WARN, "Fontconfig disabled, only default font will be used."); goto exit; } priv->config = FcConfigCreate(); rc = FcConfigParseAndLoad(priv->config, (unsigned char *) config, FcTrue); if (!rc) { ass_msg(library, MSGL_WARN, "No usable fontconfig configuration " "file found, using fallback."); FcConfigDestroy(priv->config); priv->config = FcInitLoadConfig(); rc++; } if (rc && update) { FcConfigBuildFonts(priv->config); } if (!rc || !priv->config) { ass_msg(library, MSGL_FATAL, "No valid fontconfig configuration found!"); FcConfigDestroy(priv->config); goto exit; } for (i = 0; i < library->num_fontdata; ++i) process_fontdata(priv, library, ftlibrary, i); if (dir) { ass_msg(library, MSGL_V, "Updating font cache"); rc = FcConfigAppFontAddDir(priv->config, (const FcChar8 *) dir); if (!rc) { ass_msg(library, MSGL_WARN, "%s failed", "FcConfigAppFontAddDir"); } } priv->family_default = family ? strdup(family) : NULL; exit: priv->path_default = path ? strdup(path) : NULL; priv->index_default = 0; return priv; }
/** * \brief Process memory font. * \param priv private data * \param library library object * \param ftlibrary freetype library object * \param idx index of the processed font in library->fontdata * * Builds a FontInfo with FreeType and some table reading. */ static void process_fontdata(ASS_FontProvider *priv, ASS_Library *library, FT_Library ftlibrary, int idx) { int rc; const char *name = library->fontdata[idx].name; const char *data = library->fontdata[idx].data; int data_size = library->fontdata[idx].size; FT_Face face; int face_index, num_faces = 1; for (face_index = 0; face_index < num_faces; ++face_index) { ASS_FontProviderMetaData info; FontDataFT *ft; rc = FT_New_Memory_Face(ftlibrary, (unsigned char *) data, data_size, face_index, &face); if (rc) { ass_msg(library, MSGL_WARN, "Error opening memory font '%s'", name); continue; } num_faces = face->num_faces; charmap_magic(library, face); memset(&info, 0, sizeof(ASS_FontProviderMetaData)); if (get_font_info(ftlibrary, face, &info)) { ass_msg(library, MSGL_WARN, "Error getting metadata for embedded font '%s'", name); FT_Done_Face(face); continue; } ft = calloc(1, sizeof(FontDataFT)); if (ft == NULL) { free_font_info(&info); FT_Done_Face(face); continue; } ft->lib = library; ft->face = face; ft->idx = idx; if (ass_font_provider_add_font(priv, &info, NULL, face_index, ft)) { ass_msg(library, MSGL_WARN, "Failed to add embedded font '%s'", name); } free_font_info(&info); } }
/** * \brief Process memory font. * \param priv private data * \param library library object * \param ftlibrary freetype library object * \param idx index of the processed font in library->fontdata * * Builds a font pattern in memory via FT_New_Memory_Face/FcFreeTypeQueryFace. */ static void process_fontdata(FCInstance *priv, ASS_Library *library, FT_Library ftlibrary, int idx) { int rc; const char *name = library->fontdata[idx].name; const char *data = library->fontdata[idx].data; int data_size = library->fontdata[idx].size; FT_Face face; FcPattern *pattern; FcFontSet *fset; FcBool res; int face_index, num_faces = 1; for (face_index = 0; face_index < num_faces; ++face_index) { ass_msg(library, MSGL_V, "Adding memory font '%s'", name); rc = FT_New_Memory_Face(ftlibrary, (unsigned char *) data, data_size, face_index, &face); if (rc) { ass_msg(library, MSGL_WARN, "Error opening memory font: %s", name); return; } num_faces = face->num_faces; pattern = FcFreeTypeQueryFace(face, (unsigned char *) name, face_index, FcConfigGetBlanks(priv->config)); if (!pattern) { ass_msg(library, MSGL_WARN, "%s failed", "FcFreeTypeQueryFace"); FT_Done_Face(face); return; } fset = FcConfigGetFonts(priv->config, FcSetSystem); // somehow it failes when asked for FcSetApplication if (!fset) { ass_msg(library, MSGL_WARN, "%s failed", "FcConfigGetFonts"); FT_Done_Face(face); return; } res = FcFontSetAdd(fset, pattern); if (!res) { ass_msg(library, MSGL_WARN, "%s failed", "FcFontSetAdd"); FT_Done_Face(face); return; } FT_Done_Face(face); } }
/** * \brief Select a face with the given charcode and add it to ASS_Font * \return index of the new face in font->faces, -1 if failed */ static int add_face(void *fc_priv, ASS_Font *font, uint32_t ch) { char *path; int index; FT_Face face; int error; int mem_idx; if (font->n_faces == ASS_FONT_MAX_FACES) return -1; path = fontconfig_select(font->library, fc_priv, font->desc.family, font->desc.treat_family_as_pattern, font->desc.bold, font->desc.italic, &index, ch); if (!path) return -1; mem_idx = find_font(font->library, path); if (mem_idx >= 0) { error = FT_New_Memory_Face(font->ftlibrary, (unsigned char *) font->library-> fontdata[mem_idx].data, font->library->fontdata[mem_idx].size, index, &face); if (error) { ass_msg(font->library, MSGL_WARN, "Error opening memory font: '%s'", path); free(path); return -1; } } else { error = FT_New_Face(font->ftlibrary, path, index, &face); if (error) { ass_msg(font->library, MSGL_WARN, "Error opening font: '%s', %d", path, index); free(path); return -1; } } charmap_magic(font->library, face); buggy_font_workaround(face); font->faces[font->n_faces++] = face; ass_face_set_size(face, font->size); free(path); return font->n_faces - 1; }
Bitmap *outline_to_bitmap(ASS_Library *library, FT_Library ftlib, FT_Outline *outline, int bord) { Bitmap *bm; int w, h; int error; FT_BBox bbox; FT_Bitmap bitmap; FT_Outline_Get_CBox(outline, &bbox); // move glyph to origin (0, 0) bbox.xMin &= ~63; bbox.yMin &= ~63; FT_Outline_Translate(outline, -bbox.xMin, -bbox.yMin); // bitmap size bbox.xMax = (bbox.xMax + 63) & ~63; bbox.yMax = (bbox.yMax + 63) & ~63; w = (bbox.xMax - bbox.xMin) >> 6; h = (bbox.yMax - bbox.yMin) >> 6; // pen offset bbox.xMin >>= 6; bbox.yMax >>= 6; if (w * h > 8000000) { ass_msg(library, MSGL_WARN, "Glyph bounding box too large: %dx%dpx", w, h); return NULL; } // allocate and set up bitmap bm = alloc_bitmap(w + 2 * bord, h + 2 * bord); bm->left = bbox.xMin - bord; bm->top = -bbox.yMax - bord; bitmap.width = w; bitmap.rows = h; bitmap.pitch = bm->stride; bitmap.buffer = bm->buffer + bord + bm->stride * bord; bitmap.num_grays = 256; bitmap.pixel_mode = FT_PIXEL_MODE_GRAY; // render into target bitmap if ((error = FT_Outline_Get_Bitmap(ftlib, outline, &bitmap))) { ass_msg(library, MSGL_WARN, "Failed to rasterize glyph: %d\n", error); ass_free_bitmap(bm); return NULL; } return bm; }
static Bitmap *glyph_to_bitmap_internal(ASS_Library *library, FT_Glyph glyph, int bord) { FT_BitmapGlyph bg; FT_Bitmap *bit; Bitmap *bm; int w, h; unsigned char *src; unsigned char *dst; int i; int error; if (check_glyph_area(library, glyph)) return 0; error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0); if (error) { ass_msg(library, MSGL_WARN, "FT_Glyph_To_Bitmap error %d", error); return 0; } bg = (FT_BitmapGlyph) glyph; bit = &(bg->bitmap); if (bit->pixel_mode != FT_PIXEL_MODE_GRAY) { ass_msg(library, MSGL_WARN, "Unsupported pixel mode: %d", (int) (bit->pixel_mode)); FT_Done_Glyph(glyph); return 0; } w = bit->width; h = bit->rows; bm = alloc_bitmap(w + 2 * bord, h + 2 * bord); memset(bm->buffer, 0, bm->w * bm->h); bm->left = bg->left - bord; bm->top = -bg->top - bord; src = bit->buffer; dst = bm->buffer + bord + bm->w * bord; for (i = 0; i < h; ++i) { memcpy(dst, src, w); src += bit->pitch; dst += bm->w; } FT_Done_Glyph(glyph); return bm; }
/* * \brief Finish a drawing. This only sets the horizontal advance according * to the outline's bbox at the moment. */ static void drawing_finish(ASS_Drawing *drawing, int raw_mode) { int i, offset; FT_BBox bbox = drawing->cbox; FT_Outline *ol = &drawing->outline; // Close the last contour drawing_close_shape(drawing); if (drawing->library) ass_msg(drawing->library, MSGL_V, "Parsed drawing with %d points and %d contours", ol->n_points, ol->n_contours); if (raw_mode) return; drawing->advance.x = bbox.xMax - bbox.xMin; drawing->desc = double_to_d6(-drawing->pbo * drawing->scale_y); drawing->asc = bbox.yMax - bbox.yMin + drawing->desc; // Place it onto the baseline offset = (bbox.yMax - bbox.yMin) + double_to_d6(-drawing->pbo * drawing->scale_y); for (i = 0; i < ol->n_points; i++) ol->points[i].y += offset; }
Bitmap *outline_to_bitmap(ASS_Renderer *render_priv, ASS_Outline *outline, int bord) { size_t n_points = outline->n_points; if (n_points > SHRT_MAX) { ass_msg(render_priv->library, MSGL_WARN, "Too many outline points: %d", outline->n_points); n_points = SHRT_MAX; } size_t n_contours = FFMIN(outline->n_contours, SHRT_MAX); short contours_small[EFFICIENT_CONTOUR_COUNT]; short *contours = contours_small; short *contours_large = NULL; if (n_contours > EFFICIENT_CONTOUR_COUNT) { contours_large = malloc(n_contours * sizeof(short)); if (!contours_large) return NULL; contours = contours_large; } for (size_t i = 0; i < n_contours; ++i) contours[i] = FFMIN(outline->contours[i], n_points - 1); FT_Outline ftol; ftol.n_points = n_points; ftol.n_contours = n_contours; ftol.points = outline->points; ftol.tags = outline->tags; ftol.contours = contours; ftol.flags = 0; Bitmap *bm = outline_to_bitmap_ft(render_priv, &ftol, bord); free(contours_large); return bm; }
/** * \brief Change border width * * \param render_priv renderer state object * \param info glyph state object */ void change_border(ASS_Renderer *render_priv, double border_x, double border_y) { int bord = 64 * border_x * render_priv->border_scale; if (bord > 0 && border_x == border_y) { if (!render_priv->state.stroker) { int error; error = FT_Stroker_New(render_priv->ftlibrary, &render_priv->state.stroker); if (error) { ass_msg(render_priv->library, MSGL_V, "failed to get stroker"); render_priv->state.stroker = 0; } render_priv->state.stroker_radius = -1.0; } if (render_priv->state.stroker && render_priv->state.stroker_radius != bord) { FT_Stroker_Set(render_priv->state.stroker, bord, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); render_priv->state.stroker_radius = bord; } } else { FT_Stroker_Done(render_priv->state.stroker); render_priv->state.stroker = 0; } }
/** * \brief Find a font. Use default family or path if necessary. * \param priv_ private data * \param family font family * \param treat_family_as_pattern treat family as fontconfig pattern * \param bold font weight value * \param italic font slant value * \param index out: font index inside a file * \param code: the character that should be present in the font, can be 0 * \return font file path */ char *fontconfig_select(ASS_Library *library, FCInstance *priv, const char *family, int treat_family_as_pattern, unsigned bold, unsigned italic, int *index, uint32_t code) { char *res = 0; if (!priv->config) { *index = priv->index_default; res = priv->path_default ? strdup(priv->path_default) : 0; return res; } if (family && *family) res = select_font(library, priv, family, treat_family_as_pattern, bold, italic, index, code); if (!res && priv->family_default) { res = select_font(library, priv, priv->family_default, 0, bold, italic, index, code); if (res) ass_msg(library, MSGL_WARN, "fontconfig_select: Using default " "font family: (%s, %d, %d) -> %s, %d", family, bold, italic, res, *index); } if (!res && priv->path_default) { res = strdup(priv->path_default); *index = priv->index_default; if (res) ass_msg(library, MSGL_WARN, "fontconfig_select: Using default font: " "(%s, %d, %d) -> %s, %d", family, bold, italic, res, *index); } if (!res) { res = select_font(library, priv, "Arial", 0, bold, italic, index, code); if (res) ass_msg(library, MSGL_WARN, "fontconfig_select: Using 'Arial' " "font family: (%s, %d, %d) -> %s, %d", family, bold, italic, res, *index); } if (res) ass_msg(library, MSGL_V, "fontconfig_select: (%s, %d, %d) -> %s, %d", family, bold, italic, res, *index); return res; }
/** * \brief Parse the tail of Dialogue line * \param track track * \param event parsed data goes here * \param str string to parse, zero-terminated * \param n_ignored number of format options to skip at the beginning */ static int process_event_tail(ASS_Track *track, ASS_Event *event, char *str, int n_ignored) { char *token; char *tname; char *p = str; int i; ASS_Event *target = event; char *format = strdup(track->event_format); char *q = format; // format scanning pointer if (track->n_styles == 0) { // add "Default" style to the end // will be used if track does not contain a default style (or even does not contain styles at all) int sid = ass_alloc_style(track); set_default_style(&track->styles[sid]); track->default_style = sid; } for (i = 0; i < n_ignored; ++i) { NEXT(q, tname); } while (1) { NEXT(q, tname); if (strcasecmp(tname, "Text") == 0) { char *last; event->Text = strdup(p); if (*event->Text != 0) { last = event->Text + strlen(event->Text) - 1; if (last >= event->Text && *last == '\r') *last = 0; } ass_msg(track->library, MSGL_DBG2, "Text = %s", event->Text); event->Duration -= event->Start; free(format); return 0; // "Text" is always the last } NEXT(p, token); ALIAS(End, Duration) // temporarily store end timecode in event->Duration if (0) { // cool ;) INTVAL(Layer) STYLEVAL(Style) STRVAL(Name) STRVAL(Effect) INTVAL(MarginL) INTVAL(MarginR) INTVAL(MarginV) TIMEVAL(Start) TIMEVAL(Duration) } } free(format); return 1; }
/** * \brief Print version information */ void ass_shaper_info(ASS_Library *lib) { ass_msg(lib, MSGL_V, "Shaper: FriBidi " FRIBIDI_VERSION " (SIMPLE)" #ifdef CONFIG_HARFBUZZ " HarfBuzz-ng %s (COMPLEX)", hb_version_string() #endif ); }
void *ass_guess_buffer_cp(ASS_Library *library, unsigned char *buffer, int buflen, char *preferred_language, char *fallback) { const char **languages; size_t langcnt; EncaAnalyser analyser; EncaEncoding encoding; char *detected_sub_cp = NULL; int i; languages = enca_get_languages(&langcnt); ass_msg(library, MSGL_V, "ENCA supported languages"); for (i = 0; i < langcnt; i++) { ass_msg(library, MSGL_V, "lang %s", languages[i]); } for (i = 0; i < langcnt; i++) { const char *tmp; if (strcasecmp(languages[i], preferred_language) != 0) continue; analyser = enca_analyser_alloc(languages[i]); encoding = enca_analyse_const(analyser, buffer, buflen); tmp = enca_charset_name(encoding.charset, ENCA_NAME_STYLE_ICONV); if (tmp && encoding.charset != ENCA_CS_UNKNOWN) { detected_sub_cp = strdup(tmp); ass_msg(library, MSGL_INFO, "ENCA detected charset: %s", tmp); } enca_analyser_free(analyser); } free(languages); if (!detected_sub_cp) { detected_sub_cp = strdup(fallback); ass_msg(library, MSGL_INFO, "ENCA detection failed: fallback to %s", fallback); } return detected_sub_cp; }
/** * \brief Init font selector. * \param library libass library object * \param ftlibrary freetype library object * \param family default font family * \param path default font path * \return newly created font selector */ ASS_FontSelector * ass_fontselect_init(ASS_Library *library, FT_Library ftlibrary, const char *family, const char *path, const char *config, ASS_DefaultFontProvider dfp) { ASS_FontSelector *priv = calloc(1, sizeof(ASS_FontSelector)); if (priv == NULL) return NULL; priv->uid = 1; priv->family_default = family ? strdup(family) : NULL; priv->path_default = path ? strdup(path) : NULL; priv->index_default = 0; priv->embedded_provider = ass_embedded_fonts_add_provider(library, priv, ftlibrary); if (priv->embedded_provider == NULL) { ass_msg(library, MSGL_WARN, "failed to create embedded font provider"); } if (dfp >= ASS_FONTPROVIDER_AUTODETECT) { for (int i = 0; font_constructors[i].constructor; i++ ) if (dfp == font_constructors[i].id || dfp == ASS_FONTPROVIDER_AUTODETECT) { priv->default_provider = font_constructors[i].constructor(library, priv, config); if (priv->default_provider) { ass_msg(library, MSGL_INFO, "Using font provider %s", font_constructors[i].name); break; } } if (!priv->default_provider) ass_msg(library, MSGL_WARN, "can't find selected font provider"); } return priv; }
static long long string2timecode(ASS_Library *library, char *p) { unsigned h, m, s, ms; long long tm; int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms); if (res < 4) { ass_msg(library, MSGL_WARN, "Bad timestamp"); return 0; } tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10; return tm; }
static int check_glyph_area(ASS_Library *library, FT_Glyph glyph) { FT_BBox bbox; long long dx, dy; FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox); dx = bbox.xMax - bbox.xMin; dy = bbox.yMax - bbox.yMin; if (dx * dy > 8000000) { ass_msg(library, MSGL_WARN, "Glyph bounding box too large: %dx%dpx", (int) dx, (int) dy); return 1; } else return 0; }
/** * Select a good charmap, prefer Microsoft Unicode charmaps. * Otherwise, let FreeType decide. */ void charmap_magic(ASS_Library *library, FT_Face face) { int i; int ms_cmap = -1; // Search for a Microsoft Unicode cmap for (i = 0; i < face->num_charmaps; ++i) { FT_CharMap cmap = face->charmaps[i]; unsigned pid = cmap->platform_id; unsigned eid = cmap->encoding_id; if (pid == 3 /*microsoft */ && (eid == 1 /*unicode bmp */ || eid == 10 /*full unicode */ )) { FT_Set_Charmap(face, cmap); return; } else if (pid == 3 && ms_cmap < 0) ms_cmap = i; } // Try the first Microsoft cmap if no Microsoft Unicode cmap was found if (ms_cmap >= 0) { FT_CharMap cmap = face->charmaps[ms_cmap]; FT_Set_Charmap(face, cmap); return; } if (!face->charmap) { if (face->num_charmaps == 0) { ass_msg(library, MSGL_WARN, "Font face with no charmaps"); return; } ass_msg(library, MSGL_WARN, "No charmap autodetected, trying the first one"); FT_Set_Charmap(face, face->charmaps[0]); return; } }
/** * \brief find style by name * \param track track * \param name style name * \return index in track->styles * Returnes 0 if no styles found => expects at least 1 style. * Parsing code always adds "Default" style in the end. */ static int lookup_style(ASS_Track *track, char *name) { int i; if (*name == '*') ++name; // FIXME: what does '*' really mean ? for (i = track->n_styles - 1; i >= 0; --i) { if (strcmp(track->styles[i].Name, name) == 0) return i; } i = track->default_style; ass_msg(track->library, MSGL_WARN, "[%p]: Warning: no style named '%s' found, using '%s'", track, name, track->styles[i].Name); return i; // use the first style }
FCInstance *fontconfig_init(ASS_Library *library, FT_Library ftlibrary, const char *family, const char *path, int fc, const char *config, int update) { FCInstance *priv; ass_msg(library, MSGL_WARN, "Fontconfig disabled, only default font will be used."); priv = calloc(1, sizeof(FCInstance)); priv->path_default = path ? strdup(path) : 0; priv->index_default = 0; return priv; }
/** * \brief Change border width * negative value resets border to style value */ void change_border(ASS_Renderer *render_priv, double border_x, double border_y) { int bord; if (!render_priv->state.font) return; if (border_x < 0 && border_y < 0) { if (render_priv->state.style->BorderStyle == 1 || render_priv->state.style->BorderStyle == 3) border_x = border_y = render_priv->state.style->Outline; else border_x = border_y = 1.; } render_priv->state.border_x = border_x; render_priv->state.border_y = border_y; bord = 64 * border_x * render_priv->border_scale; if (bord > 0 && border_x == border_y) { if (!render_priv->state.stroker) { int error; error = FT_Stroker_New(render_priv->ftlibrary, &render_priv->state.stroker); if (error) { ass_msg(render_priv->library, MSGL_V, "failed to get stroker"); render_priv->state.stroker = 0; } } if (render_priv->state.stroker) FT_Stroker_Set(render_priv->state.stroker, bord, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); } else { FT_Stroker_Done(render_priv->state.stroker); render_priv->state.stroker = 0; } }
/* * \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 *drawing, int raw_mode) { int i, offset; FT_BBox bbox; FT_Outline *ol = &drawing->glyph->outline; // Close the last contour drawing_close_shape(drawing); #if 0 // Dump points for (i = 0; i < ol->n_points; i++) { printf("point (%d, %d)\n", (int) ol->points[i].x, (int) ol->points[i].y); } // Dump contours for (i = 0; i < ol->n_contours; i++) 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); drawing->desc = double_to_d6(-drawing->pbo * drawing->scale_y); drawing->asc = bbox.yMax - bbox.yMin + drawing->desc; // Place it onto the baseline offset = (bbox.yMax - bbox.yMin) + double_to_d6(-drawing->pbo * drawing->scale_y); for (i = 0; i < ol->n_points; i++) ol->points[i].y += offset; }
static void load_fonts_from_dir(ASS_Library *library, const char *dir) { DIR *d = opendir(dir); if (!d) return; while (1) { struct dirent *entry = readdir(d); if (!entry) break; if (entry->d_name[0] == '.') continue; char fullname[4096]; snprintf(fullname, sizeof(fullname), "%s/%s", dir, entry->d_name); size_t bufsize = 0; ass_msg(library, MSGL_WARN, "Loading font file '%s'", fullname); void *data = read_file(library, fullname, &bufsize); if (data) { ass_add_font(library, entry->d_name, data, bufsize); free(data); } } closedir(d); }
void hashmap_done(Hashmap *map) { int i; // print stats if (map->count > 0 || map->hit_count + map->miss_count > 0) ass_msg(map->library, MSGL_V, "cache statistics: \n total accesses: %d\n hits: %d\n " "misses: %d\n object count: %d", map->hit_count + map->miss_count, map->hit_count, map->miss_count, map->count); for (i = 0; i < map->nbuckets; ++i) { HashmapItem *item = map->root[i]; while (item) { HashmapItem *next = item->next; map->item_dtor(item->key, map->key_size, item->value, map->value_size); free(item); item = next; } } free(map->root); free(map); }
int strtocolor(ASS_Library *library, char **q, uint32_t *res, int hex) { uint32_t color = 0; int result; char *p = *q; int base = hex ? 16 : 10; if (*p == '&') ++p; else ass_msg(library, MSGL_DBG2, "suspicious color format: \"%s\"\n", p); if (*p == 'H' || *p == 'h') { ++p; result = mystrtou32(&p, 16, &color); } else { result = mystrtou32(&p, base, &color); } { unsigned char *tmp = (unsigned char *) (&color); unsigned char b; b = tmp[0]; tmp[0] = tmp[3]; tmp[3] = b; b = tmp[1]; tmp[1] = tmp[2]; tmp[2] = b; } if (*p == '&') ++p; *q = p; *res = color; return result; }
static Bitmap *outline_to_bitmap_ft(ASS_Renderer *render_priv, FT_Outline *outline, int bord) { Bitmap *bm; int w, h; int error; FT_BBox bbox; FT_Bitmap bitmap; FT_Outline_Get_CBox(outline, &bbox); if (bbox.xMin >= bbox.xMax || bbox.yMin >= bbox.yMax) { bm = alloc_bitmap(2 * bord, 2 * bord); if (!bm) return NULL; bm->left = bm->top = -bord; return bm; } // move glyph to origin (0, 0) bbox.xMin &= ~63; bbox.yMin &= ~63; FT_Outline_Translate(outline, -bbox.xMin, -bbox.yMin); if (bbox.xMax > INT_MAX - 63 || bbox.yMax > INT_MAX - 63) return NULL; // bitmap size bbox.xMax = (bbox.xMax + 63) & ~63; bbox.yMax = (bbox.yMax + 63) & ~63; w = (bbox.xMax - bbox.xMin) >> 6; h = (bbox.yMax - bbox.yMin) >> 6; // pen offset bbox.xMin >>= 6; bbox.yMax >>= 6; if (w < 0 || h < 0 || w > 8000000 / FFMAX(h, 1) || w > INT_MAX - 2 * bord || h > INT_MAX - 2 * bord) { ass_msg(render_priv->library, MSGL_WARN, "Glyph bounding box too large: %dx%dpx", w, h); return NULL; } // allocate and set up bitmap bm = alloc_bitmap(w + 2 * bord, h + 2 * bord); if (!bm) return NULL; bm->left = bbox.xMin - bord; bm->top = -bbox.yMax - bord; bitmap.width = w; bitmap.rows = h; bitmap.pitch = bm->stride; bitmap.buffer = bm->buffer + bord + bm->stride * bord; bitmap.num_grays = 256; bitmap.pixel_mode = FT_PIXEL_MODE_GRAY; // render into target bitmap if ((error = FT_Outline_Get_Bitmap(render_priv->ftlibrary, outline, &bitmap))) { ass_msg(render_priv->library, MSGL_WARN, "Failed to rasterize glyph: %d\n", error); ass_free_bitmap(bm); return NULL; } return bm; }
/** * \brief Select a face with the given charcode and add it to ASS_Font * \return index of the new face in font->faces, -1 if failed */ static int add_face(ASS_FontSelector *fontsel, ASS_Font *font, uint32_t ch) { char *path; char *postscript_name = NULL; int i, index, uid, error; ASS_FontStream stream = { NULL, NULL }; FT_Face face; if (font->n_faces == ASS_FONT_MAX_FACES) return -1; path = ass_font_select(fontsel, font->library, font , &index, &postscript_name, &uid, &stream, ch); if (!path) return -1; for (i = 0; i < font->n_faces; i++) { if (font->faces_uid[i] == uid) { ass_msg(font->library, MSGL_INFO, "Got a font face that already is available! Skipping."); return i; } } if (stream.func) { FT_Open_Args args; FT_Stream ftstream = calloc(1, sizeof(FT_StreamRec)); ASS_FontStream *fs = calloc(1, sizeof(ASS_FontStream)); *fs = stream; ftstream->size = stream.func(stream.priv, NULL, 0, 0); ftstream->read = read_stream_font; ftstream->close = close_stream_font; ftstream->descriptor.pointer = (void *)fs; memset(&args, 0, sizeof(FT_Open_Args)); args.flags = FT_OPEN_STREAM; args.stream = ftstream; error = FT_Open_Face(font->ftlibrary, &args, index, &face); if (error) { ass_msg(font->library, MSGL_WARN, "Error opening memory font: '%s'", path); return -1; } } else { error = FT_New_Face(font->ftlibrary, path, index, &face); if (error) { ass_msg(font->library, MSGL_WARN, "Error opening font: '%s', %d", path, index); return -1; } if (postscript_name && index < 0 && face->num_faces > 0) { // The font provider gave us a post_script name and is not sure // about the face index.. so use the postscript name to find the // correct face_index in the collection! for (int i = 0; i < face->num_faces; i++) { FT_Done_Face(face); error = FT_New_Face(font->ftlibrary, path, i, &face); if (error) { ass_msg(font->library, MSGL_WARN, "Error opening font: '%s', %d", path, i); return -1; } const char *face_psname = FT_Get_Postscript_Name(face); if (face_psname != NULL && strcmp(face_psname, postscript_name) == 0) break; } } } charmap_magic(font->library, face); buggy_font_workaround(face); font->faces[font->n_faces] = face; font->faces_uid[font->n_faces++] = uid; ass_face_set_size(face, font->size); return font->n_faces - 1; }
static char *select_font(ASS_FontSelector *priv, ASS_Library *library, const char *family, unsigned bold, unsigned italic, int *index, char **postscript_name, int *uid, ASS_FontStream *stream, uint32_t code) { ASS_FontProvider *default_provider = priv->default_provider; ASS_FontProviderMetaData meta = {0}; char *result = NULL; bool name_match = false; if (family == NULL) return NULL; ASS_FontProviderMetaData default_meta = { .n_fullname = 1, .fullnames = (char **)&family, }; // Get a list of substitutes if applicable, and use it for matching. if (default_provider && default_provider->funcs.get_substitutions) { default_provider->funcs.get_substitutions(default_provider->priv, family, &meta); } if (!meta.n_fullname) { meta = default_meta; } result = find_font(priv, library, meta, bold, italic, index, postscript_name, uid, stream, code, &name_match); // If no matching font was found, it might not exist in the font list // yet. Call the match_fonts callback to fill in the missing fonts // on demand, and retry the search for a match. if (result == NULL && name_match == false && default_provider && default_provider->funcs.match_fonts) { // TODO: consider changing the API to make more efficient // implementations possible. for (int i = 0; i < meta.n_fullname; i++) { default_provider->funcs.match_fonts(library, default_provider, meta.fullnames[i]); } result = find_font(priv, library, meta, bold, italic, index, postscript_name, uid, stream, code, &name_match); } // cleanup if (meta.fullnames != default_meta.fullnames) { for (int i = 0; i < meta.n_fullname; i++) free(meta.fullnames[i]); free(meta.fullnames); } return result; } /** * \brief Find a font. Use default family or path if necessary. * \param library ASS library handle * \param family font family * \param treat_family_as_pattern treat family as fontconfig pattern * \param bold font weight value * \param italic font slant value * \param index out: font index inside a file * \param code: the character that should be present in the font, can be 0 * \return font file path */ char *ass_font_select(ASS_FontSelector *priv, ASS_Library *library, ASS_Font *font, int *index, char **postscript_name, int *uid, ASS_FontStream *data, uint32_t code) { char *res = 0; const char *family = font->desc.family; unsigned bold = font->desc.bold; unsigned italic = font->desc.italic; ASS_FontProvider *default_provider = priv->default_provider; if (family && *family) res = select_font(priv, library, family, bold, italic, index, postscript_name, uid, data, code); if (!res && priv->family_default) { res = select_font(priv, library, priv->family_default, bold, italic, index, postscript_name, uid, data, code); if (res) ass_msg(library, MSGL_WARN, "fontselect: Using default " "font family: (%s, %d, %d) -> %s, %d, %s", family, bold, italic, res, *index, *postscript_name ? *postscript_name : "(none)"); } if (!res && default_provider && default_provider->funcs.get_fallback) { const char *search_family = family; if (!search_family || !*search_family) search_family = "Arial"; char *fallback_family = default_provider->funcs.get_fallback( default_provider->priv, search_family, code); if (fallback_family) { res = select_font(priv, library, fallback_family, bold, italic, index, postscript_name, uid, data, code); free(fallback_family); } } if (!res && priv->path_default) { res = priv->path_default; *index = priv->index_default; ass_msg(library, MSGL_WARN, "fontselect: Using default font: " "(%s, %d, %d) -> %s, %d, %s", family, bold, italic, priv->path_default, *index, *postscript_name ? *postscript_name : "(none)"); } if (res) ass_msg(library, MSGL_INFO, "fontselect: (%s, %d, %d) -> %s, %d, %s", family, bold, italic, res, *index, *postscript_name ? *postscript_name : "(none)"); return res; }
/** * \brief Low-level font selection. * \param priv private data * \param family font family * \param treat_family_as_pattern treat family as fontconfig pattern * \param bold font weight value * \param italic font slant value * \param index out: font index inside a file * \param code: the character that should be present in the font, can be 0 * \return font file path */ static char *select_font(ASS_Library *library, FCInstance *priv, const char *family, int treat_family_as_pattern, unsigned bold, unsigned italic, int *index, uint32_t code) { FcBool rc; FcResult result; FcPattern *pat = NULL, *rpat = NULL; int r_index, r_slant, r_weight; FcChar8 *r_family, *r_style, *r_file, *r_fullname; FcBool r_outline, r_embolden; FcCharSet *r_charset; FcFontSet *ffullname = NULL, *fsorted = NULL, *fset = NULL; int curf; char *retval = NULL; int family_cnt = 0; *index = 0; if (treat_family_as_pattern) pat = FcNameParse((const FcChar8 *) family); else pat = FcPatternCreate(); if (!pat) goto error; if (!treat_family_as_pattern) { FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *) family); // In SSA/ASS fonts are sometimes referenced by their "full name", // which is usually a concatenation of family name and font // style (ex. Ottawa Bold). Full name is available from // FontConfig pattern element FC_FULLNAME, but it is never // used for font matching. // Therefore, I'm removing words from the end of the name one // by one, and adding shortened names to the pattern. It seems // that the first value (full name in this case) has // precedence in matching. // An alternative approach could be to reimplement FcFontSort // using FC_FULLNAME instead of FC_FAMILY. family_cnt = 1; { char *s = strdup(family); char *p = s + strlen(s); while (--p > s) if (*p == ' ' || *p == '-') { *p = '\0'; FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *) s); ++family_cnt; } free(s); } } FcPatternAddBool(pat, FC_OUTLINE, FcTrue); FcPatternAddInteger(pat, FC_SLANT, italic); FcPatternAddInteger(pat, FC_WEIGHT, bold); FcDefaultSubstitute(pat); rc = FcConfigSubstitute(priv->config, pat, FcMatchPattern); if (!rc) goto error; /* Fontconfig defaults include a language setting, which it sets based on * some environment variables or defaults to "en". Unset this as we don't * know the real language, and because some some attached fonts lack * non-ascii characters included in fontconfig's list of characters * required for English support and therefore don't match the lang=en * criterion. */ FcPatternDel(pat, "lang"); fsorted = FcFontSort(priv->config, pat, FcTrue, NULL, &result); ffullname = match_fullname(library, priv, family, bold, italic); if (!fsorted || !ffullname) goto error; fset = FcFontSetCreate(); for (curf = 0; curf < ffullname->nfont; ++curf) { FcPattern *curp = ffullname->fonts[curf]; FcPatternReference(curp); FcFontSetAdd(fset, curp); } for (curf = 0; curf < fsorted->nfont; ++curf) { FcPattern *curp = fsorted->fonts[curf]; FcPatternReference(curp); FcFontSetAdd(fset, curp); } for (curf = 0; curf < fset->nfont; ++curf) { FcPattern *curp = fset->fonts[curf]; result = FcPatternGetBool(curp, FC_OUTLINE, 0, &r_outline); if (result != FcResultMatch) continue; if (r_outline != FcTrue) continue; if (!code) break; result = FcPatternGetCharSet(curp, FC_CHARSET, 0, &r_charset); if (result != FcResultMatch) continue; if (FcCharSetHasChar(r_charset, code)) break; } if (curf >= fset->nfont) goto error; if (!treat_family_as_pattern) { // Remove all extra family names from original pattern. // After this, FcFontRenderPrepare will select the most relevant family // name in case there are more than one of them. for (; family_cnt > 1; --family_cnt) FcPatternRemove(pat, FC_FAMILY, family_cnt - 1); } rpat = FcFontRenderPrepare(priv->config, pat, fset->fonts[curf]); if (!rpat) goto error; result = FcPatternGetInteger(rpat, FC_INDEX, 0, &r_index); if (result != FcResultMatch) goto error; *index = r_index; result = FcPatternGetString(rpat, FC_FILE, 0, &r_file); if (result != FcResultMatch) goto error; retval = strdup((const char *) r_file); result = FcPatternGetString(rpat, FC_FAMILY, 0, &r_family); if (result != FcResultMatch) r_family = NULL; result = FcPatternGetString(rpat, FC_FULLNAME, 0, &r_fullname); if (result != FcResultMatch) r_fullname = NULL; if (!treat_family_as_pattern && !(r_family && strcasecmp((const char *) r_family, family) == 0) && !(r_fullname && strcasecmp((const char *) r_fullname, family) == 0)) { char *fallback = (char *) (r_fullname ? r_fullname : r_family); if (code) { ass_msg(library, MSGL_WARN, "fontconfig: cannot find glyph U+%04X in font '%s', falling back to '%s'", (unsigned int)code, family, fallback); } else { ass_msg(library, MSGL_WARN, "fontconfig: cannot find font '%s', falling back to '%s'", family, fallback); } } result = FcPatternGetString(rpat, FC_STYLE, 0, &r_style); if (result != FcResultMatch) r_style = NULL; result = FcPatternGetInteger(rpat, FC_SLANT, 0, &r_slant); if (result != FcResultMatch) r_slant = 0; result = FcPatternGetInteger(rpat, FC_WEIGHT, 0, &r_weight); if (result != FcResultMatch) r_weight = 0; result = FcPatternGetBool(rpat, FC_EMBOLDEN, 0, &r_embolden); if (result != FcResultMatch) r_embolden = 0; ass_msg(library, MSGL_V, "Font info: family '%s', style '%s', fullname '%s'," " slant %d, weight %d%s", (const char *) r_family, (const char *) r_style, (const char *) r_fullname, r_slant, r_weight, r_embolden ? ", embolden" : ""); error: if (pat) FcPatternDestroy(pat); if (rpat) FcPatternDestroy(rpat); if (fsorted) FcFontSetDestroy(fsorted); if (ffullname) FcFontSetDestroy(ffullname); if (fset) FcFontSetDestroy(fset); return retval; }
Bitmap *outline_to_bitmap(ASS_Renderer *render_priv, ASS_Outline *outline, int bord) { ASS_Rasterizer *rst = &render_priv->rasterizer; if (!rasterizer_set_outline(rst, outline)) { ass_msg(render_priv->library, MSGL_WARN, "Failed to process glyph outline!\n"); return NULL; } if (bord < 0 || bord > INT_MAX / 2) return NULL; if (rst->x_min >= rst->x_max || rst->y_min >= rst->y_max) { Bitmap *bm = alloc_bitmap(2 * bord, 2 * bord); if (!bm) return NULL; bm->left = bm->top = -bord; return bm; } if (rst->x_max > INT_MAX - 63 || rst->y_max > INT_MAX - 63) return NULL; int x_min = rst->x_min >> 6; int y_min = rst->y_min >> 6; int x_max = (rst->x_max + 63) >> 6; int y_max = (rst->y_max + 63) >> 6; int w = x_max - x_min; int h = y_max - y_min; int mask = (1 << rst->tile_order) - 1; if (w < 0 || h < 0 || w > 8000000 / FFMAX(h, 1) || w > INT_MAX - (2 * bord + mask) || h > INT_MAX - (2 * bord + mask)) { ass_msg(render_priv->library, MSGL_WARN, "Glyph bounding box too large: %dx%dpx", w, h); return NULL; } int tile_w = (w + 2 * bord + mask) & ~mask; int tile_h = (h + 2 * bord + mask) & ~mask; Bitmap *bm = alloc_bitmap(tile_w, tile_h); if (!bm) return NULL; bm->left = x_min - bord; bm->top = y_min - bord; int offs = bord & ~mask; if (!rasterizer_fill(rst, bm->buffer + offs * (bm->stride + 1), x_min - bord + offs, y_min - bord + offs, ((w + bord + mask) & ~mask) - offs, ((h + bord + mask) & ~mask) - offs, bm->stride)) { ass_msg(render_priv->library, MSGL_WARN, "Failed to rasterize glyph!\n"); ass_free_bitmap(bm); return NULL; } return bm; }