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 <fribidi/fribidi.h>
23 #include "ass_shaper.h"
24 #include "ass_render.h"
26 #include "ass_parse.h"
27 #include "ass_cache.h"
31 #ifdef CONFIG_HARFBUZZ
38 #define NUM_FEATURES 3
42 ASS_ShapingLevel shaping_level;
46 FriBidiChar *event_text;
47 FriBidiCharType *ctypes;
48 FriBidiLevel *emblevels;
49 FriBidiStrIndex *cmap;
50 FriBidiParType base_direction;
52 #ifdef CONFIG_HARFBUZZ
55 hb_feature_t *features;
56 hb_language_t language;
58 // Glyph metrics cache, to speed up shaping
63 #ifdef CONFIG_HARFBUZZ
64 struct ass_shaper_metrics_data {
66 GlyphMetricsHashKey hash_key;
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];
78 * \brief Print version information
80 void ass_shaper_info(ASS_Library *lib)
82 ass_msg(lib, MSGL_V, "Shaper: FriBidi "
83 FRIBIDI_VERSION " (SIMPLE)"
84 #ifdef CONFIG_HARFBUZZ
85 " HarfBuzz-ng %s (COMPLEX)", hb_version_string()
91 * \brief grow arrays, if needed
92 * \param new_size requested size
94 static void check_allocations(ASS_Shaper *shaper, size_t new_size)
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);
105 * \brief Free shaper and related data
107 void ass_shaper_free(ASS_Shaper *shaper)
109 #ifdef CONFIG_HARFBUZZ
110 ass_cache_done(shaper->metrics_cache);
111 free(shaper->features);
113 free(shaper->event_text);
114 free(shaper->ctypes);
115 free(shaper->emblevels);
120 void ass_shaper_font_data_free(ASS_ShaperFontData *priv)
122 #ifdef CONFIG_HARFBUZZ
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]);
134 #ifdef CONFIG_HARFBUZZ
136 * \brief set up the HarfBuzz OpenType feature list with some
139 static void init_features(ASS_Shaper *shaper)
141 shaper->features = calloc(sizeof(hb_feature_t), NUM_FEATURES);
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;
153 * \brief Set features depending on properties of the run
155 static void set_run_features(ASS_Shaper *shaper, GlyphInfo *info)
157 // enable vertical substitutions for @font runs
158 if (info->font->desc.vertical)
159 shaper->features[VERT].value = shaper->features[VKNA].value = 1;
161 shaper->features[VERT].value = shaper->features[VKNA].value = 0;
165 * \brief Update HarfBuzz's idea of font metrics
166 * \param hb_font HarfBuzz font
167 * \param face associated FreeType font face
169 static void update_hb_size(hb_font_t *hb_font, FT_Face face)
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);
180 * Cached glyph metrics getters follow
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.
188 GlyphMetricsHashValue *
189 get_cached_metrics(struct ass_shaper_metrics_data *metrics, FT_Face face,
190 hb_codepoint_t glyph)
192 GlyphMetricsHashValue *val;
194 metrics->hash_key.glyph_index = glyph;
195 val = ass_cache_get(metrics->metrics_cache, &metrics->hash_key);
198 int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
199 | FT_LOAD_IGNORE_TRANSFORM;
200 GlyphMetricsHashValue new_val;
202 if (FT_Load_Glyph(face, glyph, load_flags))
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);
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)
216 FT_Face face = font_data;
219 *glyph = FT_Face_GetCharVariantIndex(face, unicode, variation);
221 *glyph = FT_Get_Char_Index(face, unicode);
227 cached_h_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
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);
237 if (metrics_priv->vertical && glyph > VERTICAL_LOWER_BOUND)
238 return metrics->metrics.vertAdvance;
240 return metrics->metrics.horiAdvance;
244 cached_v_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
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);
254 return metrics->metrics.vertAdvance;
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)
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)
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);
276 *x = metrics->metrics.horiBearingX - metrics->metrics.vertBearingX;
277 *y = metrics->metrics.horiBearingY - (-metrics->metrics.vertBearingY);
283 get_h_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
284 hb_codepoint_t second, void *user_data)
286 FT_Face face = font_data;
289 if (FT_Get_Kerning (face, first, second, FT_KERNING_DEFAULT, &kern))
296 get_v_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
297 hb_codepoint_t second, void *user_data)
303 cached_extents(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
304 hb_glyph_extents_t *extents, void *user_data)
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);
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;
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)
326 FT_Face face = font_data;
327 int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
328 | FT_LOAD_IGNORE_TRANSFORM;
330 if (FT_Load_Glyph(face, glyph, load_flags))
333 if (point_index >= (unsigned)face->glyph->outline.n_points)
336 *x = face->glyph->outline.points[point_index].x;
337 *y = face->glyph->outline.points[point_index].y;
343 * \brief Retrieve HarfBuzz font from cache.
344 * Create it from FreeType font, if needed.
345 * \param info glyph cluster
346 * \return HarfBuzz font
348 static hb_font_t *get_hb_font(ASS_Shaper *shaper, GlyphInfo *info)
350 ASS_Font *font = info->font;
351 hb_font_t **hb_fonts;
353 if (!font->shaper_priv)
354 font->shaper_priv = calloc(sizeof(ASS_ShaperFontData), 1);
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);
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;
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,
374 hb_font_funcs_set_glyph_h_advance_func(funcs, cached_h_advance,
376 hb_font_funcs_set_glyph_v_advance_func(funcs, cached_v_advance,
378 hb_font_funcs_set_glyph_h_origin_func(funcs, cached_h_origin,
380 hb_font_funcs_set_glyph_v_origin_func(funcs, cached_v_origin,
382 hb_font_funcs_set_glyph_h_kerning_func(funcs, get_h_kerning,
384 hb_font_funcs_set_glyph_v_kerning_func(funcs, get_v_kerning,
386 hb_font_funcs_set_glyph_extents_func(funcs, cached_extents,
388 hb_font_funcs_set_glyph_contour_point_func(funcs, get_contour_point,
390 hb_font_set_funcs(hb_fonts[info->face_index], funcs,
391 font->faces[info->face_index], NULL);
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]);
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);
406 return hb_fonts[info->face_index];
410 * \brief Shape event text with HarfBuzz. Full OpenType shaping.
411 * \param glyphs glyph clusters
412 * \param len number of clusters
414 static void shape_harfbuzz(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
426 for (i = 0; i < len && run < MAX_RUNS; i++, run++) {
427 // get length and level of the current run
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)
433 runs[run].offset = k;
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 :
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,
444 hb_shape(runs[run].font, runs[run].buf, shaper->features,
448 // Initialize: skip all glyphs, this is undone later as needed
449 for (i = 0; i < len; i++)
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);
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;
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) {
468 info->next = malloc(sizeof(GlyphInfo));
469 memcpy(info->next, info, sizeof(GlyphInfo));
474 // set position and advance
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;
482 // accumulate advance in the root glyph
483 root->cluster_advance.x += info->advance.x;
484 root->cluster_advance.y += info->advance.y;
488 // Free runs and associated data
489 for (i = 0; i < run; i++) {
490 hb_buffer_destroy(runs[i].buf);
497 * \brief Shape event text with FriBidi. Does mirroring and simple
499 * \param len number of clusters
501 static void shape_fribidi(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
504 FriBidiJoiningType *joins = calloc(sizeof(*joins), len);
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);
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]);
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.
528 void ass_shaper_set_kerning(ASS_Shaper *shaper, int kern)
530 #ifdef CONFIG_HARFBUZZ
531 shaper->features[KERN].value = !!kern;
536 * \brief Find shape runs according to the event's selected fonts
538 void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv,
539 GlyphInfo *glyphs, size_t len)
544 for (i = 0; i < len; i++) {
545 GlyphInfo *last = glyphs + i - 1;
546 GlyphInfo *info = glyphs + i;
548 if (info->symbol == 0xfffc)
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))
558 info->shape_run_id = shape_run;
564 * \brief Set base direction (paragraph direction) of the text.
565 * \param dir base direction
567 void ass_shaper_set_base_direction(ASS_Shaper *shaper, FriBidiParType dir)
569 shaper->base_direction = dir;
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
577 void ass_shaper_set_language(ASS_Shaper *shaper, const char *code)
579 #ifdef CONFIG_HARFBUZZ
580 shaper->language = hb_language_from_string(code, -1);
585 * Set shaping level. Essentially switches between FriBidi and HarfBuzz.
587 void ass_shaper_set_level(ASS_Shaper *shaper, ASS_ShapingLevel level)
589 shaper->shaping_level = level;
593 * \brief Shape an event's text. Calculates directional runs and shapes them.
594 * \param text_info event's text
596 void ass_shaper_shape(ASS_Shaper *shaper, TextInfo *text_info)
600 GlyphInfo *glyphs = text_info->glyphs;
602 check_allocations(shaper, text_info->length);
604 // Get bidi character types and embedding levels
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);
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];
624 #ifdef CONFIG_HARFBUZZ
625 switch (shaper->shaping_level) {
626 case ASS_SHAPING_SIMPLE:
627 shape_fribidi(shaper, glyphs, text_info->length);
629 case ASS_SHAPING_COMPLEX:
630 shape_harfbuzz(shaper, glyphs, text_info->length);
634 shape_fribidi(shaper, glyphs, text_info->length);
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;
652 * \brief Create a new shaper instance and preallocate data structures
653 * \param prealloc preallocation size
655 ASS_Shaper *ass_shaper_new(size_t prealloc)
657 ASS_Shaper *shaper = calloc(sizeof(*shaper), 1);
659 shaper->base_direction = FRIBIDI_PAR_ON;
660 check_allocations(shaper, prealloc);
662 #ifdef CONFIG_HARFBUZZ
663 init_features(shaper);
664 shaper->metrics_cache = ass_glyph_metrics_cache_create();
672 * \brief clean up additional data temporarily needed for shaping and
673 * (e.g. additional glyphs allocated)
675 void ass_shaper_cleanup(ASS_Shaper *shaper, TextInfo *text_info)
679 for (i = 0; i < text_info->length; i++) {
680 GlyphInfo *info = text_info->glyphs + i;
683 GlyphInfo *next = info->next;
691 * \brief Calculate reorder map to render glyphs in visual order
693 FriBidiStrIndex *ass_shaper_reorder(ASS_Shaper *shaper, TextInfo *text_info)
697 // Initialize reorder map
698 for (i = 0; i < text_info->length; i++)
701 // Create reorder map line-by-line
702 for (i = 0; i < text_info->n_lines; i++) {
703 LineInfo *line = text_info->lines + i;
705 FriBidiParType dir = FRIBIDI_PAR_ON;
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);
717 * \brief Resolve a Windows font charset 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. The special value -1, which is not a legal Windows font charset
721 * number, can be used for autodetection.
722 * \param enc Windows font encoding
724 FriBidiParType resolve_base_direction(int enc)
728 return FRIBIDI_PAR_ON;
731 return FRIBIDI_PAR_RTL;
733 return FRIBIDI_PAR_LTR;