2 * Copyright (C) 2011 Grigori Goronzy <greg@chown.ath.cx>
4 * This file is part of libass.
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.
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.
21 #include "ass_shaper.h"
22 #include "ass_render.h"
24 #include "ass_parse.h"
25 #include "ass_cache.h"
28 #ifdef CONFIG_HARFBUZZ
37 #define NUM_FEATURES 5
41 ASS_ShapingLevel shaping_level;
45 FriBidiChar *event_text;
46 FriBidiCharType *ctypes;
47 FriBidiLevel *emblevels;
48 FriBidiStrIndex *cmap;
49 FriBidiParType base_direction;
51 #ifdef CONFIG_HARFBUZZ
54 hb_feature_t *features;
55 hb_language_t language;
57 // Glyph metrics cache, to speed up shaping
62 #ifdef CONFIG_HARFBUZZ
63 struct ass_shaper_metrics_data {
65 GlyphMetricsHashKey hash_key;
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];
77 * \brief Print version information
79 void ass_shaper_info(ASS_Library *lib)
81 ass_msg(lib, MSGL_V, "Shaper: FriBidi "
82 FRIBIDI_VERSION " (SIMPLE)"
83 #ifdef CONFIG_HARFBUZZ
84 " HarfBuzz-ng %s (COMPLEX)", hb_version_string()
90 * \brief grow arrays, if needed
91 * \param new_size requested size
93 static void check_allocations(ASS_Shaper *shaper, size_t new_size)
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);
104 * \brief Free shaper and related data
106 void ass_shaper_free(ASS_Shaper *shaper)
108 #ifdef CONFIG_HARFBUZZ
109 ass_cache_done(shaper->metrics_cache);
110 free(shaper->features);
112 free(shaper->event_text);
113 free(shaper->ctypes);
114 free(shaper->emblevels);
119 void ass_shaper_font_data_free(ASS_ShaperFontData *priv)
121 #ifdef CONFIG_HARFBUZZ
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]);
133 #ifdef CONFIG_HARFBUZZ
135 * \brief set up the HarfBuzz OpenType feature list with some
138 static void init_features(ASS_Shaper *shaper)
140 shaper->features = calloc(sizeof(hb_feature_t), NUM_FEATURES);
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;
156 * \brief Set features depending on properties of the run
158 static void set_run_features(ASS_Shaper *shaper, GlyphInfo *info)
160 // enable vertical substitutions for @font runs
161 if (info->font->desc.vertical)
162 shaper->features[VERT].value = shaper->features[VKNA].value = 1;
164 shaper->features[VERT].value = shaper->features[VKNA].value = 0;
166 // disable ligatures if horizontal spacing is non-standard
168 shaper->features[LIGA].value = shaper->features[CLIG].value = 0;
170 shaper->features[LIGA].value = shaper->features[CLIG].value = 1;
174 * \brief Update HarfBuzz's idea of font metrics
175 * \param hb_font HarfBuzz font
176 * \param face associated FreeType font face
178 static void update_hb_size(hb_font_t *hb_font, FT_Face face)
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);
189 * Cached glyph metrics getters follow
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.
197 GlyphMetricsHashValue *
198 get_cached_metrics(struct ass_shaper_metrics_data *metrics, FT_Face face,
199 hb_codepoint_t unicode, hb_codepoint_t glyph)
201 GlyphMetricsHashValue *val;
203 metrics->hash_key.glyph_index = glyph;
204 val = ass_cache_get(metrics->metrics_cache, &metrics->hash_key);
207 int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
208 | FT_LOAD_IGNORE_TRANSFORM;
209 GlyphMetricsHashValue new_val;
211 if (FT_Load_Glyph(face, glyph, load_flags))
214 memcpy(&new_val.metrics, &face->glyph->metrics, sizeof(FT_Glyph_Metrics));
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;
221 val = ass_cache_put(metrics->metrics_cache, &metrics->hash_key, &new_val);
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)
231 FT_Face face = font_data;
232 struct ass_shaper_metrics_data *metrics_priv = user_data;
235 *glyph = FT_Face_GetCharVariantIndex(face, ass_font_index_magic(face, unicode), variation);
237 *glyph = FT_Get_Char_Index(face, ass_font_index_magic(face, unicode));
239 // rotate glyph advances for @fonts while we still know the Unicode codepoints
241 get_cached_metrics(metrics_priv, face, unicode, *glyph);
247 cached_h_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
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);
257 return metrics->metrics.horiAdvance;
261 cached_v_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
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);
271 return metrics->metrics.vertAdvance;
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)
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)
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);
293 *x = metrics->metrics.horiBearingX - metrics->metrics.vertBearingX;
294 *y = metrics->metrics.horiBearingY - (-metrics->metrics.vertBearingY);
300 get_h_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
301 hb_codepoint_t second, void *user_data)
303 FT_Face face = font_data;
306 if (FT_Get_Kerning (face, first, second, FT_KERNING_DEFAULT, &kern))
313 get_v_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
314 hb_codepoint_t second, void *user_data)
320 cached_extents(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
321 hb_glyph_extents_t *extents, void *user_data)
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);
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;
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)
343 FT_Face face = font_data;
344 int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
345 | FT_LOAD_IGNORE_TRANSFORM;
347 if (FT_Load_Glyph(face, glyph, load_flags))
350 if (point_index >= (unsigned)face->glyph->outline.n_points)
353 *x = face->glyph->outline.points[point_index].x;
354 *y = face->glyph->outline.points[point_index].y;
360 * \brief Retrieve HarfBuzz font from cache.
361 * Create it from FreeType font, if needed.
362 * \param info glyph cluster
363 * \return HarfBuzz font
365 static hb_font_t *get_hb_font(ASS_Shaper *shaper, GlyphInfo *info)
367 ASS_Font *font = info->font;
368 hb_font_t **hb_fonts;
370 if (!font->shaper_priv)
371 font->shaper_priv = calloc(sizeof(ASS_ShaperFontData), 1);
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);
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;
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,
391 hb_font_funcs_set_glyph_h_advance_func(funcs, cached_h_advance,
393 hb_font_funcs_set_glyph_v_advance_func(funcs, cached_v_advance,
395 hb_font_funcs_set_glyph_h_origin_func(funcs, cached_h_origin,
397 hb_font_funcs_set_glyph_v_origin_func(funcs, cached_v_origin,
399 hb_font_funcs_set_glyph_h_kerning_func(funcs, get_h_kerning,
401 hb_font_funcs_set_glyph_v_kerning_func(funcs, get_v_kerning,
403 hb_font_funcs_set_glyph_extents_func(funcs, cached_extents,
405 hb_font_funcs_set_glyph_contour_point_func(funcs, get_contour_point,
407 hb_font_set_funcs(hb_fonts[info->face_index], funcs,
408 font->faces[info->face_index], NULL);
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]);
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);
423 return hb_fonts[info->face_index];
427 * \brief Map script to default language.
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
433 * The mapping is similar to Pango's pango-language.c.
435 * \param script script tag
436 * \return language tag
438 static hb_language_t script_to_language(hb_script_t script)
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;
470 case HB_SCRIPT_TIBETAN: return hb_language_from_string("bo", -1); break;
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;
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;
486 case HB_SCRIPT_UGARITIC: return hb_language_from_string("uga", -1); break;
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;
494 case HB_SCRIPT_NKO: return hb_language_from_string("nko", -1); break;
496 // no representative language exists
497 default: return HB_LANGUAGE_INVALID; break;
502 * \brief Determine language to be used for shaping a run.
504 * \param shaper shaper instance
505 * \param script script tag associated with run
506 * \return language tag
509 hb_shaper_get_run_language(ASS_Shaper *shaper, hb_script_t script)
513 // override set, use it
514 if (shaper->language != HB_LANGUAGE_INVALID)
515 return shaper->language;
517 // get default language for given script
518 lang = script_to_language(script);
520 // no dice, use system default
521 if (lang == HB_LANGUAGE_INVALID)
522 lang = hb_language_get_default();
528 * \brief Feed a run of shaped characters into the GlyphInfo array.
530 * \param glyphs GlyphInfo array
531 * \param buf buffer of shaped run
532 * \param offset offset into GlyphInfo array
535 shape_harfbuzz_process_run(GlyphInfo *glyphs, hb_buffer_t *buf, int offset)
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);
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;
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) {
552 info->next = malloc(sizeof(GlyphInfo));
553 memcpy(info->next, info, sizeof(GlyphInfo));
558 // set position and advance
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;
566 // accumulate advance in the root glyph
567 root->cluster_advance.x += info->advance.x;
568 root->cluster_advance.y += info->advance.y;
573 * \brief Shape event text with HarfBuzz. Full OpenType shaping.
574 * \param glyphs glyph clusters
575 * \param len number of clusters
577 static void shape_harfbuzz(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
580 hb_buffer_t *buf = hb_buffer_create();
581 hb_segment_properties_t props = HB_SEGMENT_PROPERTIES_DEFAULT;
583 // Initialize: skip all glyphs, this is undone later as needed
584 for (i = 0; i < len; i++)
587 for (i = 0; i < len; 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;
593 // advance in text until end of run
594 while (i < (len - 1) && level == glyphs[i+1].shape_run_id)
597 hb_buffer_pre_allocate(buf, i - offset + 1);
598 hb_buffer_add_utf32(buf, shaper->event_text + offset, i - offset + 1,
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);
606 set_run_features(shaper, glyphs + offset);
607 hb_shape(font, buf, shaper->features, shaper->n_features);
609 shape_harfbuzz_process_run(glyphs, buf, offset);
610 hb_buffer_reset(buf);
613 hb_buffer_destroy(buf);
617 * \brief Determine script property of all characters. Characters of script
618 * common and inherited get their script from their context.
621 void ass_shaper_determine_script(ASS_Shaper *shaper, GlyphInfo *glyphs,
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;
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);
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;
641 // do a backwards scan to check if next codepoint
642 // contains a valid script for context
645 last_script = info->script;
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;
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;
661 last_script = info->script;
668 * \brief Shape event text with FriBidi. Does mirroring and simple
670 * \param len number of clusters
672 static void shape_fribidi(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
675 FriBidiJoiningType *joins = calloc(sizeof(*joins), len);
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);
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]));
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.
699 void ass_shaper_set_kerning(ASS_Shaper *shaper, int kern)
701 #ifdef CONFIG_HARFBUZZ
702 shaper->features[KERN].value = !!kern;
707 * \brief Find shape runs according to the event's selected fonts
709 void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv,
710 GlyphInfo *glyphs, size_t len)
715 #ifdef CONFIG_HARFBUZZ
716 ass_shaper_determine_script(shaper, glyphs, len);
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;
724 if (info->symbol == 0xfffc)
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))
761 info->shape_run_id = shape_run;
766 * \brief Set base direction (paragraph direction) of the text.
767 * \param dir base direction
769 void ass_shaper_set_base_direction(ASS_Shaper *shaper, FriBidiParType dir)
771 shaper->base_direction = dir;
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
779 void ass_shaper_set_language(ASS_Shaper *shaper, const char *code)
781 #ifdef CONFIG_HARFBUZZ
785 lang = hb_language_from_string(code, -1);
787 lang = HB_LANGUAGE_INVALID;
789 shaper->language = lang;
794 * Set shaping level. Essentially switches between FriBidi and HarfBuzz.
796 void ass_shaper_set_level(ASS_Shaper *shaper, ASS_ShapingLevel level)
798 shaper->shaping_level = level;
802 * \brief Remove all zero-width invisible characters from the text.
803 * \param text_info text
805 static void ass_shaper_skip_characters(TextInfo *text_info)
808 GlyphInfo *glyphs = text_info->glyphs;
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;
825 * \brief Shape an event's text. Calculates directional runs and shapes them.
826 * \param text_info event's text
828 void ass_shaper_shape(ASS_Shaper *shaper, TextInfo *text_info)
832 GlyphInfo *glyphs = text_info->glyphs;
834 check_allocations(shaper, text_info->length);
836 // Get bidi character types and embedding levels
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);
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];
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);
862 case ASS_SHAPING_COMPLEX:
863 shape_harfbuzz(shaper, glyphs, text_info->length);
867 shape_fribidi(shaper, glyphs, text_info->length);
868 ass_shaper_skip_characters(text_info);
873 * \brief Create a new shaper instance and preallocate data structures
874 * \param prealloc preallocation size
876 ASS_Shaper *ass_shaper_new(size_t prealloc)
878 ASS_Shaper *shaper = calloc(sizeof(*shaper), 1);
880 shaper->base_direction = FRIBIDI_PAR_ON;
881 check_allocations(shaper, prealloc);
883 #ifdef CONFIG_HARFBUZZ
884 init_features(shaper);
885 shaper->metrics_cache = ass_glyph_metrics_cache_create();
893 * \brief clean up additional data temporarily needed for shaping and
894 * (e.g. additional glyphs allocated)
896 void ass_shaper_cleanup(ASS_Shaper *shaper, TextInfo *text_info)
900 for (i = 0; i < text_info->length; i++) {
901 GlyphInfo *info = text_info->glyphs + i;
904 GlyphInfo *next = info->next;
912 * \brief Calculate reorder map to render glyphs in visual order
914 FriBidiStrIndex *ass_shaper_reorder(ASS_Shaper *shaper, TextInfo *text_info)
918 // Initialize reorder map
919 for (i = 0; i < text_info->length; i++)
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;
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);
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
944 FriBidiParType resolve_base_direction(int enc)
948 return FRIBIDI_PAR_ON;
951 return FRIBIDI_PAR_RTL;
953 return FRIBIDI_PAR_LTR;