1 /*
2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
3 *
4 * This file is part of libass.
5 *
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.
9 *
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.
17 */
18
19 #include "config.h"
20 #include "ass_compat.h"
21
22 #include <inttypes.h>
23 #include <ft2build.h>
24 #include FT_FREETYPE_H
25 #include FT_SYNTHESIS_H
26 #include FT_GLYPH_H
27 #include FT_TRUETYPE_TABLES_H
28 #include FT_OUTLINE_H
29 #include <limits.h>
30
31 #include "ass.h"
32 #include "ass_library.h"
33 #include "ass_font.h"
34 #include "ass_fontselect.h"
35 #include "ass_utils.h"
36 #include "ass_shaper.h"
37
38 /**
39 * Select a good charmap, prefer Microsoft Unicode charmaps.
40 * Otherwise, let FreeType decide.
41 */
charmap_magic(ASS_Library * library,FT_Face face)42 void charmap_magic(ASS_Library *library, FT_Face face)
43 {
44 int i;
45 int ms_cmap = -1;
46
47 // Search for a Microsoft Unicode cmap
48 for (i = 0; i < face->num_charmaps; ++i) {
49 FT_CharMap cmap = face->charmaps[i];
50 unsigned pid = cmap->platform_id;
51 unsigned eid = cmap->encoding_id;
52 if (pid == 3 /*microsoft */
53 && (eid == 1 /*unicode bmp */
54 || eid == 10 /*full unicode */ )) {
55 FT_Set_Charmap(face, cmap);
56 return;
57 } else if (pid == 3 && ms_cmap < 0)
58 ms_cmap = i;
59 }
60
61 // Try the first Microsoft cmap if no Microsoft Unicode cmap was found
62 if (ms_cmap >= 0) {
63 FT_CharMap cmap = face->charmaps[ms_cmap];
64 FT_Set_Charmap(face, cmap);
65 return;
66 }
67
68 if (!face->charmap) {
69 if (face->num_charmaps == 0) {
70 ass_msg(library, MSGL_WARN, "Font face with no charmaps");
71 return;
72 }
73 ass_msg(library, MSGL_WARN,
74 "No charmap autodetected, trying the first one");
75 FT_Set_Charmap(face, face->charmaps[0]);
76 return;
77 }
78 }
79
80 /**
81 * Adjust char index if the charmap is weird
82 * (currently just MS Symbol)
83 */
84
ass_font_index_magic(FT_Face face,uint32_t symbol)85 uint32_t ass_font_index_magic(FT_Face face, uint32_t symbol)
86 {
87 if (!face->charmap)
88 return symbol;
89
90 switch (face->charmap->encoding) {
91 case FT_ENCODING_MS_SYMBOL:
92 return 0xF000 | symbol;
93 default:
94 return symbol;
95 }
96 }
97
set_font_metrics(FT_Face face)98 static void set_font_metrics(FT_Face face)
99 {
100 // Mimicking GDI's behavior for asc/desc/height.
101 // These fields are (apparently) sometimes used for signed values,
102 // despite being unsigned in the spec.
103 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
104 if (os2 && ((short)os2->usWinAscent + (short)os2->usWinDescent != 0)) {
105 face->ascender = (short)os2->usWinAscent;
106 face->descender = -(short)os2->usWinDescent;
107 face->height = face->ascender - face->descender;
108 }
109
110 // If we didn't have usable Win values in the OS/2 table,
111 // then the values from FreeType will still be in these fields.
112 // It'll use either the OS/2 typo metrics or the hhea ones.
113 // If the font has typo metrics but FreeType didn't use them
114 // (either old FT or USE_TYPO_METRICS not set), we'll try those.
115 // In the case of a very broken font that has none of those options,
116 // we fall back on using face.bbox.
117 // Anything without valid OS/2 Win values isn't supported by VSFilter,
118 // so at this point compatibility's out the window and we're just
119 // trying to render _something_ readable.
120 if (face->ascender - face->descender == 0 || face->height == 0) {
121 if (os2 && (os2->sTypoAscender - os2->sTypoDescender) != 0) {
122 face->ascender = os2->sTypoAscender;
123 face->descender = os2->sTypoDescender;
124 face->height = face->ascender - face->descender;
125 } else {
126 face->ascender = face->bbox.yMax;
127 face->descender = face->bbox.yMin;
128 face->height = face->ascender - face->descender;
129 }
130 }
131 }
132
ass_face_open(ASS_Library * lib,FT_Library ftlib,const char * path,const char * postscript_name,int index)133 FT_Face ass_face_open(ASS_Library *lib, FT_Library ftlib, const char *path,
134 const char *postscript_name, int index)
135 {
136 FT_Face face;
137 int error = FT_New_Face(ftlib, path, index, &face);
138 if (error) {
139 ass_msg(lib, MSGL_WARN, "Error opening font: '%s', %d", path, index);
140 return NULL;
141 }
142
143 if (postscript_name && index < 0 && face->num_faces > 0) {
144 // The font provider gave us a postscript name and is not sure
145 // about the face index.. so use the postscript name to find the
146 // correct face_index in the collection!
147 for (int i = 0; i < face->num_faces; i++) {
148 FT_Done_Face(face);
149 error = FT_New_Face(ftlib, path, i, &face);
150 if (error) {
151 ass_msg(lib, MSGL_WARN, "Error opening font: '%s', %d", path, i);
152 return NULL;
153 }
154
155 const char *face_psname = FT_Get_Postscript_Name(face);
156 if (face_psname != NULL &&
157 strcmp(face_psname, postscript_name) == 0)
158 break;
159 }
160 }
161
162 return face;
163 }
164
165 static unsigned long
read_stream_font(FT_Stream stream,unsigned long offset,unsigned char * buffer,unsigned long count)166 read_stream_font(FT_Stream stream, unsigned long offset, unsigned char *buffer,
167 unsigned long count)
168 {
169 ASS_FontStream *font = (ASS_FontStream *)stream->descriptor.pointer;
170
171 font->func(font->priv, buffer, offset, count);
172 return count;
173 }
174
175 static void
close_stream_font(FT_Stream stream)176 close_stream_font(FT_Stream stream)
177 {
178 free(stream->descriptor.pointer);
179 free(stream);
180 }
181
ass_face_stream(ASS_Library * lib,FT_Library ftlib,const char * name,const ASS_FontStream * stream,int index)182 FT_Face ass_face_stream(ASS_Library *lib, FT_Library ftlib, const char *name,
183 const ASS_FontStream *stream, int index)
184 {
185 ASS_FontStream *fs = calloc(1, sizeof(ASS_FontStream));
186 if (!fs)
187 return NULL;
188 *fs = *stream;
189
190 FT_Stream ftstream = calloc(1, sizeof(FT_StreamRec));
191 if (!ftstream) {
192 free(fs);
193 return NULL;
194 }
195 ftstream->size = stream->func(stream->priv, NULL, 0, 0);
196 ftstream->read = read_stream_font;
197 ftstream->close = close_stream_font;
198 ftstream->descriptor.pointer = (void *)fs;
199
200 FT_Open_Args args = {
201 .flags = FT_OPEN_STREAM,
202 .stream = ftstream,
203 };
204
205 FT_Face face;
206 int error = FT_Open_Face(ftlib, &args, index, &face);
207 if (error) {
208 if (name) {
209 ass_msg(lib, MSGL_WARN,
210 "Error opening memory font: '%s'", name);
211 } else {
212 ass_msg(lib, MSGL_WARN,
213 "Error opening memory font");
214 }
215 return NULL;
216 }
217
218 return face;
219 }
220
221 /**
222 * \brief Select a face with the given charcode and add it to ASS_Font
223 * \return index of the new face in font->faces, -1 if failed
224 */
add_face(ASS_FontSelector * fontsel,ASS_Font * font,uint32_t ch)225 static int add_face(ASS_FontSelector *fontsel, ASS_Font *font, uint32_t ch)
226 {
227 char *path;
228 char *postscript_name = NULL;
229 int i, index, uid;
230 ASS_FontStream stream = { NULL, NULL };
231 FT_Face face;
232
233 if (font->n_faces == ASS_FONT_MAX_FACES)
234 return -1;
235
236 path = ass_font_select(fontsel, font, &index,
237 &postscript_name, &uid, &stream, ch);
238
239 if (!path)
240 return -1;
241
242 for (i = 0; i < font->n_faces; i++) {
243 if (font->faces_uid[i] == uid) {
244 ass_msg(font->library, MSGL_INFO,
245 "Got a font face that already is available! Skipping.");
246 return i;
247 }
248 }
249
250 if (stream.func) {
251 face = ass_face_stream(font->library, font->ftlibrary, path,
252 &stream, index);
253 } else {
254 face = ass_face_open(font->library, font->ftlibrary, path,
255 postscript_name, index);
256 }
257
258 if (!face)
259 return -1;
260
261 charmap_magic(font->library, face);
262 set_font_metrics(face);
263
264 font->faces[font->n_faces] = face;
265 font->faces_uid[font->n_faces++] = uid;
266 ass_face_set_size(face, font->size);
267 return font->n_faces - 1;
268 }
269
270 /**
271 * \brief Create a new ASS_Font according to "desc" argument
272 */
ass_font_new(ASS_Renderer * render_priv,ASS_FontDesc * desc)273 ASS_Font *ass_font_new(ASS_Renderer *render_priv, ASS_FontDesc *desc)
274 {
275 ASS_Font *font = ass_cache_get(render_priv->cache.font_cache, desc, render_priv);
276 if (!font)
277 return NULL;
278 if (font->library)
279 return font;
280 ass_cache_dec_ref(font);
281 return NULL;
282 }
283
ass_font_construct(void * key,void * value,void * priv)284 size_t ass_font_construct(void *key, void *value, void *priv)
285 {
286 ASS_Renderer *render_priv = priv;
287 ASS_FontDesc *desc = key;
288 ASS_Font *font = value;
289
290 font->library = render_priv->library;
291 font->ftlibrary = render_priv->ftlibrary;
292 font->shaper_priv = NULL;
293 font->n_faces = 0;
294 font->desc.family = desc->family;
295 font->desc.bold = desc->bold;
296 font->desc.italic = desc->italic;
297 font->desc.vertical = desc->vertical;
298
299 font->size = 0.;
300
301 int error = add_face(render_priv->fontselect, font, 0);
302 if (error == -1)
303 font->library = NULL;
304 return 1;
305 }
306
ass_face_set_size(FT_Face face,double size)307 void ass_face_set_size(FT_Face face, double size)
308 {
309 FT_Size_RequestRec rq;
310 memset(&rq, 0, sizeof(rq));
311 rq.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
312 rq.width = 0;
313 rq.height = double_to_d6(size);
314 rq.horiResolution = rq.vertResolution = 0;
315 FT_Request_Size(face, &rq);
316 }
317
318 /**
319 * \brief Set font size
320 **/
ass_font_set_size(ASS_Font * font,double size)321 void ass_font_set_size(ASS_Font *font, double size)
322 {
323 int i;
324 if (font->size != size) {
325 font->size = size;
326 for (i = 0; i < font->n_faces; ++i)
327 ass_face_set_size(font->faces[i], size);
328 }
329 }
330
331 /**
332 * \brief Get face weight
333 **/
ass_face_get_weight(FT_Face face)334 int ass_face_get_weight(FT_Face face)
335 {
336 #if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR >= 6)
337 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
338 #else
339 // This old name is still included (as a macro), but deprecated as of 2.6, so avoid using it if we can
340 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
341 #endif
342 if (os2 && os2->version != 0xffff && os2->usWeightClass)
343 return os2->usWeightClass;
344 else
345 return 300 * !!(face->style_flags & FT_STYLE_FLAG_BOLD) + 400;
346 }
347
348 /**
349 * \brief Get maximal font ascender and descender.
350 **/
ass_font_get_asc_desc(ASS_Font * font,int face_index,int * asc,int * desc)351 void ass_font_get_asc_desc(ASS_Font *font, int face_index,
352 int *asc, int *desc)
353 {
354 FT_Face face = font->faces[face_index];
355 int y_scale = face->size->metrics.y_scale;
356 *asc = FT_MulFix(face->ascender, y_scale);
357 *desc = FT_MulFix(-face->descender, y_scale);
358 }
359
add_line(FT_Outline * ol,int bear,int advance,int dir,int pos,int size)360 static void add_line(FT_Outline *ol, int bear, int advance, int dir, int pos, int size) {
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},
366 };
367
368 if (dir == FT_ORIENTATION_TRUETYPE) {
369 int i;
370 for (i = 0; i < 4; i++) {
371 ol->points[ol->n_points] = points[i];
372 ol->tags[ol->n_points++] = 1;
373 }
374 } else {
375 int i;
376 for (i = 3; i >= 0; i--) {
377 ol->points[ol->n_points] = points[i];
378 ol->tags[ol->n_points++] = 1;
379 }
380 }
381
382 ol->contours[ol->n_contours++] = ol->n_points - 1;
383 }
384
385 /*
386 * Strike a glyph with a horizontal line; it's possible to underline it
387 * and/or strike through it. For the line's position and size, truetype
388 * tables are consulted. Obviously this relies on the data in the tables
389 * being accurate.
390 *
391 */
ass_strike_outline_glyph(FT_Face face,ASS_Font * font,FT_Glyph glyph,int under,int through)392 static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
393 FT_Glyph glyph, int under, int through)
394 {
395 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
396 TT_Postscript *ps = FT_Get_Sfnt_Table(face, ft_sfnt_post);
397 FT_Outline *ol = &((FT_OutlineGlyph) glyph)->outline;
398 int advance, y_scale, i, dir;
399
400 if (!under && !through)
401 return 0;
402
403 // Grow outline
404 i = (under ? 4 : 0) + (through ? 4 : 0);
405 if (ol->n_points > SHRT_MAX - i)
406 return 0;
407 if (!ASS_REALLOC_ARRAY(ol->points, ol->n_points + i))
408 return 0;
409 if (!ASS_REALLOC_ARRAY(ol->tags, ol->n_points + i))
410 return 0;
411 i = !!under + !!through;
412 if (ol->n_contours > SHRT_MAX - i)
413 return 0;
414 if (!ASS_REALLOC_ARRAY(ol->contours, ol->n_contours + i))
415 return 0;
416
417 advance = d16_to_d6(glyph->advance.x);
418 y_scale = face->size->metrics.y_scale;
419
420 // Reverse drawing direction for non-truetype fonts
421 dir = FT_Outline_Get_Orientation(ol);
422
423 // Add points to the outline
424 if (under && ps) {
425 int pos = FT_MulFix(ps->underlinePosition, y_scale);
426 int size = FT_MulFix(ps->underlineThickness, y_scale / 2);
427
428 if (pos > 0 || size <= 0)
429 return 1;
430
431 add_line(ol, 0, advance, dir, pos, size);
432 }
433
434 if (through && os2) {
435 int pos = FT_MulFix(os2->yStrikeoutPosition, y_scale);
436 int size = FT_MulFix(os2->yStrikeoutSize, y_scale / 2);
437
438 if (pos < 0 || size <= 0)
439 return 1;
440
441 add_line(ol, 0, advance, dir, pos, size);
442 }
443
444 return 0;
445 }
446
447 /**
448 * Slightly embold a glyph without touching its metrics
449 */
ass_glyph_embolden(FT_GlyphSlot slot)450 static void ass_glyph_embolden(FT_GlyphSlot slot)
451 {
452 int str;
453
454 if (slot->format != FT_GLYPH_FORMAT_OUTLINE)
455 return;
456
457 str = FT_MulFix(slot->face->units_per_EM,
458 slot->face->size->metrics.y_scale) / 64;
459
460 FT_Outline_Embolden(&slot->outline, str);
461 }
462
463 /**
464 * \brief Get glyph and face index
465 * Finds a face that has the requested codepoint and returns both face
466 * and glyph index.
467 */
ass_font_get_index(ASS_FontSelector * fontsel,ASS_Font * font,uint32_t symbol,int * face_index,int * glyph_index)468 int ass_font_get_index(ASS_FontSelector *fontsel, ASS_Font *font,
469 uint32_t symbol, int *face_index, int *glyph_index)
470 {
471 int index = 0;
472 int i;
473 FT_Face face = 0;
474
475 *glyph_index = 0;
476
477 if (symbol < 0x20) {
478 *face_index = 0;
479 return 0;
480 }
481 // Handle NBSP like a regular space when rendering the glyph
482 if (symbol == 0xa0)
483 symbol = ' ';
484 if (font->n_faces == 0) {
485 *face_index = 0;
486 return 0;
487 }
488
489 for (i = 0; i < font->n_faces && index == 0; ++i) {
490 face = font->faces[i];
491 index = FT_Get_Char_Index(face, ass_font_index_magic(face, symbol));
492 if (index)
493 *face_index = i;
494 }
495
496 if (index == 0) {
497 int face_idx;
498 ass_msg(font->library, MSGL_INFO,
499 "Glyph 0x%X not found, selecting one more "
500 "font for (%.*s, %d, %d)", symbol, (int) font->desc.family.len, font->desc.family.str,
501 font->desc.bold, font->desc.italic);
502 face_idx = *face_index = add_face(fontsel, font, symbol);
503 if (face_idx >= 0) {
504 face = font->faces[face_idx];
505 index = FT_Get_Char_Index(face, ass_font_index_magic(face, symbol));
506 if (index == 0 && face->num_charmaps > 0) {
507 int i;
508 ass_msg(font->library, MSGL_WARN,
509 "Glyph 0x%X not found, broken font? Trying all charmaps", symbol);
510 for (i = 0; i < face->num_charmaps; i++) {
511 FT_Set_Charmap(face, face->charmaps[i]);
512 if ((index = FT_Get_Char_Index(face, ass_font_index_magic(face, symbol))) != 0) break;
513 }
514 }
515 if (index == 0) {
516 ass_msg(font->library, MSGL_ERR,
517 "Glyph 0x%X not found in font for (%.*s, %d, %d)",
518 symbol, (int) font->desc.family.len, font->desc.family.str, font->desc.bold,
519 font->desc.italic);
520 }
521 }
522 }
523
524 // FIXME: make sure we have a valid face_index. this is a HACK.
525 *face_index = FFMAX(*face_index, 0);
526 *glyph_index = index;
527
528 return 1;
529 }
530
531 /**
532 * \brief Get a glyph
533 * \param ch character code
534 **/
ass_font_get_glyph(ASS_Font * font,int face_index,int index,ASS_Hinting hinting,int deco)535 FT_Glyph ass_font_get_glyph(ASS_Font *font, int face_index, int index,
536 ASS_Hinting hinting, int deco)
537 {
538 int error;
539 FT_Glyph glyph;
540 FT_Face face = font->faces[face_index];
541 int flags = 0;
542
543 flags = FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
544 | FT_LOAD_IGNORE_TRANSFORM;
545 switch (hinting) {
546 case ASS_HINTING_NONE:
547 flags |= FT_LOAD_NO_HINTING;
548 break;
549 case ASS_HINTING_LIGHT:
550 flags |= FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT;
551 break;
552 case ASS_HINTING_NORMAL:
553 flags |= FT_LOAD_FORCE_AUTOHINT;
554 break;
555 case ASS_HINTING_NATIVE:
556 break;
557 }
558
559 error = FT_Load_Glyph(face, index, flags);
560 if (error) {
561 ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
562 index);
563 return 0;
564 }
565 if (!(face->style_flags & FT_STYLE_FLAG_ITALIC) &&
566 (font->desc.italic > 55)) {
567 FT_GlyphSlot_Oblique(face->glyph);
568 }
569
570 if (font->desc.bold > ass_face_get_weight(face) + 150) {
571 ass_glyph_embolden(face->glyph);
572 }
573 error = FT_Get_Glyph(face->glyph, &glyph);
574 if (error) {
575 ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
576 index);
577 return 0;
578 }
579
580 // Rotate glyph, if needed
581 if (deco & DECO_ROTATE) {
582 FT_Matrix m = { 0, double_to_d16(-1.0), double_to_d16(1.0), 0 };
583 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
584 int desc = 0;
585
586 if (os2)
587 desc = FT_MulFix(os2->sTypoDescender, face->size->metrics.y_scale);
588
589 FT_Outline_Translate(&((FT_OutlineGlyph) glyph)->outline, 0, -desc);
590 FT_Outline_Transform(&((FT_OutlineGlyph) glyph)->outline, &m);
591 FT_Outline_Translate(&((FT_OutlineGlyph) glyph)->outline,
592 face->glyph->metrics.vertAdvance, desc);
593 glyph->advance.x = face->glyph->linearVertAdvance;
594 }
595
596 ass_strike_outline_glyph(face, font, glyph, deco & DECO_UNDERLINE,
597 deco & DECO_STRIKETHROUGH);
598
599 return glyph;
600 }
601
602 /**
603 * \brief Deallocate ASS_Font internals
604 **/
ass_font_clear(ASS_Font * font)605 void ass_font_clear(ASS_Font *font)
606 {
607 int i;
608 if (font->shaper_priv)
609 ass_shaper_font_data_free(font->shaper_priv);
610 for (i = 0; i < font->n_faces; ++i) {
611 if (font->faces[i])
612 FT_Done_Face(font->faces[i]);
613 }
614 free((char *) font->desc.family.str);
615 }
616