]> granicus.if.org Git - libass/blob - libass/ass_shaper.c
More malloc checking
[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 "config.h"
20
21 #include "ass_shaper.h"
22 #include "ass_render.h"
23 #include "ass_font.h"
24 #include "ass_parse.h"
25 #include "ass_cache.h"
26 #include <limits.h>
27 #include <stdbool.h>
28
29 #ifdef CONFIG_HARFBUZZ
30 #include <hb-ft.h>
31 enum {
32     VERT = 0,
33     VKNA,
34     KERN,
35     LIGA,
36     CLIG
37 };
38 #define NUM_FEATURES 5
39 #endif
40
41 struct ass_shaper {
42     ASS_ShapingLevel shaping_level;
43
44     // FriBidi log2vis
45     int n_glyphs;
46     FriBidiChar *event_text;
47     FriBidiCharType *ctypes;
48     FriBidiLevel *emblevels;
49     FriBidiStrIndex *cmap;
50     FriBidiParType base_direction;
51
52 #ifdef CONFIG_HARFBUZZ
53     // OpenType features
54     int n_features;
55     hb_feature_t *features;
56     hb_language_t language;
57
58     // Glyph metrics cache, to speed up shaping
59     Cache *metrics_cache;
60 #endif
61 };
62
63 #ifdef CONFIG_HARFBUZZ
64 struct ass_shaper_metrics_data {
65     Cache *metrics_cache;
66     GlyphMetricsHashKey hash_key;
67     int vertical;
68 };
69
70 struct ass_shaper_font_data {
71     hb_font_t *fonts[ASS_FONT_MAX_FACES];
72     hb_font_funcs_t *font_funcs[ASS_FONT_MAX_FACES];
73     struct ass_shaper_metrics_data *metrics_data[ASS_FONT_MAX_FACES];
74 };
75 #endif
76
77 /**
78  * \brief Print version information
79  */
80 void ass_shaper_info(ASS_Library *lib)
81 {
82     ass_msg(lib, MSGL_V, "Shaper: FriBidi "
83             FRIBIDI_VERSION " (SIMPLE)"
84 #ifdef CONFIG_HARFBUZZ
85             " HarfBuzz-ng %s (COMPLEX)", hb_version_string()
86 #endif
87            );
88 }
89
90 /**
91  * \brief grow arrays, if needed
92  * \param new_size requested size
93  */
94 static bool check_allocations(ASS_Shaper *shaper, size_t new_size)
95 {
96     if (new_size > shaper->n_glyphs) {
97         if (!ASS_REALLOC_ARRAY(shaper->event_text, new_size) ||
98             !ASS_REALLOC_ARRAY(shaper->ctypes, new_size) ||
99             !ASS_REALLOC_ARRAY(shaper->emblevels, new_size) ||
100             !ASS_REALLOC_ARRAY(shaper->cmap, new_size))
101             return false;
102     }
103     return true;
104 }
105
106 /**
107  * \brief Free shaper and related data
108  */
109 void ass_shaper_free(ASS_Shaper *shaper)
110 {
111 #ifdef CONFIG_HARFBUZZ
112     ass_cache_done(shaper->metrics_cache);
113     free(shaper->features);
114 #endif
115     free(shaper->event_text);
116     free(shaper->ctypes);
117     free(shaper->emblevels);
118     free(shaper->cmap);
119     free(shaper);
120 }
121
122 void ass_shaper_font_data_free(ASS_ShaperFontData *priv)
123 {
124 #ifdef CONFIG_HARFBUZZ
125     int i;
126     for (i = 0; i < ASS_FONT_MAX_FACES; i++)
127         if (priv->fonts[i]) {
128             free(priv->metrics_data[i]);
129             hb_font_destroy(priv->fonts[i]);
130             hb_font_funcs_destroy(priv->font_funcs[i]);
131         }
132     free(priv);
133 #endif
134 }
135
136 #ifdef CONFIG_HARFBUZZ
137 /**
138  * \brief set up the HarfBuzz OpenType feature list with some
139  * standard features.
140  */
141 static bool init_features(ASS_Shaper *shaper)
142 {
143     shaper->features = calloc(sizeof(hb_feature_t), NUM_FEATURES);
144     if (!shaper->features)
145         return false;
146
147     shaper->n_features = NUM_FEATURES;
148     shaper->features[VERT].tag = HB_TAG('v', 'e', 'r', 't');
149     shaper->features[VERT].end = UINT_MAX;
150     shaper->features[VKNA].tag = HB_TAG('v', 'k', 'n', 'a');
151     shaper->features[VKNA].end = UINT_MAX;
152     shaper->features[KERN].tag = HB_TAG('k', 'e', 'r', 'n');
153     shaper->features[KERN].end = UINT_MAX;
154     shaper->features[LIGA].tag = HB_TAG('l', 'i', 'g', 'a');
155     shaper->features[LIGA].end = UINT_MAX;
156     shaper->features[CLIG].tag = HB_TAG('c', 'l', 'i', 'g');
157     shaper->features[CLIG].end = UINT_MAX;
158
159     return true;
160 }
161
162 /**
163  * \brief Set features depending on properties of the run
164  */
165 static void set_run_features(ASS_Shaper *shaper, GlyphInfo *info)
166 {
167     // enable vertical substitutions for @font runs
168     if (info->font->desc.vertical)
169         shaper->features[VERT].value = shaper->features[VKNA].value = 1;
170     else
171         shaper->features[VERT].value = shaper->features[VKNA].value = 0;
172
173     // disable ligatures if horizontal spacing is non-standard
174     if (info->hspacing)
175         shaper->features[LIGA].value = shaper->features[CLIG].value = 0;
176     else
177         shaper->features[LIGA].value = shaper->features[CLIG].value = 1;
178 }
179
180 /**
181  * \brief Update HarfBuzz's idea of font metrics
182  * \param hb_font HarfBuzz font
183  * \param face associated FreeType font face
184  */
185 static void update_hb_size(hb_font_t *hb_font, FT_Face face)
186 {
187     hb_font_set_scale (hb_font,
188             ((uint64_t) face->size->metrics.x_scale * (uint64_t) face->units_per_EM) >> 16,
189             ((uint64_t) face->size->metrics.y_scale * (uint64_t) face->units_per_EM) >> 16);
190     hb_font_set_ppem (hb_font, face->size->metrics.x_ppem,
191             face->size->metrics.y_ppem);
192 }
193
194
195 /*
196  * Cached glyph metrics getters follow
197  *
198  * These functions replace HarfBuzz' standard FreeType font functions
199  * and provide cached access to essential glyph metrics. This usually
200  * speeds up shaping a lot. It also allows us to use custom load flags.
201  *
202  */
203
204 GlyphMetricsHashValue *
205 get_cached_metrics(struct ass_shaper_metrics_data *metrics, FT_Face face,
206                    hb_codepoint_t unicode, hb_codepoint_t glyph)
207 {
208     GlyphMetricsHashValue *val;
209
210     metrics->hash_key.glyph_index = glyph;
211     val = ass_cache_get(metrics->metrics_cache, &metrics->hash_key);
212
213     if (!val) {
214         int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
215             | FT_LOAD_IGNORE_TRANSFORM;
216         GlyphMetricsHashValue new_val;
217
218         if (FT_Load_Glyph(face, glyph, load_flags))
219             return NULL;
220
221         memcpy(&new_val.metrics, &face->glyph->metrics, sizeof(FT_Glyph_Metrics));
222
223         // if @font rendering is enabled and the glyph should be rotated,
224         // make cached_h_advance pick up the right advance later
225         if (metrics->vertical && unicode >= VERTICAL_LOWER_BOUND)
226             new_val.metrics.horiAdvance = new_val.metrics.vertAdvance;
227
228         val = ass_cache_put(metrics->metrics_cache, &metrics->hash_key, &new_val);
229     }
230
231     return val;
232 }
233
234 static hb_bool_t
235 get_glyph(hb_font_t *font, void *font_data, hb_codepoint_t unicode,
236           hb_codepoint_t variation, hb_codepoint_t *glyph, void *user_data)
237 {
238     FT_Face face = font_data;
239     struct ass_shaper_metrics_data *metrics_priv = user_data;
240
241     if (variation)
242         *glyph = FT_Face_GetCharVariantIndex(face, ass_font_index_magic(face, unicode), variation);
243     else
244         *glyph = FT_Get_Char_Index(face, ass_font_index_magic(face, unicode));
245
246     // rotate glyph advances for @fonts while we still know the Unicode codepoints
247     if (*glyph != 0)
248         get_cached_metrics(metrics_priv, face, unicode, *glyph);
249
250     return *glyph != 0;
251 }
252
253 static hb_position_t
254 cached_h_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
255                  void *user_data)
256 {
257     FT_Face face = font_data;
258     struct ass_shaper_metrics_data *metrics_priv = user_data;
259     GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, 0, glyph);
260
261     if (!metrics)
262         return 0;
263
264     return metrics->metrics.horiAdvance;
265 }
266
267 static hb_position_t
268 cached_v_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
269                  void *user_data)
270 {
271     FT_Face face = font_data;
272     struct ass_shaper_metrics_data *metrics_priv = user_data;
273     GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, 0, glyph);
274
275     if (!metrics)
276         return 0;
277
278     return metrics->metrics.vertAdvance;
279
280 }
281
282 static hb_bool_t
283 cached_h_origin(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
284                 hb_position_t *x, hb_position_t *y, void *user_data)
285 {
286     return 1;
287 }
288
289 static hb_bool_t
290 cached_v_origin(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
291                 hb_position_t *x, hb_position_t *y, void *user_data)
292 {
293     FT_Face face = font_data;
294     struct ass_shaper_metrics_data *metrics_priv = user_data;
295     GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, 0, glyph);
296
297     if (!metrics)
298         return 0;
299
300     *x = metrics->metrics.horiBearingX - metrics->metrics.vertBearingX;
301     *y = metrics->metrics.horiBearingY - (-metrics->metrics.vertBearingY);
302
303     return 1;
304 }
305
306 static hb_position_t
307 get_h_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
308                  hb_codepoint_t second, void *user_data)
309 {
310     FT_Face face = font_data;
311     FT_Vector kern;
312
313     if (FT_Get_Kerning (face, first, second, FT_KERNING_DEFAULT, &kern))
314         return 0;
315
316     return kern.x;
317 }
318
319 static hb_position_t
320 get_v_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
321                  hb_codepoint_t second, void *user_data)
322 {
323     return 0;
324 }
325
326 static hb_bool_t
327 cached_extents(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
328                hb_glyph_extents_t *extents, void *user_data)
329 {
330     FT_Face face = font_data;
331     struct ass_shaper_metrics_data *metrics_priv = user_data;
332     GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, 0, glyph);
333
334     if (!metrics)
335         return 0;
336
337     extents->x_bearing = metrics->metrics.horiBearingX;
338     extents->y_bearing = metrics->metrics.horiBearingY;
339     extents->width     = metrics->metrics.width;
340     extents->height    = -metrics->metrics.height;
341
342     return 1;
343 }
344
345 static hb_bool_t
346 get_contour_point(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
347                      unsigned int point_index, hb_position_t *x,
348                      hb_position_t *y, void *user_data)
349 {
350     FT_Face face = font_data;
351     int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
352         | FT_LOAD_IGNORE_TRANSFORM;
353
354     if (FT_Load_Glyph(face, glyph, load_flags))
355         return 0;
356
357     if (point_index >= (unsigned)face->glyph->outline.n_points)
358         return 0;
359
360     *x = face->glyph->outline.points[point_index].x;
361     *y = face->glyph->outline.points[point_index].y;
362
363     return 1;
364 }
365
366 /**
367  * \brief Retrieve HarfBuzz font from cache.
368  * Create it from FreeType font, if needed.
369  * \param info glyph cluster
370  * \return HarfBuzz font
371  */
372 static hb_font_t *get_hb_font(ASS_Shaper *shaper, GlyphInfo *info)
373 {
374     ASS_Font *font = info->font;
375     hb_font_t **hb_fonts;
376
377     if (!font->shaper_priv)
378         font->shaper_priv = calloc(sizeof(ASS_ShaperFontData), 1);
379
380
381     hb_fonts = font->shaper_priv->fonts;
382     if (!hb_fonts[info->face_index]) {
383         hb_fonts[info->face_index] =
384             hb_ft_font_create(font->faces[info->face_index], NULL);
385
386         // set up cached metrics access
387         font->shaper_priv->metrics_data[info->face_index] =
388             calloc(sizeof(struct ass_shaper_metrics_data), 1);
389         struct ass_shaper_metrics_data *metrics =
390             font->shaper_priv->metrics_data[info->face_index];
391         metrics->metrics_cache = shaper->metrics_cache;
392         metrics->vertical = info->font->desc.vertical;
393
394         hb_font_funcs_t *funcs = hb_font_funcs_create();
395         font->shaper_priv->font_funcs[info->face_index] = funcs;
396         hb_font_funcs_set_glyph_func(funcs, get_glyph,
397                 metrics, NULL);
398         hb_font_funcs_set_glyph_h_advance_func(funcs, cached_h_advance,
399                 metrics, NULL);
400         hb_font_funcs_set_glyph_v_advance_func(funcs, cached_v_advance,
401                 metrics, NULL);
402         hb_font_funcs_set_glyph_h_origin_func(funcs, cached_h_origin,
403                 metrics, NULL);
404         hb_font_funcs_set_glyph_v_origin_func(funcs, cached_v_origin,
405                 metrics, NULL);
406         hb_font_funcs_set_glyph_h_kerning_func(funcs, get_h_kerning,
407                 metrics, NULL);
408         hb_font_funcs_set_glyph_v_kerning_func(funcs, get_v_kerning,
409                 metrics, NULL);
410         hb_font_funcs_set_glyph_extents_func(funcs, cached_extents,
411                 metrics, NULL);
412         hb_font_funcs_set_glyph_contour_point_func(funcs, get_contour_point,
413                 metrics, NULL);
414         hb_font_set_funcs(hb_fonts[info->face_index], funcs,
415                 font->faces[info->face_index], NULL);
416     }
417
418     ass_face_set_size(font->faces[info->face_index], info->font_size);
419     update_hb_size(hb_fonts[info->face_index], font->faces[info->face_index]);
420
421     // update hash key for cached metrics
422     struct ass_shaper_metrics_data *metrics =
423         font->shaper_priv->metrics_data[info->face_index];
424     metrics->hash_key.font = info->font;
425     metrics->hash_key.face_index = info->face_index;
426     metrics->hash_key.size = info->font_size;
427     metrics->hash_key.scale_x = double_to_d6(info->scale_x);
428     metrics->hash_key.scale_y = double_to_d6(info->scale_y);
429
430     return hb_fonts[info->face_index];
431 }
432
433 /**
434  * \brief Map script to default language.
435  *
436  * This maps a script to a language, if a script has a representative
437  * language it is typically used with. Otherwise, the invalid language
438  * is returned.
439  *
440  * The mapping is similar to Pango's pango-language.c.
441  *
442  * \param script script tag
443  * \return language tag
444  */
445 static hb_language_t script_to_language(hb_script_t script)
446 {
447     switch (script) {
448         // Unicode 1.1
449         case HB_SCRIPT_ARABIC: return hb_language_from_string("ar", -1); break;
450         case HB_SCRIPT_ARMENIAN: return hb_language_from_string("hy", -1); break;
451         case HB_SCRIPT_BENGALI: return hb_language_from_string("bn", -1); break;
452         case HB_SCRIPT_CANADIAN_ABORIGINAL: return hb_language_from_string("iu", -1); break;
453         case HB_SCRIPT_CHEROKEE: return hb_language_from_string("chr", -1); break;
454         case HB_SCRIPT_COPTIC: return hb_language_from_string("cop", -1); break;
455         case HB_SCRIPT_CYRILLIC: return hb_language_from_string("ru", -1); break;
456         case HB_SCRIPT_DEVANAGARI: return hb_language_from_string("hi", -1); break;
457         case HB_SCRIPT_GEORGIAN: return hb_language_from_string("ka", -1); break;
458         case HB_SCRIPT_GREEK: return hb_language_from_string("el", -1); break;
459         case HB_SCRIPT_GUJARATI: return hb_language_from_string("gu", -1); break;
460         case HB_SCRIPT_GURMUKHI: return hb_language_from_string("pa", -1); break;
461         case HB_SCRIPT_HANGUL: return hb_language_from_string("ko", -1); break;
462         case HB_SCRIPT_HEBREW: return hb_language_from_string("he", -1); break;
463         case HB_SCRIPT_HIRAGANA: return hb_language_from_string("ja", -1); break;
464         case HB_SCRIPT_KANNADA: return hb_language_from_string("kn", -1); break;
465         case HB_SCRIPT_KATAKANA: return hb_language_from_string("ja", -1); break;
466         case HB_SCRIPT_LAO: return hb_language_from_string("lo", -1); break;
467         case HB_SCRIPT_LATIN: return hb_language_from_string("en", -1); break;
468         case HB_SCRIPT_MALAYALAM: return hb_language_from_string("ml", -1); break;
469         case HB_SCRIPT_MONGOLIAN: return hb_language_from_string("mn", -1); break;
470         case HB_SCRIPT_ORIYA: return hb_language_from_string("or", -1); break;
471         case HB_SCRIPT_SYRIAC: return hb_language_from_string("syr", -1); break;
472         case HB_SCRIPT_TAMIL: return hb_language_from_string("ta", -1); break;
473         case HB_SCRIPT_TELUGU: return hb_language_from_string("te", -1); break;
474         case HB_SCRIPT_THAI: return hb_language_from_string("th", -1); break;
475
476         // Unicode 2.0
477         case HB_SCRIPT_TIBETAN: return hb_language_from_string("bo", -1); break;
478
479         // Unicode 3.0
480         case HB_SCRIPT_ETHIOPIC: return hb_language_from_string("am", -1); break;
481         case HB_SCRIPT_KHMER: return hb_language_from_string("km", -1); break;
482         case HB_SCRIPT_MYANMAR: return hb_language_from_string("my", -1); break;
483         case HB_SCRIPT_SINHALA: return hb_language_from_string("si", -1); break;
484         case HB_SCRIPT_THAANA: return hb_language_from_string("dv", -1); break;
485
486         // Unicode 3.2
487         case HB_SCRIPT_BUHID: return hb_language_from_string("bku", -1); break;
488         case HB_SCRIPT_HANUNOO: return hb_language_from_string("hnn", -1); break;
489         case HB_SCRIPT_TAGALOG: return hb_language_from_string("tl", -1); break;
490         case HB_SCRIPT_TAGBANWA: return hb_language_from_string("tbw", -1); break;
491
492         // Unicode 4.0
493         case HB_SCRIPT_UGARITIC: return hb_language_from_string("uga", -1); break;
494
495         // Unicode 4.1
496         case HB_SCRIPT_BUGINESE: return hb_language_from_string("bug", -1); break;
497         case HB_SCRIPT_OLD_PERSIAN: return hb_language_from_string("peo", -1); break;
498         case HB_SCRIPT_SYLOTI_NAGRI: return hb_language_from_string("syl", -1); break;
499
500         // Unicode 5.0
501         case HB_SCRIPT_NKO: return hb_language_from_string("nko", -1); break;
502
503         // no representative language exists
504         default: return HB_LANGUAGE_INVALID; break;
505     }
506 }
507
508 /**
509  * \brief Determine language to be used for shaping a run.
510  *
511  * \param shaper shaper instance
512  * \param script script tag associated with run
513  * \return language tag
514  */
515 static hb_language_t
516 hb_shaper_get_run_language(ASS_Shaper *shaper, hb_script_t script)
517 {
518     hb_language_t lang;
519
520     // override set, use it
521     if (shaper->language != HB_LANGUAGE_INVALID)
522         return shaper->language;
523
524     // get default language for given script
525     lang = script_to_language(script);
526
527     // no dice, use system default
528     if (lang == HB_LANGUAGE_INVALID)
529         lang = hb_language_get_default();
530
531     return lang;
532 }
533
534 /**
535  * \brief Feed a run of shaped characters into the GlyphInfo array.
536  *
537  * \param glyphs GlyphInfo array
538  * \param buf buffer of shaped run
539  * \param offset offset into GlyphInfo array
540  */
541 static void
542 shape_harfbuzz_process_run(GlyphInfo *glyphs, hb_buffer_t *buf, int offset)
543 {
544     int j;
545     int num_glyphs = hb_buffer_get_length(buf);
546     hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buf, NULL);
547     hb_glyph_position_t *pos    = hb_buffer_get_glyph_positions(buf, NULL);
548
549     for (j = 0; j < num_glyphs; j++) {
550         unsigned idx = glyph_info[j].cluster + offset;
551         GlyphInfo *info = glyphs + idx;
552         GlyphInfo *root = info;
553
554         // if we have more than one glyph per cluster, allocate a new one
555         // and attach to the root glyph
556         if (info->skip == 0) {
557             while (info->next)
558                 info = info->next;
559             info->next = malloc(sizeof(GlyphInfo));
560             if (info->next) {
561                 memcpy(info->next, info, sizeof(GlyphInfo));
562                 info = info->next;
563                 info->next = NULL;
564             }
565         }
566
567         // set position and advance
568         info->skip = 0;
569         info->glyph_index = glyph_info[j].codepoint;
570         info->offset.x    = pos[j].x_offset * info->scale_x;
571         info->offset.y    = -pos[j].y_offset * info->scale_y;
572         info->advance.x   = pos[j].x_advance * info->scale_x;
573         info->advance.y   = -pos[j].y_advance * info->scale_y;
574
575         // accumulate advance in the root glyph
576         root->cluster_advance.x += info->advance.x;
577         root->cluster_advance.y += info->advance.y;
578     }
579 }
580
581 /**
582  * \brief Shape event text with HarfBuzz. Full OpenType shaping.
583  * \param glyphs glyph clusters
584  * \param len number of clusters
585  */
586 static void shape_harfbuzz(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
587 {
588     int i;
589     hb_buffer_t *buf = hb_buffer_create();
590     hb_segment_properties_t props = HB_SEGMENT_PROPERTIES_DEFAULT;
591
592     // Initialize: skip all glyphs, this is undone later as needed
593     for (i = 0; i < len; i++)
594         glyphs[i].skip = 1;
595
596     for (i = 0; i < len; i++) {
597         int offset = i;
598         hb_font_t *font = get_hb_font(shaper, glyphs + offset);
599         int level = glyphs[offset].shape_run_id;
600         int direction = shaper->emblevels[offset] % 2;
601
602         // advance in text until end of run
603         while (i < (len - 1) && level == glyphs[i+1].shape_run_id)
604             i++;
605
606         hb_buffer_pre_allocate(buf, i - offset + 1);
607         hb_buffer_add_utf32(buf, shaper->event_text + offset, i - offset + 1,
608                 0, i - offset + 1);
609
610         props.direction = direction ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
611         props.script = glyphs[offset].script;
612         props.language  = hb_shaper_get_run_language(shaper, props.script);
613         hb_buffer_set_segment_properties(buf, &props);
614
615         set_run_features(shaper, glyphs + offset);
616         hb_shape(font, buf, shaper->features, shaper->n_features);
617
618         shape_harfbuzz_process_run(glyphs, buf, offset);
619         hb_buffer_reset(buf);
620     }
621
622     hb_buffer_destroy(buf);
623 }
624
625 /**
626  * \brief Determine script property of all characters. Characters of script
627  * common and inherited get their script from their context.
628  *
629  */
630 void ass_shaper_determine_script(ASS_Shaper *shaper, GlyphInfo *glyphs,
631                                   size_t len)
632 {
633     int i;
634     int backwards_scan = 0;
635     hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default();
636     hb_script_t last_script = HB_SCRIPT_UNKNOWN;
637
638     // determine script (forward scan)
639     for (i = 0; i < len; i++) {
640         GlyphInfo *info = glyphs + i;
641         info->script = hb_unicode_script(ufuncs, info->symbol);
642
643         // common/inherit codepoints inherit script from context
644         if (info->script == HB_SCRIPT_COMMON ||
645                 info->script == HB_SCRIPT_INHERITED) {
646             // unknown is not a valid context
647             if (last_script != HB_SCRIPT_UNKNOWN)
648                 info->script = last_script;
649             else
650                 // do a backwards scan to check if next codepoint
651                 // contains a valid script for context
652                 backwards_scan = 1;
653         } else {
654             last_script = info->script;
655         }
656     }
657
658     // determine script (backwards scan, if needed)
659     last_script = HB_SCRIPT_UNKNOWN;
660     for (i = len - 1; i >= 0 && backwards_scan; i--) {
661         GlyphInfo *info = glyphs + i;
662
663         // common/inherit codepoints inherit script from context
664         if (info->script == HB_SCRIPT_COMMON ||
665                 info->script == HB_SCRIPT_INHERITED) {
666             // unknown script is not a valid context
667             if (last_script != HB_SCRIPT_UNKNOWN)
668                 info->script = last_script;
669         } else {
670             last_script = info->script;
671         }
672     }
673 }
674 #endif
675
676 /**
677  * \brief Shape event text with FriBidi. Does mirroring and simple
678  * Arabic shaping.
679  * \param len number of clusters
680  */
681 static void shape_fribidi(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
682 {
683     int i;
684     FriBidiJoiningType *joins = calloc(sizeof(*joins), len);
685
686     // shape on codepoint level
687     fribidi_get_joining_types(shaper->event_text, len, joins);
688     fribidi_join_arabic(shaper->ctypes, len, shaper->emblevels, joins);
689     fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
690             shaper->emblevels, len, joins, shaper->event_text);
691
692     // update indexes
693     for (i = 0; i < len; i++) {
694         GlyphInfo *info = glyphs + i;
695         FT_Face face = info->font->faces[info->face_index];
696         info->symbol = shaper->event_text[i];
697         info->glyph_index = FT_Get_Char_Index(face, ass_font_index_magic(face, shaper->event_text[i]));
698     }
699
700     free(joins);
701 }
702
703 /**
704  * \brief Toggle kerning for HarfBuzz shaping.
705  * NOTE: currently only works with OpenType fonts, the TrueType fallback *always*
706  * kerns. It's a bug in HarfBuzz.
707  */
708 void ass_shaper_set_kerning(ASS_Shaper *shaper, int kern)
709 {
710 #ifdef CONFIG_HARFBUZZ
711     shaper->features[KERN].value = !!kern;
712 #endif
713 }
714
715 /**
716  * \brief Find shape runs according to the event's selected fonts
717  */
718 void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv,
719                           GlyphInfo *glyphs, size_t len)
720 {
721     int i;
722     int shape_run = 0;
723
724 #ifdef CONFIG_HARFBUZZ
725     ass_shaper_determine_script(shaper, glyphs, len);
726 #endif
727
728     // find appropriate fonts for the shape runs
729     for (i = 0; i < len; i++) {
730         GlyphInfo *last = glyphs + i - 1;
731         GlyphInfo *info = glyphs + i;
732         // skip drawings
733         if (info->symbol == 0xfffc)
734             continue;
735         // set size and get glyph index
736         ass_font_get_index(render_priv->fontconfig_priv, info->font,
737                 info->symbol, &info->face_index, &info->glyph_index);
738         // shape runs break on: xbord, ybord, xshad, yshad,
739         // all four colors, all four alphas, be, blur, fn, fs,
740         // fscx, fscy, fsp, bold, italic, underline, strikeout,
741         // frx, fry, frz, fax, fay, karaoke start, karaoke type,
742         // and on every line break
743         if (i > 0 && (last->font != info->font ||
744                     last->face_index != info->face_index ||
745                     last->script != info->script ||
746                     last->font_size != info->font_size ||
747                     last->c[0] != info->c[0] ||
748                     last->c[1] != info->c[1] ||
749                     last->c[2] != info->c[2] ||
750                     last->c[3] != info->c[3] ||
751                     last->be != info->be ||
752                     last->blur != info->blur ||
753                     last->shadow_x != info->shadow_x ||
754                     last->shadow_y != info->shadow_y ||
755                     last->frx != info->frx ||
756                     last->fry != info->fry ||
757                     last->frz != info->frz ||
758                     last->fax != info->fax ||
759                     last->fay != info->fay ||
760                     last->scale_x != info->scale_x ||
761                     last->scale_y != info->scale_y ||
762                     last->border_style != info->border_style ||
763                     last->border_x != info->border_x ||
764                     last->border_y != info->border_y ||
765                     last->hspacing != info->hspacing ||
766                     last->italic != info->italic ||
767                     last->bold != info->bold ||
768                     last->flags != info->flags))
769             shape_run++;
770         info->shape_run_id = shape_run;
771     }
772 }
773
774 /**
775  * \brief Set base direction (paragraph direction) of the text.
776  * \param dir base direction
777  */
778 void ass_shaper_set_base_direction(ASS_Shaper *shaper, FriBidiParType dir)
779 {
780     shaper->base_direction = dir;
781 }
782
783 /**
784  * \brief Set language hint. Some languages have specific character variants,
785  * like Serbian Cyrillic.
786  * \param lang ISO 639-1 two-letter language code
787  */
788 void ass_shaper_set_language(ASS_Shaper *shaper, const char *code)
789 {
790 #ifdef CONFIG_HARFBUZZ
791     hb_language_t lang;
792
793     if (code)
794         lang = hb_language_from_string(code, -1);
795     else
796         lang = HB_LANGUAGE_INVALID;
797
798     shaper->language = lang;
799 #endif
800 }
801
802 /**
803  * Set shaping level. Essentially switches between FriBidi and HarfBuzz.
804  */
805 void ass_shaper_set_level(ASS_Shaper *shaper, ASS_ShapingLevel level)
806 {
807     shaper->shaping_level = level;
808 }
809
810 /**
811   * \brief Remove all zero-width invisible characters from the text.
812   * \param text_info text
813   */
814 static void ass_shaper_skip_characters(TextInfo *text_info)
815 {
816     int i;
817     GlyphInfo *glyphs = text_info->glyphs;
818
819     for (i = 0; i < text_info->length; i++) {
820         // Skip direction override control characters
821         if ((glyphs[i].symbol <= 0x202e && glyphs[i].symbol >= 0x202a)
822                 || (glyphs[i].symbol <= 0x200f && glyphs[i].symbol >= 0x200b)
823                 || (glyphs[i].symbol <= 0x2063 && glyphs[i].symbol >= 0x2060)
824                 || glyphs[i].symbol == 0xfeff
825                 || glyphs[i].symbol == 0x00ad
826                 || glyphs[i].symbol == 0x034f) {
827             glyphs[i].symbol = 0;
828             glyphs[i].skip++;
829         }
830     }
831 }
832
833 /**
834  * \brief Shape an event's text. Calculates directional runs and shapes them.
835  * \param text_info event's text
836  * \return success, when 0
837  */
838 int ass_shaper_shape(ASS_Shaper *shaper, TextInfo *text_info)
839 {
840     int i, ret, last_break;
841     FriBidiParType dir;
842     GlyphInfo *glyphs = text_info->glyphs;
843
844     if (!check_allocations(shaper, text_info->length))
845         return -1;
846
847     // Get bidi character types and embedding levels
848     last_break = 0;
849     for (i = 0; i < text_info->length; i++) {
850         shaper->event_text[i] = glyphs[i].symbol;
851         // embedding levels should be calculated paragraph by paragraph
852         if (glyphs[i].symbol == '\n' || i == text_info->length - 1) {
853             dir = shaper->base_direction;
854             fribidi_get_bidi_types(shaper->event_text + last_break,
855                     i - last_break + 1, shaper->ctypes + last_break);
856             ret = fribidi_get_par_embedding_levels(shaper->ctypes + last_break,
857                     i - last_break + 1, &dir, shaper->emblevels + last_break);
858             if (ret == 0)
859                 return -1;
860             last_break = i + 1;
861         }
862     }
863
864     // add embedding levels to shape runs for final runs
865     for (i = 0; i < text_info->length; i++) {
866         glyphs[i].shape_run_id += shaper->emblevels[i];
867     }
868
869 #ifdef CONFIG_HARFBUZZ
870     switch (shaper->shaping_level) {
871     case ASS_SHAPING_SIMPLE:
872         shape_fribidi(shaper, glyphs, text_info->length);
873         ass_shaper_skip_characters(text_info);
874         break;
875     case ASS_SHAPING_COMPLEX:
876         shape_harfbuzz(shaper, glyphs, text_info->length);
877         break;
878     }
879 #else
880         shape_fribidi(shaper, glyphs, text_info->length);
881         ass_shaper_skip_characters(text_info);
882 #endif
883
884     return 0;
885 }
886
887 /**
888  * \brief Create a new shaper instance and preallocate data structures
889  * \param prealloc preallocation size
890  */
891 ASS_Shaper *ass_shaper_new(size_t prealloc)
892 {
893     ASS_Shaper *shaper = calloc(sizeof(*shaper), 1);
894     if (!shaper)
895         return NULL;
896
897     shaper->base_direction = FRIBIDI_PAR_ON;
898     if (!check_allocations(shaper, prealloc))
899         goto error;
900
901 #ifdef CONFIG_HARFBUZZ
902     if (!init_features(shaper))
903         goto error;
904     shaper->metrics_cache = ass_glyph_metrics_cache_create();
905     if (!shaper->metrics_cache)
906         goto error;
907 #endif
908
909     return shaper;
910
911 error:
912     ass_shaper_free(shaper);
913     return NULL;
914 }
915
916
917 /**
918  * \brief clean up additional data temporarily needed for shaping and
919  * (e.g. additional glyphs allocated)
920  */
921 void ass_shaper_cleanup(ASS_Shaper *shaper, TextInfo *text_info)
922 {
923     int i;
924
925     for (i = 0; i < text_info->length; i++) {
926         GlyphInfo *info = text_info->glyphs + i;
927         info = info->next;
928         while (info) {
929             GlyphInfo *next = info->next;
930             free(info);
931             info = next;
932         }
933     }
934 }
935
936 /**
937  * \brief Calculate reorder map to render glyphs in visual order
938  * \param shaper shaper instance
939  * \param text_info text to be reordered
940  * \return map of reordered characters, or NULL
941  */
942 FriBidiStrIndex *ass_shaper_reorder(ASS_Shaper *shaper, TextInfo *text_info)
943 {
944     int i, ret;
945
946     // Initialize reorder map
947     for (i = 0; i < text_info->length; i++)
948         shaper->cmap[i] = i;
949
950     // Create reorder map line-by-line
951     for (i = 0; i < text_info->n_lines; i++) {
952         LineInfo *line = text_info->lines + i;
953         FriBidiParType dir = FRIBIDI_PAR_ON;
954
955         ret = fribidi_reorder_line(0,
956                 shaper->ctypes + line->offset, line->len, 0, dir,
957                 shaper->emblevels + line->offset, NULL,
958                 shaper->cmap + line->offset);
959         if (ret == 0)
960             return NULL;
961     }
962
963     return shaper->cmap;
964 }
965
966 /**
967  * \brief Resolve a Windows font charset number to a suitable
968  * base direction. 177 and 178 are Hebrew and Arabic respectively, and
969  * they map to RTL. Everything else maps to LTR for compatibility
970  * reasons. The special value -1, which is not a legal Windows font charset
971  * number, can be used for autodetection.
972  * \param enc Windows font encoding
973  */
974 FriBidiParType resolve_base_direction(int enc)
975 {
976     switch (enc) {
977         case -1:
978             return FRIBIDI_PAR_ON;
979         case 177:
980         case 178:
981             return FRIBIDI_PAR_RTL;
982         default:
983             return FRIBIDI_PAR_LTR;
984     }
985 }