]> granicus.if.org Git - libass/blob - libass/ass_shaper.c
Update HarfBuzz' font metrics
[libass] / libass / ass_shaper.c
1 /*
2  * Copyright (C) 2011 Grigori Goronzy <greg@chown.ath.cx>
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 <fribidi/fribidi.h>
20 #include <hb-ft.h>
21
22 #include "ass_shaper.h"
23 #include "ass_render.h"
24 #include "ass_font.h"
25 #include "ass_parse.h"
26
27 #define MAX_RUNS 50
28
29 enum {
30     VERT = 0,
31     VKNA,
32     KERN
33 };
34 #define NUM_FEATURES 3
35
36 struct ass_shaper {
37     // FriBidi log2vis
38     int n_glyphs;
39     FriBidiChar *event_text;
40     FriBidiCharType *ctypes;
41     FriBidiLevel *emblevels;
42     FriBidiStrIndex *cmap;
43     FriBidiParType base_direction;
44     // OpenType features
45     int n_features;
46     hb_feature_t *features;
47 };
48
49 struct ass_shaper_font_data {
50     hb_font_t *fonts[ASS_FONT_MAX_FACES];
51 };
52
53 /**
54  * \brief Print version information
55  */
56 void ass_shaper_info(ASS_Library *lib)
57 {
58     ass_msg(lib, MSGL_V, "Complex text layout enabled, using FriBidi "
59             FRIBIDI_VERSION " HarfBuzz-ng %s", hb_version_string());
60 }
61
62 /**
63  * \brief grow arrays, if needed
64  * \param new_size requested size
65  */
66 static void check_allocations(ASS_Shaper *shaper, size_t new_size)
67 {
68     if (new_size > shaper->n_glyphs) {
69         shaper->event_text = realloc(shaper->event_text, sizeof(FriBidiChar) * new_size);
70         shaper->ctypes     = realloc(shaper->ctypes, sizeof(FriBidiCharType) * new_size);
71         shaper->emblevels  = realloc(shaper->emblevels, sizeof(FriBidiLevel) * new_size);
72         shaper->cmap       = realloc(shaper->cmap, sizeof(FriBidiStrIndex) * new_size);
73     }
74 }
75
76 /**
77  * \brief set up the HarfBuzz OpenType feature list with some
78  * standard features.
79  */
80 static void init_features(ASS_Shaper *shaper)
81 {
82     shaper->features = calloc(sizeof(hb_feature_t), NUM_FEATURES);
83
84     shaper->n_features = NUM_FEATURES;
85     shaper->features[VERT].tag = HB_TAG('v', 'e', 'r', 't');
86     shaper->features[VERT].end = INT_MAX;
87     shaper->features[VKNA].tag = HB_TAG('v', 'k', 'n', 'a');
88     shaper->features[VKNA].end = INT_MAX;
89     shaper->features[KERN].tag = HB_TAG('k', 'e', 'r', 'n');
90     shaper->features[KERN].end = INT_MAX;
91 }
92
93 /**
94  * \brief Create a new shaper instance and preallocate data structures
95  * \param prealloc preallocation size
96  */
97 ASS_Shaper *ass_shaper_new(size_t prealloc)
98 {
99     ASS_Shaper *shaper = calloc(sizeof(*shaper), 1);
100
101     shaper->base_direction = FRIBIDI_PAR_ON;
102     init_features(shaper);
103     check_allocations(shaper, prealloc);
104
105     return shaper;
106 }
107
108 /**
109  * \brief Free shaper and related data
110  */
111 void ass_shaper_free(ASS_Shaper *shaper)
112 {
113     free(shaper->event_text);
114     free(shaper->ctypes);
115     free(shaper->emblevels);
116     free(shaper->cmap);
117     free(shaper->features);
118     free(shaper);
119 }
120
121 void ass_shaper_font_data_free(ASS_ShaperFontData *priv)
122 {
123     int i;
124     for (i = 0; i < ASS_FONT_MAX_FACES; i++)
125         if (priv->fonts[i])
126             hb_font_destroy(priv->fonts[i]);
127     free(priv);
128 }
129
130 /**
131  * \brief Set features depending on properties of the run
132  */
133 static void set_run_features(ASS_Shaper *shaper, GlyphInfo *info)
134 {
135         // enable vertical substitutions for @font runs
136         if (info->font->desc.vertical)
137             shaper->features[VERT].value = shaper->features[VKNA].value = 1;
138         else
139             shaper->features[VERT].value = shaper->features[VKNA].value = 0;
140 }
141
142 /**
143  * \brief Update HarfBuzz's idea of font metrics
144  * \param hb_font HarfBuzz font
145  * \param face associated FreeType font face
146  */
147 static void update_hb_size(hb_font_t *hb_font, FT_Face face)
148 {
149     hb_font_set_scale (hb_font,
150             ((uint64_t) face->size->metrics.x_scale * (uint64_t) face->units_per_EM) >> 16,
151             ((uint64_t) face->size->metrics.y_scale * (uint64_t) face->units_per_EM) >> 16);
152     hb_font_set_ppem (hb_font, face->size->metrics.x_ppem,
153             face->size->metrics.y_ppem);
154 }
155
156 /**
157  * \brief Retrieve HarfBuzz font from cache.
158  * Create it from FreeType font, if needed.
159  * \param info glyph cluster
160  * \return HarfBuzz font
161  */
162 static hb_font_t *get_hb_font(GlyphInfo *info)
163 {
164     ASS_Font *font = info->font;
165     hb_font_t **hb_fonts;
166
167     if (!font->shaper_priv)
168         font->shaper_priv = calloc(sizeof(ASS_ShaperFontData), 1);
169
170
171     hb_fonts = font->shaper_priv->fonts;
172     if (!hb_fonts[info->face_index])
173         hb_fonts[info->face_index] =
174             hb_ft_font_create(font->faces[info->face_index], NULL);
175
176     ass_face_set_size(font->faces[info->face_index], info->font_size);
177     update_hb_size(hb_fonts[info->face_index], font->faces[info->face_index]);
178
179     return hb_fonts[info->face_index];
180 }
181
182 /**
183  * \brief Shape event text with HarfBuzz. Full OpenType shaping.
184  * \param glyphs glyph clusters
185  * \param len number of clusters
186  */
187 static void shape_harfbuzz(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
188 {
189     int i, j;
190     int run = 0;
191     struct {
192         int offset;
193         int end;
194         hb_buffer_t *buf;
195         hb_font_t *font;
196     } runs[MAX_RUNS];
197
198
199     for (i = 0; i < len && run < MAX_RUNS; i++, run++) {
200         // get length and level of the current run
201         int k = i;
202         int level = glyphs[i].shape_run_id;
203         int direction = shaper->emblevels[k] % 2;
204         while (i < (len - 1) && level == glyphs[i+1].shape_run_id)
205             i++;
206         //printf("run %d from %d to %d with level %d\n", run, k, i, level);
207         runs[run].offset = k;
208         runs[run].end    = i;
209         runs[run].buf    = hb_buffer_create(i - k + 1);
210         runs[run].font   = get_hb_font(glyphs + k);
211         set_run_features(shaper, glyphs + k);
212         hb_buffer_set_direction(runs[run].buf, direction ? HB_DIRECTION_RTL :
213                 HB_DIRECTION_LTR);
214         hb_buffer_add_utf32(runs[run].buf, shaper->event_text + k, i - k + 1,
215                 0, i - k + 1);
216         hb_shape(runs[run].font, runs[run].buf, shaper->features,
217                 shaper->n_features);
218     }
219     //printf("shaped %d runs\n", run);
220
221     // Initialize: skip all glyphs, this is undone later as needed
222     for (i = 0; i < len; i++)
223         glyphs[i].skip = 1;
224
225     // Update glyph indexes, positions and advances from the shaped runs
226     for (i = 0; i < run; i++) {
227         int num_glyphs = hb_buffer_get_length(runs[i].buf);
228         hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(runs[i].buf, NULL);
229         hb_glyph_position_t *pos    = hb_buffer_get_glyph_positions(runs[i].buf, NULL);
230         //printf("run text len %d num_glyphs %d\n", runs[i].end - runs[i].offset + 1,
231         //        num_glyphs);
232         // Update glyphs
233         for (j = 0; j < num_glyphs; j++) {
234             int idx = glyph_info[j].cluster + runs[i].offset;
235             GlyphInfo *info = glyphs + idx;
236             GlyphInfo *root = info;
237 #if 0
238             printf("run %d cluster %d codepoint %d -> '%c'\n", i, idx,
239                     glyph_info[j].codepoint, event_text[idx]);
240             printf("position %d %d advance %d %d\n",
241                     pos[j].x_offset, pos[j].y_offset,
242                     pos[j].x_advance, pos[j].y_advance);
243 #endif
244
245             // if we have more than one glyph per cluster, allocate a new one
246             // and attach to the root glyph
247             if (info->skip == 0) {
248                 //printf("duplicate cluster entry, adding glyph\n");
249                 while (info->next)
250                     info = info->next;
251                 info->next = malloc(sizeof(GlyphInfo));
252                 memcpy(info->next, info, sizeof(GlyphInfo));
253                 info = info->next;
254                 info->next = NULL;
255             }
256
257             // set position and advance
258             info->skip = 0;
259             info->glyph_index = glyph_info[j].codepoint;
260             info->offset.x    = pos[j].x_offset * info->scale_x;
261             info->offset.y    = -pos[j].y_offset * info->scale_y;
262             info->advance.x   = pos[j].x_advance * info->scale_x;
263             info->advance.y   = -pos[j].y_advance * info->scale_y;
264
265             // accumulate advance in the root glyph
266             root->cluster_advance.x += info->advance.x;
267             root->cluster_advance.y += info->advance.y;
268         }
269     }
270
271     // Free runs and associated data
272     for (i = 0; i < run; i++) {
273         hb_buffer_destroy(runs[i].buf);
274     }
275
276 }
277
278 /**
279  * \brief Shape event text with FriBidi. Does mirroring and simple
280  * Arabic shaping.
281  * \param len number of clusters
282  */
283 static void shape_fribidi(ASS_Shaper *shaper, size_t len)
284 {
285     FriBidiJoiningType *joins = calloc(sizeof(*joins), len);
286
287     fribidi_get_joining_types(shaper->event_text, len, joins);
288     fribidi_join_arabic(shaper->ctypes, len, shaper->emblevels, joins);
289     fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
290             shaper->emblevels, len, joins, shaper->event_text);
291
292     free(joins);
293 }
294
295 /**
296  * \brief Toggle kerning for HarfBuzz shaping.
297  * NOTE: currently only works with OpenType fonts, the TrueType fallback *always*
298  * kerns. It's a bug in HarfBuzz.
299  */
300 void ass_shaper_set_kerning(ASS_Shaper *shaper, int kern)
301 {
302     shaper->features[KERN].value = !!kern;
303 }
304
305 /**
306  * \brief Find shape runs according to the event's selected fonts
307  */
308 void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv,
309                           GlyphInfo *glyphs, size_t len)
310 {
311     int i;
312     int shape_run = 0;
313
314     for (i = 0; i < len; i++) {
315         GlyphInfo *last = glyphs + i - 1;
316         GlyphInfo *info = glyphs + i;
317         // skip drawings
318         if (info->symbol == 0xfffc)
319             continue;
320         // initialize face_index to continue with the same face, if possible
321         // XXX: can be problematic in some cases, for example if a font misses
322         // a single glyph, like space (U+0020)
323         if (i > 0)
324             info->face_index = last->face_index;
325         // set size and get glyph index
326         ass_font_get_index(render_priv->fontconfig_priv, info->font,
327                 info->symbol, &info->face_index, &info->glyph_index);
328         // shape runs share the same font face and size
329         if (i > 0 && (last->font != info->font ||
330                     last->font_size != info->font_size ||
331                     last->face_index != info->face_index))
332             shape_run++;
333         info->shape_run_id = shape_run;
334         //printf("glyph '%c' shape run id %d face %d\n", info->symbol, info->shape_run_id,
335         //        info->face_index);
336     }
337
338 }
339
340 /**
341  * \brief Set base direction (paragraph direction) of the text.
342  * \param dir base direction
343  */
344 void ass_shaper_set_base_direction(ASS_Shaper *shaper, FriBidiParType dir)
345 {
346     shaper->base_direction = dir;
347 }
348
349 /**
350  * \brief Shape an event's text. Calculates directional runs and shapes them.
351  * \param text_info event's text
352  */
353 void ass_shaper_shape(ASS_Shaper *shaper, TextInfo *text_info)
354 {
355     int i, last_break;
356     FriBidiParType dir;
357     GlyphInfo *glyphs = text_info->glyphs;
358
359     check_allocations(shaper, text_info->length);
360
361     // Get bidi character types and embedding levels
362     last_break = 0;
363     for (i = 0; i < text_info->length; i++) {
364         shaper->event_text[i] = glyphs[i].symbol;
365         // embedding levels should be calculated paragraph by paragraph
366         if (glyphs[i].symbol == '\n' || i == text_info->length - 1) {
367             //printf("paragraph from %d to %d\n", last_break, i);
368             dir = shaper->base_direction;
369             fribidi_get_bidi_types(shaper->event_text + last_break,
370                     i - last_break + 1, shaper->ctypes + last_break);
371             fribidi_get_par_embedding_levels(shaper->ctypes + last_break,
372                     i - last_break + 1, &dir, shaper->emblevels + last_break);
373             last_break = i + 1;
374         }
375     }
376
377     // add embedding levels to shape runs for final runs
378     for (i = 0; i < text_info->length; i++) {
379         glyphs[i].shape_run_id += shaper->emblevels[i];
380     }
381
382 #if 0
383     printf("levels ");
384     for (i = 0; i < text_info->length; i++) {
385         printf("%d ", glyphs[i].shape_run_id);
386     }
387     printf("\n");
388 #endif
389
390     //shape_fribidi(shaper, text_info->length);
391     shape_harfbuzz(shaper, glyphs, text_info->length);
392
393     // Update glyphs
394     for (i = 0; i < text_info->length; i++) {
395         glyphs[i].symbol = shaper->event_text[i];
396         // Skip direction override control characters
397         // NOTE: Behdad said HarfBuzz is supposed to remove these, but this hasn't
398         // been implemented yet
399         if (glyphs[i].symbol <= 0x202F && glyphs[i].symbol >= 0x202a) {
400             glyphs[i].symbol = 0;
401             glyphs[i].skip++;
402         }
403     }
404 }
405
406 /**
407  * \brief clean up additional data temporarily needed for shaping and
408  * (e.g. additional glyphs allocated)
409  */
410 void ass_shaper_cleanup(ASS_Shaper *shaper, TextInfo *text_info)
411 {
412     int i;
413
414     for (i = 0; i < text_info->length; i++) {
415         GlyphInfo *info = text_info->glyphs + i;
416         info = info->next;
417         while (info) {
418             GlyphInfo *next = info->next;
419             free(info);
420             info = next;
421         }
422     }
423 }
424
425 /**
426  * \brief Calculate reorder map to render glyphs in visual order
427  */
428 FriBidiStrIndex *ass_shaper_reorder(ASS_Shaper *shaper, TextInfo *text_info)
429 {
430     int i;
431
432     // Initialize reorder map
433     for (i = 0; i < text_info->length; i++)
434         shaper->cmap[i] = i;
435
436     // Create reorder map line-by-line
437     for (i = 0; i < text_info->n_lines; i++) {
438         LineInfo *line = text_info->lines + i;
439         int level;
440         FriBidiParType dir = FRIBIDI_PAR_ON;
441
442         // FIXME: we should actually specify
443         // the correct paragraph base direction
444         level = fribidi_reorder_line(FRIBIDI_FLAGS_DEFAULT,
445                 shaper->ctypes + line->offset, line->len, 0, dir,
446                 shaper->emblevels + line->offset, NULL,
447                 shaper->cmap + line->offset);
448         //printf("reorder line %d to level %d\n", i, level);
449     }
450
451 #if 0
452     printf("map ");
453     for (i = 0; i < text_info->length; i++) {
454         printf("%d ", cmap[i]);
455     }
456     printf("\n");
457 #endif
458
459     return shaper->cmap;
460 }
461
462 /**
463  * \brief Resolve a Windows font encoding number to a suitable
464  * base direction. 177 and 178 are Hebrew and Arabic respectively, and
465  * they map to RTL. 1 is autodetection and is mapped to just that.
466  * Everything else is mapped to LTR.
467  * \param enc Windows font encoding
468  */
469 FriBidiParType resolve_base_direction(int enc)
470 {
471     switch (enc) {
472         case 1:
473             return FRIBIDI_PAR_ON;
474         case 177:
475         case 178:
476             return FRIBIDI_PAR_RTL;
477         default:
478             return FRIBIDI_PAR_LTR;
479     }
480 }