2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
4 * This file is part of libass.
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 #include FT_FREETYPE_H
24 #include FT_SYNTHESIS_H
26 #include FT_TRUETYPE_TABLES_H
31 #include "ass_library.h"
33 #include "ass_fontconfig.h"
34 #include "ass_utils.h"
35 #include "ass_shaper.h"
37 #define VERTICAL_LOWER_BOUND 0x02f1
40 * Select a good charmap, prefer Microsoft Unicode charmaps.
41 * Otherwise, let FreeType decide.
43 static void charmap_magic(ASS_Library *library, FT_Face face)
48 // Search for a Microsoft Unicode cmap
49 for (i = 0; i < face->num_charmaps; ++i) {
50 FT_CharMap cmap = face->charmaps[i];
51 unsigned pid = cmap->platform_id;
52 unsigned eid = cmap->encoding_id;
53 if (pid == 3 /*microsoft */
54 && (eid == 1 /*unicode bmp */
55 || eid == 10 /*full unicode */ )) {
56 FT_Set_Charmap(face, cmap);
58 } else if (pid == 3 && ms_cmap < 0)
62 // Try the first Microsoft cmap if no Microsoft Unicode cmap was found
64 FT_CharMap cmap = face->charmaps[ms_cmap];
65 FT_Set_Charmap(face, cmap);
70 if (face->num_charmaps == 0) {
71 ass_msg(library, MSGL_WARN, "Font face with no charmaps");
74 ass_msg(library, MSGL_WARN,
75 "No charmap autodetected, trying the first one");
76 FT_Set_Charmap(face, face->charmaps[0]);
82 * \brief find a memory font by name
84 static int find_font(ASS_Library *library, char *name)
87 for (i = 0; i < library->num_fontdata; ++i)
88 if (strcasecmp(name, library->fontdata[i].name) == 0)
93 static void buggy_font_workaround(FT_Face face)
95 // Some fonts have zero Ascender/Descender fields in 'hhea' table.
96 // In this case, get the information from 'os2' table or, as
97 // a last resort, from face.bbox.
98 if (face->ascender + face->descender == 0 || face->height == 0) {
99 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
101 face->ascender = os2->sTypoAscender;
102 face->descender = os2->sTypoDescender;
103 face->height = face->ascender - face->descender;
105 face->ascender = face->bbox.yMax;
106 face->descender = face->bbox.yMin;
107 face->height = face->ascender - face->descender;
113 * \brief Select a face with the given charcode and add it to ASS_Font
114 * \return index of the new face in font->faces, -1 if failed
116 static int add_face(void *fc_priv, ASS_Font *font, uint32_t ch)
124 if (font->n_faces == ASS_FONT_MAX_FACES)
128 fontconfig_select(font->library, fc_priv, font->desc.family,
129 font->desc.treat_family_as_pattern,
130 font->desc.bold, font->desc.italic, &index, ch);
134 mem_idx = find_font(font->library, path);
137 FT_New_Memory_Face(font->ftlibrary,
138 (unsigned char *) font->library->
139 fontdata[mem_idx].data,
140 font->library->fontdata[mem_idx].size, index,
143 ass_msg(font->library, MSGL_WARN,
144 "Error opening memory font: '%s'", path);
149 error = FT_New_Face(font->ftlibrary, path, index, &face);
151 ass_msg(font->library, MSGL_WARN,
152 "Error opening font: '%s', %d", path, index);
157 charmap_magic(font->library, face);
158 buggy_font_workaround(face);
160 font->faces[font->n_faces++] = face;
161 ass_face_set_size(face, font->size);
163 return font->n_faces - 1;
167 * \brief Create a new ASS_Font according to "desc" argument
169 ASS_Font *ass_font_new(Cache *font_cache, ASS_Library *library,
170 FT_Library ftlibrary, void *fc_priv,
177 fontp = ass_cache_get(font_cache, desc);
181 font.library = library;
182 font.ftlibrary = ftlibrary;
183 font.shaper_priv = NULL;
185 font.desc.family = strdup(desc->family);
186 font.desc.treat_family_as_pattern = desc->treat_family_as_pattern;
187 font.desc.bold = desc->bold;
188 font.desc.italic = desc->italic;
189 font.desc.vertical = desc->vertical;
191 font.scale_x = font.scale_y = 1.;
192 font.v.x = font.v.y = 0;
195 error = add_face(fc_priv, &font, 0);
197 free(font.desc.family);
200 return ass_cache_put(font_cache, &font.desc, &font);
204 * \brief Set font transformation matrix and shift vector
206 void ass_font_set_transform(ASS_Font *font, double scale_x,
207 double scale_y, FT_Vector *v)
209 font->scale_x = scale_x;
210 font->scale_y = scale_y;
217 void ass_face_set_size(FT_Face face, double size)
219 TT_HoriHeader *hori = FT_Get_Sfnt_Table(face, ft_sfnt_hhea);
220 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
222 FT_Size_RequestRec rq;
223 FT_Size_Metrics *m = &face->size->metrics;
224 // VSFilter uses metrics from TrueType OS/2 table
225 // The idea was borrowed from asa (http://asa.diac24.net)
227 int hori_height = hori->Ascender - hori->Descender;
228 int os2_height = os2->usWinAscent + os2->usWinDescent;
229 if (hori_height && os2_height)
230 mscale = (double) hori_height / os2_height;
232 memset(&rq, 0, sizeof(rq));
233 rq.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
235 rq.height = double_to_d6(size * mscale);
236 rq.horiResolution = rq.vertResolution = 0;
237 FT_Request_Size(face, &rq);
238 m->ascender /= mscale;
239 m->descender /= mscale;
244 * \brief Set font size
246 void ass_font_set_size(ASS_Font *font, double size)
249 if (font->size != size) {
251 for (i = 0; i < font->n_faces; ++i)
252 ass_face_set_size(font->faces[i], size);
257 * \brief Get maximal font ascender and descender.
258 * \param ch character code
259 * The values are extracted from the font face that provides glyphs for the given character
261 void ass_font_get_asc_desc(ASS_Font *font, uint32_t ch, int *asc,
265 for (i = 0; i < font->n_faces; ++i) {
266 FT_Face face = font->faces[i];
267 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
268 if (FT_Get_Char_Index(face, ch)) {
269 int y_scale = face->size->metrics.y_scale;
271 *asc = FT_MulFix(os2->usWinAscent, y_scale);
272 *desc = FT_MulFix(os2->usWinDescent, y_scale);
274 *asc = FT_MulFix(face->ascender, y_scale);
275 *desc = FT_MulFix(-face->descender, y_scale);
285 * Strike a glyph with a horizontal line; it's possible to underline it
286 * and/or strike through it. For the line's position and size, truetype
287 * tables are consulted. Obviously this relies on the data in the tables
291 static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
292 FT_Glyph glyph, int under, int through)
294 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
295 TT_Postscript *ps = FT_Get_Sfnt_Table(face, ft_sfnt_post);
296 FT_Outline *ol = &((FT_OutlineGlyph) glyph)->outline;
297 int bear, advance, y_scale, i, dir;
299 if (!under && !through)
303 i = (under ? 4 : 0) + (through ? 4 : 0);
304 ol->points = realloc(ol->points, sizeof(FT_Vector) *
306 ol->tags = realloc(ol->tags, ol->n_points + i);
307 i = !!under + !!through;
308 ol->contours = realloc(ol->contours, sizeof(short) *
309 (ol->n_contours + i));
311 // If the bearing is negative, the glyph starts left of the current
313 bear = FFMIN(face->glyph->metrics.horiBearingX, 0);
314 // We're adding half a pixel to avoid small gaps
315 advance = d16_to_d6(glyph->advance.x) + 32;
316 y_scale = face->size->metrics.y_scale;
318 // Reverse drawing direction for non-truetype fonts
319 dir = FT_Outline_Get_Orientation(ol);
321 // Add points to the outline
324 pos = FT_MulFix(ps->underlinePosition, y_scale * font->scale_y);
325 size = FT_MulFix(ps->underlineThickness,
326 y_scale * font->scale_y / 2);
328 if (pos > 0 || size <= 0)
331 FT_Vector points[4] = {
332 {.x = bear, .y = pos + size},
333 {.x = advance, .y = pos + size},
334 {.x = advance, .y = pos - size},
335 {.x = bear, .y = pos - size},
338 if (dir == FT_ORIENTATION_TRUETYPE) {
339 for (i = 0; i < 4; i++) {
340 ol->points[ol->n_points] = points[i];
341 ol->tags[ol->n_points++] = 1;
344 for (i = 3; i >= 0; i--) {
345 ol->points[ol->n_points] = points[i];
346 ol->tags[ol->n_points++] = 1;
350 ol->contours[ol->n_contours++] = ol->n_points - 1;
353 if (through && os2) {
355 pos = FT_MulFix(os2->yStrikeoutPosition, y_scale * font->scale_y);
356 size = FT_MulFix(os2->yStrikeoutSize, y_scale * font->scale_y / 2);
358 if (pos < 0 || size <= 0)
361 FT_Vector points[4] = {
362 {.x = bear, .y = pos + size},
363 {.x = advance, .y = pos + size},
364 {.x = advance, .y = pos - size},
365 {.x = bear, .y = pos - size},
368 if (dir == FT_ORIENTATION_TRUETYPE) {
369 for (i = 0; i < 4; i++) {
370 ol->points[ol->n_points] = points[i];
371 ol->tags[ol->n_points++] = 1;
374 for (i = 3; i >= 0; i--) {
375 ol->points[ol->n_points] = points[i];
376 ol->tags[ol->n_points++] = 1;
380 ol->contours[ol->n_contours++] = ol->n_points - 1;
386 void outline_copy(FT_Library lib, FT_Outline *source, FT_Outline **dest)
388 if (source == NULL) {
392 *dest = calloc(1, sizeof(**dest));
394 FT_Outline_New(lib, source->n_points, source->n_contours, *dest);
395 FT_Outline_Copy(source, *dest);
398 void outline_free(FT_Library lib, FT_Outline *outline)
401 FT_Outline_Done(lib, outline);
406 * Slightly embold a glyph without touching its metrics
408 static void ass_glyph_embolden(FT_GlyphSlot slot)
412 if (slot->format != FT_GLYPH_FORMAT_OUTLINE)
415 str = FT_MulFix(slot->face->units_per_EM,
416 slot->face->size->metrics.y_scale) / 64;
418 FT_Outline_Embolden(&slot->outline, str);
422 * \brief Get glyph and face index
423 * Finds a face that has the requested codepoint and returns both face
426 int ass_font_get_index(void *fcpriv, ASS_Font *font, uint32_t symbol,
427 int *face_index, int *glyph_index)
439 // Handle NBSP like a regular space when rendering the glyph
442 if (font->n_faces == 0) {
447 // try with the requested face
448 if (*face_index < font->n_faces) {
449 face = font->faces[*face_index];
450 index = FT_Get_Char_Index(face, symbol);
453 // not found in requested face, try all others
454 for (i = 0; i < font->n_faces && index == 0; ++i) {
455 face = font->faces[i];
456 index = FT_Get_Char_Index(face, symbol);
461 #ifdef CONFIG_FONTCONFIG
464 ass_msg(font->library, MSGL_INFO,
465 "Glyph 0x%X not found, selecting one more "
466 "font for (%s, %d, %d)", symbol, font->desc.family,
467 font->desc.bold, font->desc.italic);
468 face_idx = *face_index = add_face(fcpriv, font, symbol);
470 face = font->faces[face_idx];
471 index = FT_Get_Char_Index(face, symbol);
472 if (index == 0 && face->num_charmaps > 0) {
474 ass_msg(font->library, MSGL_WARN,
475 "Glyph 0x%X not found, broken font? Trying all charmaps", symbol);
476 for (i = 0; i < face->num_charmaps; i++) {
477 FT_Set_Charmap(face, face->charmaps[i]);
478 if ((index = FT_Get_Char_Index(face, symbol)) != 0) break;
482 ass_msg(font->library, MSGL_ERR,
483 "Glyph 0x%X not found in font for (%s, %d, %d)",
484 symbol, font->desc.family, font->desc.bold,
490 // FIXME: make sure we have a valid face_index. this is a HACK.
491 *face_index = FFMAX(*face_index, 0);
492 *glyph_index = index;
499 * \param ch character code
501 FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
502 uint32_t ch, int face_index, int index,
503 ASS_Hinting hinting, int deco)
507 FT_Face face = font->faces[face_index];
509 int vertical = font->desc.vertical;
511 flags = FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
512 | FT_LOAD_IGNORE_TRANSFORM;
514 case ASS_HINTING_NONE:
515 flags |= FT_LOAD_NO_HINTING;
517 case ASS_HINTING_LIGHT:
518 flags |= FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT;
520 case ASS_HINTING_NORMAL:
521 flags |= FT_LOAD_FORCE_AUTOHINT;
523 case ASS_HINTING_NATIVE:
527 error = FT_Load_Glyph(face, index, flags);
529 ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
533 if (!(face->style_flags & FT_STYLE_FLAG_ITALIC) &&
534 (font->desc.italic > 55)) {
535 FT_GlyphSlot_Oblique(face->glyph);
538 if (!(face->style_flags & FT_STYLE_FLAG_BOLD) &&
539 (font->desc.bold > 80)) {
540 ass_glyph_embolden(face->glyph);
542 error = FT_Get_Glyph(face->glyph, &glyph);
544 ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
549 // Rotate glyph, if needed
550 if (vertical && ch >= VERTICAL_LOWER_BOUND) {
551 FT_Matrix m = { 0, double_to_d16(-1.0), double_to_d16(1.0), 0 };
552 FT_Outline_Transform(&((FT_OutlineGlyph) glyph)->outline, &m);
553 FT_Outline_Translate(&((FT_OutlineGlyph) glyph)->outline,
554 face->glyph->metrics.vertAdvance,
556 glyph->advance.x = face->glyph->linearVertAdvance;
559 // Apply scaling and shift
560 FT_Matrix scale = { double_to_d16(font->scale_x), 0, 0,
561 double_to_d16(font->scale_y) };
562 FT_Outline *outl = &((FT_OutlineGlyph) glyph)->outline;
563 FT_Outline_Transform(outl, &scale);
564 FT_Outline_Translate(outl, font->v.x, font->v.y);
565 glyph->advance.x *= font->scale_x;
567 ass_strike_outline_glyph(face, font, glyph, deco & DECO_UNDERLINE,
568 deco & DECO_STRIKETHROUGH);
574 * \brief Get kerning for the pair of glyphs.
576 FT_Vector ass_font_get_kerning(ASS_Font *font, uint32_t c1, uint32_t c2)
578 FT_Vector v = { 0, 0 };
581 if (font->desc.vertical)
584 for (i = 0; i < font->n_faces; ++i) {
585 FT_Face face = font->faces[i];
586 int i1 = FT_Get_Char_Index(face, c1);
587 int i2 = FT_Get_Char_Index(face, c2);
589 if (FT_HAS_KERNING(face))
590 FT_Get_Kerning(face, i1, i2, FT_KERNING_DEFAULT, &v);
593 if (i1 || i2) // these glyphs are from different font faces, no kerning information
600 * \brief Deallocate ASS_Font
602 void ass_font_free(ASS_Font *font)
605 for (i = 0; i < font->n_faces; ++i)
607 FT_Done_Face(font->faces[i]);
608 if (font->shaper_priv)
609 ass_shaper_font_data_free(font->shaper_priv);
610 free(font->desc.family);
615 * \brief Calculate the cbox of a series of points
618 get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end)
620 box->xMin = box->yMin = INT_MAX;
621 box->xMax = box->yMax = INT_MIN;
624 for (i = start; i <= end; i++) {
625 box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin;
626 box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax;
627 box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin;
628 box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax;
633 * \brief Determine winding direction of a contour
634 * \return direction; 0 = clockwise
636 static int get_contour_direction(FT_Vector *points, int start, int end)
640 int x = points[start].x;
641 int y = points[start].y;
642 for (i = start + 1; i <= end; i++) {
643 sum += x * (points[i].y - y) - y * (points[i].x - x);
647 sum += x * (points[start].y - y) - y * (points[start].x - x);
652 * \brief Apply fixups to please the FreeType stroker and improve the
653 * rendering result, especially in case the outline has some anomalies.
654 * At the moment, the following fixes are done:
656 * 1. Reverse contours that have "inside" winding direction but are not
657 * contained in any other contours' cbox.
658 * 2. Remove "inside" contours depending on border size, so that large
659 * borders do not reverse the winding direction, which leads to "holes"
660 * inside the border. The inside will be filled by the border of the
661 * outside contour anyway in this case.
663 * \param outline FreeType outline, modified in-place
664 * \param border_x border size, x direction, d6 format
665 * \param border_x border size, y direction, d6 format
667 void fix_freetype_stroker(FT_Outline *outline, int border_x, int border_y)
669 int nc = outline->n_contours;
672 char *valid_cont = malloc(nc);
675 FT_BBox *boxes = malloc(nc * sizeof(FT_BBox));
677 int inside_direction;
679 inside_direction = FT_Outline_Get_Orientation(outline) ==
680 FT_ORIENTATION_TRUETYPE;
682 // create a list of cboxes of the contours
683 for (i = 0; i < nc; i++) {
685 end = outline->contours[i];
686 get_contour_cbox(&boxes[i], outline->points, start, end);
689 // for each contour, check direction and whether it's "outside"
690 // or contained in another contour
692 for (i = 0; i < nc; i++) {
694 end = outline->contours[i];
695 int dir = get_contour_direction(outline->points, start, end);
697 if (dir == inside_direction) {
698 for (j = 0; j < nc; j++) {
701 if (boxes[i].xMin >= boxes[j].xMin &&
702 boxes[i].xMax <= boxes[j].xMax &&
703 boxes[i].yMin >= boxes[j].yMin &&
704 boxes[i].yMax <= boxes[j].yMax)
707 /* "inside" contour but we can't find anything it could be
708 * inside of - assume the font is buggy and it should be
709 * an "outside" contour, and reverse it */
710 for (j = 0; j < (end + 1 - start) / 2; j++) {
711 FT_Vector temp = outline->points[start + j];
712 char temp2 = outline->tags[start + j];
713 outline->points[start + j] = outline->points[end - j];
714 outline->points[end - j] = temp;
715 outline->tags[start + j] = outline->tags[end - j];
716 outline->tags[end - j] = temp2;
721 if (dir == inside_direction) {
723 get_contour_cbox(&box, outline->points, start, end);
724 int width = box.xMax - box.xMin;
725 int height = box.yMax - box.yMin;
726 if (width < border_x * 2 || height < border_y * 2) {
733 // if we need to modify the outline, rewrite it and skip
734 // the contours that we determined should be removed.
737 for (i = 0; i < nc; i++) {
740 begin = (i == 0) ? 0 : outline->contours[i - 1] + 1;
741 stop = outline->contours[i];
742 for (j = begin; j <= stop; j++) {
743 outline->points[p].x = outline->points[j].x;
744 outline->points[p].y = outline->points[j].y;
745 outline->tags[p] = outline->tags[j];
748 outline->contours[c] = p - 1;
751 outline->n_points = p;
752 outline->n_contours = c;