]> granicus.if.org Git - libass/blob - libass/ass_shaper.c
shaper: always use LTR base direction by default
[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 <fribidi/fribidi.h>
22
23 #include "ass_shaper.h"
24 #include "ass_render.h"
25 #include "ass_font.h"
26 #include "ass_parse.h"
27 #include "ass_cache.h"
28
29 #define MAX_RUNS 50
30
31 #ifdef CONFIG_HARFBUZZ
32 #include <hb-ft.h>
33 enum {
34     VERT = 0,
35     VKNA,
36     KERN
37 };
38 #define NUM_FEATURES 3
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 void check_allocations(ASS_Shaper *shaper, size_t new_size)
95 {
96     if (new_size > shaper->n_glyphs) {
97         shaper->event_text = realloc(shaper->event_text, sizeof(FriBidiChar) * new_size);
98         shaper->ctypes     = realloc(shaper->ctypes, sizeof(FriBidiCharType) * new_size);
99         shaper->emblevels  = realloc(shaper->emblevels, sizeof(FriBidiLevel) * new_size);
100         shaper->cmap       = realloc(shaper->cmap, sizeof(FriBidiStrIndex) * new_size);
101     }
102 }
103
104 /**
105  * \brief Free shaper and related data
106  */
107 void ass_shaper_free(ASS_Shaper *shaper)
108 {
109 #ifdef CONFIG_HARFBUZZ
110     ass_cache_done(shaper->metrics_cache);
111     free(shaper->features);
112 #endif
113     free(shaper->event_text);
114     free(shaper->ctypes);
115     free(shaper->emblevels);
116     free(shaper->cmap);
117     free(shaper);
118 }
119
120 void ass_shaper_font_data_free(ASS_ShaperFontData *priv)
121 {
122 #ifdef CONFIG_HARFBUZZ
123     int i;
124     for (i = 0; i < ASS_FONT_MAX_FACES; i++)
125         if (priv->fonts[i]) {
126             free(priv->metrics_data[i]);
127             hb_font_destroy(priv->fonts[i]);
128             hb_font_funcs_destroy(priv->font_funcs[i]);
129         }
130     free(priv);
131 #endif
132 }
133
134 #ifdef CONFIG_HARFBUZZ
135 /**
136  * \brief set up the HarfBuzz OpenType feature list with some
137  * standard features.
138  */
139 static void init_features(ASS_Shaper *shaper)
140 {
141     shaper->features = calloc(sizeof(hb_feature_t), NUM_FEATURES);
142
143     shaper->n_features = NUM_FEATURES;
144     shaper->features[VERT].tag = HB_TAG('v', 'e', 'r', 't');
145     shaper->features[VERT].end = INT_MAX;
146     shaper->features[VKNA].tag = HB_TAG('v', 'k', 'n', 'a');
147     shaper->features[VKNA].end = INT_MAX;
148     shaper->features[KERN].tag = HB_TAG('k', 'e', 'r', 'n');
149     shaper->features[KERN].end = INT_MAX;
150 }
151
152 /**
153  * \brief Set features depending on properties of the run
154  */
155 static void set_run_features(ASS_Shaper *shaper, GlyphInfo *info)
156 {
157         // enable vertical substitutions for @font runs
158         if (info->font->desc.vertical)
159             shaper->features[VERT].value = shaper->features[VKNA].value = 1;
160         else
161             shaper->features[VERT].value = shaper->features[VKNA].value = 0;
162 }
163
164 /**
165  * \brief Update HarfBuzz's idea of font metrics
166  * \param hb_font HarfBuzz font
167  * \param face associated FreeType font face
168  */
169 static void update_hb_size(hb_font_t *hb_font, FT_Face face)
170 {
171     hb_font_set_scale (hb_font,
172             ((uint64_t) face->size->metrics.x_scale * (uint64_t) face->units_per_EM) >> 16,
173             ((uint64_t) face->size->metrics.y_scale * (uint64_t) face->units_per_EM) >> 16);
174     hb_font_set_ppem (hb_font, face->size->metrics.x_ppem,
175             face->size->metrics.y_ppem);
176 }
177
178
179 /*
180  * Cached glyph metrics getters follow
181  *
182  * These functions replace HarfBuzz' standard FreeType font functions
183  * and provide cached access to essential glyph metrics. This usually
184  * speeds up shaping a lot. It also allows us to use custom load flags.
185  *
186  */
187
188 GlyphMetricsHashValue *
189 get_cached_metrics(struct ass_shaper_metrics_data *metrics, FT_Face face,
190                    hb_codepoint_t glyph)
191 {
192     GlyphMetricsHashValue *val;
193
194     metrics->hash_key.glyph_index = glyph;
195     val = ass_cache_get(metrics->metrics_cache, &metrics->hash_key);
196
197     if (!val) {
198         int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
199             | FT_LOAD_IGNORE_TRANSFORM;
200         GlyphMetricsHashValue new_val;
201
202         if (FT_Load_Glyph(face, glyph, load_flags))
203             return NULL;
204
205         memcpy(&new_val.metrics, &face->glyph->metrics, sizeof(FT_Glyph_Metrics));
206         val = ass_cache_put(metrics->metrics_cache, &metrics->hash_key, &new_val);
207     }
208
209     return val;
210 }
211
212 static hb_bool_t
213 get_glyph(hb_font_t *font, void *font_data, hb_codepoint_t unicode,
214           hb_codepoint_t variation, hb_codepoint_t *glyph, void *user_data)
215 {
216     FT_Face face = font_data;
217
218     if (variation)
219         *glyph = FT_Face_GetCharVariantIndex(face, unicode, variation);
220     else
221         *glyph = FT_Get_Char_Index(face, unicode);
222
223     return *glyph != 0;
224 }
225
226 static hb_position_t
227 cached_h_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
228                  void *user_data)
229 {
230     FT_Face face = font_data;
231     struct ass_shaper_metrics_data *metrics_priv = user_data;
232     GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
233
234     if (!metrics)
235         return 0;
236
237     if (metrics_priv->vertical && glyph > VERTICAL_LOWER_BOUND)
238         return metrics->metrics.vertAdvance;
239
240     return metrics->metrics.horiAdvance;
241 }
242
243 static hb_position_t
244 cached_v_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
245                  void *user_data)
246 {
247     FT_Face face = font_data;
248     struct ass_shaper_metrics_data *metrics_priv = user_data;
249     GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
250
251     if (!metrics)
252         return 0;
253
254     return metrics->metrics.vertAdvance;
255
256 }
257
258 static hb_bool_t
259 cached_h_origin(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
260                 hb_position_t *x, hb_position_t *y, void *user_data)
261 {
262     return 1;
263 }
264
265 static hb_bool_t
266 cached_v_origin(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
267                 hb_position_t *x, hb_position_t *y, void *user_data)
268 {
269     FT_Face face = font_data;
270     struct ass_shaper_metrics_data *metrics_priv = user_data;
271     GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
272
273     if (!metrics)
274         return 0;
275
276     *x = metrics->metrics.horiBearingX - metrics->metrics.vertBearingX;
277     *y = metrics->metrics.horiBearingY - (-metrics->metrics.vertBearingY);
278
279     return 1;
280 }
281
282 static hb_position_t
283 get_h_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
284                  hb_codepoint_t second, void *user_data)
285 {
286     FT_Face face = font_data;
287     FT_Vector kern;
288
289     if (FT_Get_Kerning (face, first, second, FT_KERNING_DEFAULT, &kern))
290         return 0;
291
292     return kern.x;
293 }
294
295 static hb_position_t
296 get_v_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
297                  hb_codepoint_t second, void *user_data)
298 {
299     return 0;
300 }
301
302 static hb_bool_t
303 cached_extents(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
304                hb_glyph_extents_t *extents, void *user_data)
305 {
306     FT_Face face = font_data;
307     struct ass_shaper_metrics_data *metrics_priv = user_data;
308     GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
309
310     if (!metrics)
311         return 0;
312
313     extents->x_bearing = metrics->metrics.horiBearingX;
314     extents->y_bearing = metrics->metrics.horiBearingY;
315     extents->width     = metrics->metrics.width;
316     extents->height    = metrics->metrics.height;
317
318     return 1;
319 }
320
321 static hb_bool_t
322 get_contour_point(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
323                      unsigned int point_index, hb_position_t *x,
324                      hb_position_t *y, void *user_data)
325 {
326     FT_Face face = font_data;
327     int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
328         | FT_LOAD_IGNORE_TRANSFORM;
329
330     if (FT_Load_Glyph(face, glyph, load_flags))
331         return 0;
332
333     if (point_index >= (unsigned)face->glyph->outline.n_points)
334         return 0;
335
336     *x = face->glyph->outline.points[point_index].x;
337     *y = face->glyph->outline.points[point_index].y;
338
339     return 1;
340 }
341
342 /**
343  * \brief Retrieve HarfBuzz font from cache.
344  * Create it from FreeType font, if needed.
345  * \param info glyph cluster
346  * \return HarfBuzz font
347  */
348 static hb_font_t *get_hb_font(ASS_Shaper *shaper, GlyphInfo *info)
349 {
350     ASS_Font *font = info->font;
351     hb_font_t **hb_fonts;
352
353     if (!font->shaper_priv)
354         font->shaper_priv = calloc(sizeof(ASS_ShaperFontData), 1);
355
356
357     hb_fonts = font->shaper_priv->fonts;
358     if (!hb_fonts[info->face_index]) {
359         hb_fonts[info->face_index] =
360             hb_ft_font_create(font->faces[info->face_index], NULL);
361
362         // set up cached metrics access
363         font->shaper_priv->metrics_data[info->face_index] =
364             calloc(sizeof(struct ass_shaper_metrics_data), 1);
365         struct ass_shaper_metrics_data *metrics =
366             font->shaper_priv->metrics_data[info->face_index];
367         metrics->metrics_cache = shaper->metrics_cache;
368         metrics->vertical = info->font->desc.vertical;
369
370         hb_font_funcs_t *funcs = hb_font_funcs_create();
371         font->shaper_priv->font_funcs[info->face_index] = funcs;
372         hb_font_funcs_set_glyph_func(funcs, get_glyph,
373                 metrics, NULL);
374         hb_font_funcs_set_glyph_h_advance_func(funcs, cached_h_advance,
375                 metrics, NULL);
376         hb_font_funcs_set_glyph_v_advance_func(funcs, cached_v_advance,
377                 metrics, NULL);
378         hb_font_funcs_set_glyph_h_origin_func(funcs, cached_h_origin,
379                 metrics, NULL);
380         hb_font_funcs_set_glyph_v_origin_func(funcs, cached_v_origin,
381                 metrics, NULL);
382         hb_font_funcs_set_glyph_h_kerning_func(funcs, get_h_kerning,
383                 metrics, NULL);
384         hb_font_funcs_set_glyph_v_kerning_func(funcs, get_v_kerning,
385                 metrics, NULL);
386         hb_font_funcs_set_glyph_extents_func(funcs, cached_extents,
387                 metrics, NULL);
388         hb_font_funcs_set_glyph_contour_point_func(funcs, get_contour_point,
389                 metrics, NULL);
390         hb_font_set_funcs(hb_fonts[info->face_index], funcs,
391                 font->faces[info->face_index], NULL);
392     }
393
394     ass_face_set_size(font->faces[info->face_index], info->font_size);
395     update_hb_size(hb_fonts[info->face_index], font->faces[info->face_index]);
396
397     // update hash key for cached metrics
398     struct ass_shaper_metrics_data *metrics =
399         font->shaper_priv->metrics_data[info->face_index];
400     metrics->hash_key.font = info->font;
401     metrics->hash_key.face_index = info->face_index;
402     metrics->hash_key.size = info->font_size;
403     metrics->hash_key.scale_x = double_to_d6(info->scale_x);
404     metrics->hash_key.scale_y = double_to_d6(info->scale_y);
405
406     return hb_fonts[info->face_index];
407 }
408
409 /**
410  * \brief Shape event text with HarfBuzz. Full OpenType shaping.
411  * \param glyphs glyph clusters
412  * \param len number of clusters
413  */
414 static void shape_harfbuzz(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
415 {
416     int i, j;
417     int run = 0;
418     struct {
419         int offset;
420         int end;
421         hb_buffer_t *buf;
422         hb_font_t *font;
423     } runs[MAX_RUNS];
424
425
426     for (i = 0; i < len && run < MAX_RUNS; i++, run++) {
427         // get length and level of the current run
428         int k = i;
429         int level = glyphs[i].shape_run_id;
430         int direction = shaper->emblevels[k] % 2;
431         while (i < (len - 1) && level == glyphs[i+1].shape_run_id)
432             i++;
433         runs[run].offset = k;
434         runs[run].end    = i;
435         runs[run].buf    = hb_buffer_create();
436         runs[run].font   = get_hb_font(shaper, glyphs + k);
437         set_run_features(shaper, glyphs + k);
438         hb_buffer_pre_allocate(runs[run].buf, i - k + 1);
439         hb_buffer_set_direction(runs[run].buf, direction ? HB_DIRECTION_RTL :
440                 HB_DIRECTION_LTR);
441         hb_buffer_set_language(runs[run].buf, shaper->language);
442         hb_buffer_add_utf32(runs[run].buf, shaper->event_text + k, i - k + 1,
443                 0, i - k + 1);
444         hb_shape(runs[run].font, runs[run].buf, shaper->features,
445                 shaper->n_features);
446     }
447
448     // Initialize: skip all glyphs, this is undone later as needed
449     for (i = 0; i < len; i++)
450         glyphs[i].skip = 1;
451
452     // Update glyph indexes, positions and advances from the shaped runs
453     for (i = 0; i < run; i++) {
454         int num_glyphs = hb_buffer_get_length(runs[i].buf);
455         hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(runs[i].buf, NULL);
456         hb_glyph_position_t *pos    = hb_buffer_get_glyph_positions(runs[i].buf, NULL);
457
458         for (j = 0; j < num_glyphs; j++) {
459             int idx = glyph_info[j].cluster + runs[i].offset;
460             GlyphInfo *info = glyphs + idx;
461             GlyphInfo *root = info;
462
463             // if we have more than one glyph per cluster, allocate a new one
464             // and attach to the root glyph
465             if (info->skip == 0) {
466                 while (info->next)
467                     info = info->next;
468                 info->next = malloc(sizeof(GlyphInfo));
469                 memcpy(info->next, info, sizeof(GlyphInfo));
470                 info = info->next;
471                 info->next = NULL;
472             }
473
474             // set position and advance
475             info->skip = 0;
476             info->glyph_index = glyph_info[j].codepoint;
477             info->offset.x    = pos[j].x_offset * info->scale_x;
478             info->offset.y    = -pos[j].y_offset * info->scale_y;
479             info->advance.x   = pos[j].x_advance * info->scale_x;
480             info->advance.y   = -pos[j].y_advance * info->scale_y;
481
482             // accumulate advance in the root glyph
483             root->cluster_advance.x += info->advance.x;
484             root->cluster_advance.y += info->advance.y;
485         }
486     }
487
488     // Free runs and associated data
489     for (i = 0; i < run; i++) {
490         hb_buffer_destroy(runs[i].buf);
491     }
492
493 }
494 #endif
495
496 /**
497  * \brief Shape event text with FriBidi. Does mirroring and simple
498  * Arabic shaping.
499  * \param len number of clusters
500  */
501 static void shape_fribidi(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
502 {
503     int i;
504     FriBidiJoiningType *joins = calloc(sizeof(*joins), len);
505
506     // shape on codepoint level
507     fribidi_get_joining_types(shaper->event_text, len, joins);
508     fribidi_join_arabic(shaper->ctypes, len, shaper->emblevels, joins);
509     fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
510             shaper->emblevels, len, joins, shaper->event_text);
511
512     // update indexes
513     for (i = 0; i < len; i++) {
514         GlyphInfo *info = glyphs + i;
515         FT_Face face = info->font->faces[info->face_index];
516         info->symbol = shaper->event_text[i];
517         info->glyph_index = FT_Get_Char_Index(face, shaper->event_text[i]);
518     }
519
520     free(joins);
521 }
522
523 /**
524  * \brief Toggle kerning for HarfBuzz shaping.
525  * NOTE: currently only works with OpenType fonts, the TrueType fallback *always*
526  * kerns. It's a bug in HarfBuzz.
527  */
528 void ass_shaper_set_kerning(ASS_Shaper *shaper, int kern)
529 {
530 #ifdef CONFIG_HARFBUZZ
531     shaper->features[KERN].value = !!kern;
532 #endif
533 }
534
535 /**
536  * \brief Find shape runs according to the event's selected fonts
537  */
538 void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv,
539                           GlyphInfo *glyphs, size_t len)
540 {
541     int i;
542     int shape_run = 0;
543
544     for (i = 0; i < len; i++) {
545         GlyphInfo *last = glyphs + i - 1;
546         GlyphInfo *info = glyphs + i;
547         // skip drawings
548         if (info->symbol == 0xfffc)
549             continue;
550         // set size and get glyph index
551         ass_font_get_index(render_priv->fontconfig_priv, info->font,
552                 info->symbol, &info->face_index, &info->glyph_index);
553         // shape runs share the same font face and size
554         if (i > 0 && (last->font != info->font ||
555                     last->font_size != info->font_size ||
556                     last->face_index != info->face_index))
557             shape_run++;
558         info->shape_run_id = shape_run;
559     }
560
561 }
562
563 /**
564  * \brief Set base direction (paragraph direction) of the text.
565  * \param dir base direction
566  */
567 void ass_shaper_set_base_direction(ASS_Shaper *shaper, FriBidiParType dir)
568 {
569     shaper->base_direction = dir;
570 }
571
572 /**
573  * \brief Set language hint. Some languages have specific character variants,
574  * like Serbian Cyrillic.
575  * \param lang ISO 639-1 two-letter language code
576  */
577 void ass_shaper_set_language(ASS_Shaper *shaper, const char *code)
578 {
579 #ifdef CONFIG_HARFBUZZ
580     shaper->language = hb_language_from_string(code, -1);
581 #endif
582 }
583
584 /**
585  * Set shaping level. Essentially switches between FriBidi and HarfBuzz.
586  */
587 void ass_shaper_set_level(ASS_Shaper *shaper, ASS_ShapingLevel level)
588 {
589     shaper->shaping_level = level;
590 }
591
592 /**
593  * \brief Shape an event's text. Calculates directional runs and shapes them.
594  * \param text_info event's text
595  */
596 void ass_shaper_shape(ASS_Shaper *shaper, TextInfo *text_info)
597 {
598     int i, last_break;
599     FriBidiParType dir;
600     GlyphInfo *glyphs = text_info->glyphs;
601
602     check_allocations(shaper, text_info->length);
603
604     // Get bidi character types and embedding levels
605     last_break = 0;
606     for (i = 0; i < text_info->length; i++) {
607         shaper->event_text[i] = glyphs[i].symbol;
608         // embedding levels should be calculated paragraph by paragraph
609         if (glyphs[i].symbol == '\n' || i == text_info->length - 1) {
610             dir = shaper->base_direction;
611             fribidi_get_bidi_types(shaper->event_text + last_break,
612                     i - last_break + 1, shaper->ctypes + last_break);
613             fribidi_get_par_embedding_levels(shaper->ctypes + last_break,
614                     i - last_break + 1, &dir, shaper->emblevels + last_break);
615             last_break = i + 1;
616         }
617     }
618
619     // add embedding levels to shape runs for final runs
620     for (i = 0; i < text_info->length; i++) {
621         glyphs[i].shape_run_id += shaper->emblevels[i];
622     }
623
624 #ifdef CONFIG_HARFBUZZ
625     switch (shaper->shaping_level) {
626     case ASS_SHAPING_SIMPLE:
627         shape_fribidi(shaper, glyphs, text_info->length);
628         break;
629     case ASS_SHAPING_COMPLEX:
630         shape_harfbuzz(shaper, glyphs, text_info->length);
631         break;
632     }
633 #else
634         shape_fribidi(shaper, glyphs, text_info->length);
635 #endif
636
637
638     // clean up
639     for (i = 0; i < text_info->length; i++) {
640         // Skip direction override control characters
641         // NOTE: Behdad said HarfBuzz is supposed to remove these, but this hasn't
642         // been implemented yet
643         if ((glyphs[i].symbol <= 0x202e && glyphs[i].symbol >= 0x202a)
644                 || (glyphs[i].symbol <= 0x200f && glyphs[i].symbol >= 0x200c)) {
645             glyphs[i].symbol = 0;
646             glyphs[i].skip++;
647         }
648     }
649 }
650
651 /**
652  * \brief Create a new shaper instance and preallocate data structures
653  * \param prealloc preallocation size
654  */
655 ASS_Shaper *ass_shaper_new(size_t prealloc)
656 {
657     ASS_Shaper *shaper = calloc(sizeof(*shaper), 1);
658
659     shaper->base_direction = FRIBIDI_PAR_ON;
660     check_allocations(shaper, prealloc);
661
662 #ifdef CONFIG_HARFBUZZ
663     init_features(shaper);
664     shaper->metrics_cache = ass_glyph_metrics_cache_create();
665 #endif
666
667     return shaper;
668 }
669
670
671 /**
672  * \brief clean up additional data temporarily needed for shaping and
673  * (e.g. additional glyphs allocated)
674  */
675 void ass_shaper_cleanup(ASS_Shaper *shaper, TextInfo *text_info)
676 {
677     int i;
678
679     for (i = 0; i < text_info->length; i++) {
680         GlyphInfo *info = text_info->glyphs + i;
681         info = info->next;
682         while (info) {
683             GlyphInfo *next = info->next;
684             free(info);
685             info = next;
686         }
687     }
688 }
689
690 /**
691  * \brief Calculate reorder map to render glyphs in visual order
692  */
693 FriBidiStrIndex *ass_shaper_reorder(ASS_Shaper *shaper, TextInfo *text_info)
694 {
695     int i;
696
697     // Initialize reorder map
698     for (i = 0; i < text_info->length; i++)
699         shaper->cmap[i] = i;
700
701     // Create reorder map line-by-line
702     for (i = 0; i < text_info->n_lines; i++) {
703         LineInfo *line = text_info->lines + i;
704         int level;
705         FriBidiParType dir = FRIBIDI_PAR_ON;
706
707         level = fribidi_reorder_line(0,
708                 shaper->ctypes + line->offset, line->len, 0, dir,
709                 shaper->emblevels + line->offset, NULL,
710                 shaper->cmap + line->offset);
711     }
712
713     return shaper->cmap;
714 }
715
716 /**
717  * \brief Resolve a Windows font encoding number to a suitable
718  * base direction. 177 and 178 are Hebrew and Arabic respectively, and
719  * they map to RTL. Everything else maps to LTR for compatibility
720  * reasons.
721  * \param enc Windows font encoding
722  */
723 FriBidiParType resolve_base_direction(int enc)
724 {
725     switch (enc) {
726         case 177:
727         case 178:
728             return FRIBIDI_PAR_RTL;
729         default:
730             return FRIBIDI_PAR_LTR;
731     }
732 }