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