]> granicus.if.org Git - libass/blob - libass/ass_font.c
Message callback funtionality
[libass] / libass / ass_font.c
1 /*
2  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
3  *
4  * This file is part of libass.
5  *
6  * libass is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * libass is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with libass; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include "config.h"
22
23 #include <inttypes.h>
24 #include <ft2build.h>
25 #include FT_FREETYPE_H
26 #include FT_SYNTHESIS_H
27 #include FT_GLYPH_H
28 #include FT_TRUETYPE_TABLES_H
29
30 #include "ass.h"
31 #include "ass_library.h"
32 #include "ass_font.h"
33 #include "ass_bitmap.h"
34 #include "ass_cache.h"
35 #include "ass_fontconfig.h"
36 #include "ass_utils.h"
37
38 /**
39  * Select Microfost Unicode CharMap, if the font has one.
40  * Otherwise, let FreeType decide.
41  */
42 static void charmap_magic(ass_library_t *library, FT_Face face)
43 {
44     int i;
45     for (i = 0; i < face->num_charmaps; ++i) {
46         FT_CharMap cmap = face->charmaps[i];
47         unsigned pid = cmap->platform_id;
48         unsigned eid = cmap->encoding_id;
49         if (pid == 3 /*microsoft */ 
50             && (eid == 1 /*unicode bmp */ 
51                 || eid == 10 /*full unicode */ )) {
52             FT_Set_Charmap(face, cmap);
53             return;
54         }
55     }
56
57     if (!face->charmap) {
58         if (face->num_charmaps == 0) {
59             ass_msg(library, MSGL_WARN, "Font face with no charmaps");
60             return;
61         }
62         ass_msg(library, MSGL_WARN,
63                 "No charmap autodetected, trying the first one");
64         FT_Set_Charmap(face, face->charmaps[0]);
65         return;
66     }
67 }
68
69 static void update_transform(ass_font_t *font)
70 {
71     int i;
72     FT_Matrix m;
73     m.xx = double_to_d16(font->scale_x);
74     m.yy = double_to_d16(font->scale_y);
75     m.xy = m.yx = 0;
76     for (i = 0; i < font->n_faces; ++i)
77         FT_Set_Transform(font->faces[i], &m, &font->v);
78 }
79
80 /**
81  * \brief find a memory font by name
82  */
83 static int find_font(ass_library_t *library, char *name)
84 {
85     int i;
86     for (i = 0; i < library->num_fontdata; ++i)
87         if (strcasecmp(name, library->fontdata[i].name) == 0)
88             return i;
89     return -1;
90 }
91
92 static void face_set_size(FT_Face face, double size);
93
94 static void buggy_font_workaround(FT_Face face)
95 {
96     // Some fonts have zero Ascender/Descender fields in 'hhea' table.
97     // In this case, get the information from 'os2' table or, as
98     // a last resort, from face.bbox.
99     if (face->ascender + face->descender == 0 || face->height == 0) {
100         TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
101         if (os2) {
102             face->ascender = os2->sTypoAscender;
103             face->descender = os2->sTypoDescender;
104             face->height = face->ascender - face->descender;
105         } else {
106             face->ascender = face->bbox.yMax;
107             face->descender = face->bbox.yMin;
108             face->height = face->ascender - face->descender;
109         }
110     }
111 }
112
113 /**
114  * \brief Select a face with the given charcode and add it to ass_font_t
115  * \return index of the new face in font->faces, -1 if failed
116  */
117 static int add_face(void *fc_priv, ass_font_t *font, uint32_t ch)
118 {
119     char *path;
120     int index;
121     FT_Face face;
122     int error;
123     int mem_idx;
124
125     if (font->n_faces == ASS_FONT_MAX_FACES)
126         return -1;
127
128     path =
129         fontconfig_select(font->library, fc_priv, font->desc.family,
130                           font->desc.treat_family_as_pattern,
131                           font->desc.bold, font->desc.italic, &index, ch);
132     if (!path)
133         return -1;
134
135     mem_idx = find_font(font->library, path);
136     if (mem_idx >= 0) {
137         error =
138             FT_New_Memory_Face(font->ftlibrary,
139                                (unsigned char *) font->library->
140                                fontdata[mem_idx].data,
141                                font->library->fontdata[mem_idx].size, 0,
142                                &face);
143         if (error) {
144             ass_msg(font->library, MSGL_WARN,
145                     "Error opening memory font: '%s'", path);
146             free(path);
147             return -1;
148         }
149     } else {
150         error = FT_New_Face(font->ftlibrary, path, index, &face);
151         if (error) {
152             ass_msg(font->library, MSGL_WARN,
153                     "Error opening font: '%s', %d", path, index);
154             free(path);
155             return -1;
156         }
157     }
158     charmap_magic(font->library, face);
159     buggy_font_workaround(face);
160
161     font->faces[font->n_faces++] = face;
162     update_transform(font);
163     face_set_size(face, font->size);
164     free(path);
165     return font->n_faces - 1;
166 }
167
168 /**
169  * \brief Create a new ass_font_t according to "desc" argument
170  */
171 ass_font_t *ass_font_new(void *font_cache, ass_library_t *library,
172                          FT_Library ftlibrary, void *fc_priv,
173                          ass_font_desc_t *desc)
174 {
175     int error;
176     ass_font_t *fontp;
177     ass_font_t font;
178
179     fontp = ass_font_cache_find((hashmap_t *) font_cache, desc);
180     if (fontp)
181         return fontp;
182
183     font.library = library;
184     font.ftlibrary = ftlibrary;
185     font.n_faces = 0;
186     font.desc.family = strdup(desc->family);
187     font.desc.treat_family_as_pattern = desc->treat_family_as_pattern;
188     font.desc.bold = desc->bold;
189     font.desc.italic = desc->italic;
190
191     font.scale_x = font.scale_y = 1.;
192     font.v.x = font.v.y = 0;
193     font.size = 0.;
194
195     error = add_face(fc_priv, &font, 0);
196     if (error == -1) {
197         free(font.desc.family);
198         return 0;
199     } else
200         return ass_font_cache_add((hashmap_t *) font_cache, &font);
201 }
202
203 /**
204  * \brief Set font transformation matrix and shift vector
205  **/
206 void ass_font_set_transform(ass_font_t *font, double scale_x,
207                             double scale_y, FT_Vector *v)
208 {
209     font->scale_x = scale_x;
210     font->scale_y = scale_y;
211     font->v.x = v->x;
212     font->v.y = v->y;
213     update_transform(font);
214 }
215
216 static void face_set_size(FT_Face face, double size)
217 {
218 #if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1))
219     TT_HoriHeader *hori = FT_Get_Sfnt_Table(face, ft_sfnt_hhea);
220     TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
221     double mscale = 1.;
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)
226     if (hori && os2) {
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;
231     }
232     memset(&rq, 0, sizeof(rq));
233     rq.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
234     rq.width = 0;
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;
240     m->height /= mscale;
241 #else
242     FT_Set_Char_Size(face, 0, double_to_d6(size), 0, 0);
243 #endif
244 }
245
246 /**
247  * \brief Set font size
248  **/
249 void ass_font_set_size(ass_font_t *font, double size)
250 {
251     int i;
252     if (font->size != size) {
253         font->size = size;
254         for (i = 0; i < font->n_faces; ++i)
255             face_set_size(font->faces[i], size);
256     }
257 }
258
259 /**
260  * \brief Get maximal font ascender and descender.
261  * \param ch character code
262  * The values are extracted from the font face that provides glyphs for the given character
263  **/
264 void ass_font_get_asc_desc(ass_font_t *font, uint32_t ch, int *asc,
265                            int *desc)
266 {
267     int i;
268     for (i = 0; i < font->n_faces; ++i) {
269         FT_Face face = font->faces[i];
270         if (FT_Get_Char_Index(face, ch)) {
271             *asc = face->size->metrics.ascender;
272             *desc = -face->size->metrics.descender;
273             return;
274         }
275     }
276
277     *asc = *desc = 0;
278 }
279
280 /*
281  * Strike a glyph with a horizontal line; it's possible to underline it
282  * and/or strike through it.  For the line's position and size, truetype
283  * tables are consulted.  Obviously this relies on the data in the tables
284  * being accurate.
285  *
286  */
287 static int ass_strike_outline_glyph(FT_Face face, ass_font_t *font,
288                                     FT_Glyph glyph, int under, int through)
289 {
290     TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
291     TT_Postscript *ps = FT_Get_Sfnt_Table(face, ft_sfnt_post);
292     FT_Outline *ol = &((FT_OutlineGlyph) glyph)->outline;
293     int bear, advance, y_scale, i;
294
295     // Grow outline
296     i = (under ? 4 : 0) + (through ? 4 : 0);
297     ol->points = realloc(ol->points, sizeof(FT_Vector) *
298                          (ol->n_points + i));
299     ol->tags = realloc(ol->tags, ol->n_points + i);
300     i = !!under + !!through;
301     ol->contours = realloc(ol->contours, sizeof(short) *
302                            (ol->n_contours + i));
303
304     // If the bearing is negative, the glyph starts left of the current
305     // pen position
306     bear = FFMIN(face->glyph->metrics.horiBearingX, 0);
307     // We're adding half a pixel to avoid small gaps
308     advance = d16_to_d6(glyph->advance.x) + 32;
309     y_scale = face->size->metrics.y_scale;
310
311     // Add points to the outline
312     if (under) {
313         int pos, size;
314         pos = FT_MulFix(ps->underlinePosition, y_scale * font->scale_y);
315         size = FT_MulFix(ps->underlineThickness,
316                          y_scale * font->scale_y / 2);
317
318         if (pos > 0 || size <= 0)
319             return 0;
320
321         FT_Vector points[4] = {
322             {.x = bear,      .y = pos + size},
323             {.x = advance,   .y = pos + size},
324             {.x = advance,   .y = pos - size},
325             {.x = bear,      .y = pos - size},
326         };
327
328         for (i = 0; i < 4; i++) {
329             ol->points[ol->n_points] = points[i];
330             ol->tags[ol->n_points++] = 1;
331         }
332         ol->contours[ol->n_contours++] = ol->n_points - 1;
333     }
334
335     if (through) {
336         int pos, size;
337         pos = FT_MulFix(os2->yStrikeoutPosition, y_scale * font->scale_y);
338         size = FT_MulFix(os2->yStrikeoutSize, y_scale * font->scale_y / 2);
339
340         if (pos < 0 || size <= 0)
341             return 0;
342
343         FT_Vector points[4] = {
344             {.x = bear,      .y = pos + size},
345             {.x = advance,   .y = pos + size},
346             {.x = advance,   .y = pos - size},
347             {.x = bear,      .y = pos - size},
348         };
349
350         for (i = 0; i < 4; i++) {
351             ol->points[ol->n_points] = points[i];
352             ol->tags[ol->n_points++] = 1;
353         }
354
355         ol->contours[ol->n_contours++] = ol->n_points - 1;
356     }
357
358     return 1;
359 }
360
361 /**
362  * \brief Get a glyph
363  * \param ch character code
364  **/
365 FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ass_font_t *font,
366                             uint32_t ch, ass_hinting_t hinting, int deco)
367 {
368     int error;
369     int index = 0;
370     int i;
371     FT_Glyph glyph;
372     FT_Face face = 0;
373     int flags = 0;
374
375     if (ch < 0x20)
376         return 0;
377     if (font->n_faces == 0)
378         return 0;
379
380     for (i = 0; i < font->n_faces; ++i) {
381         face = font->faces[i];
382         index = FT_Get_Char_Index(face, ch);
383         if (index)
384             break;
385     }
386
387 #ifdef CONFIG_FONTCONFIG
388     if (index == 0) {
389         int face_idx;
390         ass_msg(font->library, MSGL_INFO,
391                 "Glyph 0x%X not found, selecting one more "
392                 "font for (%s, %d, %d)", ch, font->desc.family,
393                 font->desc.bold, font->desc.italic);
394         face_idx = add_face(fontconfig_priv, font, ch);
395         if (face_idx >= 0) {
396             face = font->faces[face_idx];
397             index = FT_Get_Char_Index(face, ch);
398             if (index == 0) {
399                 ass_msg(font->library, MSGL_ERR,
400                         "Glyph 0x%X not found in font for (%s, %d, %d)",
401                         ch, font->desc.family, font->desc.bold,
402                         font->desc.italic);
403             }
404         }
405     }
406 #endif
407
408     switch (hinting) {
409     case ASS_HINTING_NONE:
410         flags = FT_LOAD_NO_HINTING;
411         break;
412     case ASS_HINTING_LIGHT:
413         flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT;
414         break;
415     case ASS_HINTING_NORMAL:
416         flags = FT_LOAD_FORCE_AUTOHINT;
417         break;
418     case ASS_HINTING_NATIVE:
419         flags = 0;
420         break;
421     }
422
423     error = FT_Load_Glyph(face, index, FT_LOAD_NO_BITMAP | flags);
424     if (error) {
425         ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
426                 index);
427         return 0;
428     }
429 #if (FREETYPE_MAJOR > 2) || \
430     ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR >= 2)) || \
431     ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR == 1) && (FREETYPE_PATCH >= 10))
432 // FreeType >= 2.1.10 required
433     if (!(face->style_flags & FT_STYLE_FLAG_ITALIC) &&
434         (font->desc.italic > 55)) {
435         FT_GlyphSlot_Oblique(face->glyph);
436     }
437 #endif
438     error = FT_Get_Glyph(face->glyph, &glyph);
439     if (error) {
440         ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
441                 index);
442         return 0;
443     }
444
445     ass_strike_outline_glyph(face, font, glyph, deco & DECO_UNDERLINE,
446                              deco & DECO_STRIKETHROUGH);
447
448     return glyph;
449 }
450
451 /**
452  * \brief Get kerning for the pair of glyphs.
453  **/
454 FT_Vector ass_font_get_kerning(ass_font_t *font, uint32_t c1, uint32_t c2)
455 {
456     FT_Vector v = { 0, 0 };
457     int i;
458
459     for (i = 0; i < font->n_faces; ++i) {
460         FT_Face face = font->faces[i];
461         int i1 = FT_Get_Char_Index(face, c1);
462         int i2 = FT_Get_Char_Index(face, c2);
463         if (i1 && i2) {
464             if (FT_HAS_KERNING(face))
465                 FT_Get_Kerning(face, i1, i2, FT_KERNING_DEFAULT, &v);
466             return v;
467         }
468         if (i1 || i2)           // these glyphs are from different font faces, no kerning information
469             return v;
470     }
471     return v;
472 }
473
474 /**
475  * \brief Deallocate ass_font_t
476  **/
477 void ass_font_free(ass_font_t *font)
478 {
479     int i;
480     for (i = 0; i < font->n_faces; ++i)
481         if (font->faces[i])
482             FT_Done_Face(font->faces[i]);
483     if (font->desc.family)
484         free(font->desc.family);
485     free(font);
486 }