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