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.
19 #include <fribidi/fribidi.h>
22 #include "ass_shaper.h"
23 #include "ass_render.h"
25 #include "ass_parse.h"
26 #include "ass_cache.h"
35 #define NUM_FEATURES 3
40 FriBidiChar *event_text;
41 FriBidiCharType *ctypes;
42 FriBidiLevel *emblevels;
43 FriBidiStrIndex *cmap;
44 FriBidiParType base_direction;
47 hb_feature_t *features;
48 hb_language_t language;
49 // Glyph metrics cache, to speed up shaping
53 struct ass_shaper_metrics_data {
55 GlyphMetricsHashKey hash_key;
58 struct ass_shaper_font_data {
59 hb_font_t *fonts[ASS_FONT_MAX_FACES];
60 hb_font_funcs_t *font_funcs[ASS_FONT_MAX_FACES];
61 struct ass_shaper_metrics_data *metrics_data[ASS_FONT_MAX_FACES];
65 * \brief Print version information
67 void ass_shaper_info(ASS_Library *lib)
69 ass_msg(lib, MSGL_V, "Complex text layout enabled, using FriBidi "
70 FRIBIDI_VERSION " HarfBuzz-ng %s", hb_version_string());
74 * \brief grow arrays, if needed
75 * \param new_size requested size
77 static void check_allocations(ASS_Shaper *shaper, size_t new_size)
79 if (new_size > shaper->n_glyphs) {
80 shaper->event_text = realloc(shaper->event_text, sizeof(FriBidiChar) * new_size);
81 shaper->ctypes = realloc(shaper->ctypes, sizeof(FriBidiCharType) * new_size);
82 shaper->emblevels = realloc(shaper->emblevels, sizeof(FriBidiLevel) * new_size);
83 shaper->cmap = realloc(shaper->cmap, sizeof(FriBidiStrIndex) * new_size);
88 * \brief set up the HarfBuzz OpenType feature list with some
91 static void init_features(ASS_Shaper *shaper)
93 shaper->features = calloc(sizeof(hb_feature_t), NUM_FEATURES);
95 shaper->n_features = NUM_FEATURES;
96 shaper->features[VERT].tag = HB_TAG('v', 'e', 'r', 't');
97 shaper->features[VERT].end = INT_MAX;
98 shaper->features[VKNA].tag = HB_TAG('v', 'k', 'n', 'a');
99 shaper->features[VKNA].end = INT_MAX;
100 shaper->features[KERN].tag = HB_TAG('k', 'e', 'r', 'n');
101 shaper->features[KERN].end = INT_MAX;
105 * \brief Create a new shaper instance and preallocate data structures
106 * \param prealloc preallocation size
108 ASS_Shaper *ass_shaper_new(size_t prealloc)
110 ASS_Shaper *shaper = calloc(sizeof(*shaper), 1);
112 shaper->base_direction = FRIBIDI_PAR_ON;
113 init_features(shaper);
114 check_allocations(shaper, prealloc);
116 shaper->metrics_cache = ass_glyph_metrics_cache_create();
122 * \brief Free shaper and related data
124 void ass_shaper_free(ASS_Shaper *shaper)
126 ass_cache_done(shaper->metrics_cache);
127 free(shaper->event_text);
128 free(shaper->ctypes);
129 free(shaper->emblevels);
131 free(shaper->features);
135 void ass_shaper_font_data_free(ASS_ShaperFontData *priv)
138 for (i = 0; i < ASS_FONT_MAX_FACES; i++)
139 if (priv->fonts[i]) {
140 free(priv->metrics_data[i]);
141 hb_font_destroy(priv->fonts[i]);
142 hb_font_funcs_destroy(priv->font_funcs[i]);
148 * \brief Set features depending on properties of the run
150 static void set_run_features(ASS_Shaper *shaper, GlyphInfo *info)
152 // enable vertical substitutions for @font runs
153 if (info->font->desc.vertical)
154 shaper->features[VERT].value = shaper->features[VKNA].value = 1;
156 shaper->features[VERT].value = shaper->features[VKNA].value = 0;
160 * \brief Update HarfBuzz's idea of font metrics
161 * \param hb_font HarfBuzz font
162 * \param face associated FreeType font face
164 static void update_hb_size(hb_font_t *hb_font, FT_Face face)
166 hb_font_set_scale (hb_font,
167 ((uint64_t) face->size->metrics.x_scale * (uint64_t) face->units_per_EM) >> 16,
168 ((uint64_t) face->size->metrics.y_scale * (uint64_t) face->units_per_EM) >> 16);
169 hb_font_set_ppem (hb_font, face->size->metrics.x_ppem,
170 face->size->metrics.y_ppem);
175 * Cached glyph metrics getters follow
177 * These functions replace HarfBuzz' standard FreeType font functions
178 * and provide cached access to essential glyph metrics. This usually
179 * speeds up shaping a lot. It also allows us to use custom load flags.
183 GlyphMetricsHashValue *
184 get_cached_metrics(struct ass_shaper_metrics_data *metrics, FT_Face face,
185 hb_codepoint_t glyph)
187 GlyphMetricsHashValue *val;
189 metrics->hash_key.glyph_index = glyph;
190 val = ass_cache_get(metrics->metrics_cache, &metrics->hash_key);
193 int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
194 | FT_LOAD_IGNORE_TRANSFORM;
195 GlyphMetricsHashValue new_val;
197 if (FT_Load_Glyph(face, glyph, load_flags))
200 memcpy(&new_val.metrics, &face->glyph->metrics, sizeof(FT_Glyph_Metrics));
201 val = ass_cache_put(metrics->metrics_cache, &metrics->hash_key, &new_val);
208 get_glyph(hb_font_t *font, void *font_data, hb_codepoint_t unicode,
209 hb_codepoint_t variation, hb_codepoint_t *glyph, void *user_data)
211 FT_Face face = font_data;
214 *glyph = FT_Face_GetCharVariantIndex(face, unicode, variation);
216 *glyph = FT_Get_Char_Index(face, unicode);
222 cached_h_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
225 FT_Face face = font_data;
226 struct ass_shaper_metrics_data *metrics_priv = user_data;
227 GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
232 return metrics->metrics.horiAdvance;
236 cached_v_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
239 FT_Face face = font_data;
240 struct ass_shaper_metrics_data *metrics_priv = user_data;
241 GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
246 return metrics->metrics.vertAdvance;
251 cached_h_origin(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
252 hb_position_t *x, hb_position_t *y, void *user_data)
258 cached_v_origin(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
259 hb_position_t *x, hb_position_t *y, void *user_data)
261 FT_Face face = font_data;
262 struct ass_shaper_metrics_data *metrics_priv = user_data;
263 GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
268 *x = metrics->metrics.horiBearingX - metrics->metrics.vertBearingX;
269 *y = metrics->metrics.horiBearingY - (-metrics->metrics.vertBearingY);
275 get_h_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
276 hb_codepoint_t second, void *user_data)
278 FT_Face face = font_data;
281 if (FT_Get_Kerning (face, first, second, FT_KERNING_DEFAULT, &kern))
288 get_v_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
289 hb_codepoint_t second, void *user_data)
295 cached_extents(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
296 hb_glyph_extents_t *extents, void *user_data)
298 FT_Face face = font_data;
299 struct ass_shaper_metrics_data *metrics_priv = user_data;
300 GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
305 extents->x_bearing = metrics->metrics.horiBearingX;
306 extents->y_bearing = metrics->metrics.horiBearingY;
307 extents->width = metrics->metrics.width;
308 extents->height = metrics->metrics.height;
314 get_contour_point(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
315 unsigned int point_index, hb_position_t *x,
316 hb_position_t *y, void *user_data)
318 FT_Face face = font_data;
319 int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
320 | FT_LOAD_IGNORE_TRANSFORM;
322 if (FT_Load_Glyph(face, glyph, load_flags))
325 if (point_index >= (unsigned)face->glyph->outline.n_points)
328 *x = face->glyph->outline.points[point_index].x;
329 *y = face->glyph->outline.points[point_index].y;
335 * \brief Retrieve HarfBuzz font from cache.
336 * Create it from FreeType font, if needed.
337 * \param info glyph cluster
338 * \return HarfBuzz font
340 static hb_font_t *get_hb_font(ASS_Shaper *shaper, GlyphInfo *info)
342 ASS_Font *font = info->font;
343 hb_font_t **hb_fonts;
345 if (!font->shaper_priv)
346 font->shaper_priv = calloc(sizeof(ASS_ShaperFontData), 1);
349 hb_fonts = font->shaper_priv->fonts;
350 if (!hb_fonts[info->face_index]) {
351 hb_fonts[info->face_index] =
352 hb_ft_font_create(font->faces[info->face_index], NULL);
354 // set up cached metrics access
355 font->shaper_priv->metrics_data[info->face_index] =
356 calloc(sizeof(struct ass_shaper_metrics_data), 1);
357 struct ass_shaper_metrics_data *metrics =
358 font->shaper_priv->metrics_data[info->face_index];
359 metrics->metrics_cache = shaper->metrics_cache;
361 hb_font_funcs_t *funcs = hb_font_funcs_create();
362 font->shaper_priv->font_funcs[info->face_index] = funcs;
363 hb_font_funcs_set_glyph_func(funcs, get_glyph,
365 hb_font_funcs_set_glyph_h_advance_func(funcs, cached_h_advance,
367 hb_font_funcs_set_glyph_v_advance_func(funcs, cached_v_advance,
369 hb_font_funcs_set_glyph_h_origin_func(funcs, cached_h_origin,
371 hb_font_funcs_set_glyph_v_origin_func(funcs, cached_v_origin,
373 hb_font_funcs_set_glyph_h_kerning_func(funcs, get_h_kerning,
375 hb_font_funcs_set_glyph_v_kerning_func(funcs, get_v_kerning,
377 hb_font_funcs_set_glyph_extents_func(funcs, cached_extents,
379 hb_font_funcs_set_glyph_contour_point_func(funcs, get_contour_point,
381 hb_font_set_funcs(hb_fonts[info->face_index], funcs,
382 font->faces[info->face_index], NULL);
385 ass_face_set_size(font->faces[info->face_index], info->font_size);
386 update_hb_size(hb_fonts[info->face_index], font->faces[info->face_index]);
388 // update hash key for cached metrics
389 struct ass_shaper_metrics_data *metrics =
390 font->shaper_priv->metrics_data[info->face_index];
391 metrics->hash_key.font = info->font;
392 metrics->hash_key.face_index = info->face_index;
393 metrics->hash_key.size = info->font_size;
394 metrics->hash_key.scale_x = double_to_d6(info->scale_x);
395 metrics->hash_key.scale_y = double_to_d6(info->scale_y);
397 return hb_fonts[info->face_index];
401 * \brief Shape event text with HarfBuzz. Full OpenType shaping.
402 * \param glyphs glyph clusters
403 * \param len number of clusters
405 static void shape_harfbuzz(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
417 for (i = 0; i < len && run < MAX_RUNS; i++, run++) {
418 // get length and level of the current run
420 int level = glyphs[i].shape_run_id;
421 int direction = shaper->emblevels[k] % 2;
422 while (i < (len - 1) && level == glyphs[i+1].shape_run_id)
424 runs[run].offset = k;
426 runs[run].buf = hb_buffer_create(i - k + 1);
427 runs[run].font = get_hb_font(shaper, glyphs + k);
428 set_run_features(shaper, glyphs + k);
429 hb_buffer_set_direction(runs[run].buf, direction ? HB_DIRECTION_RTL :
431 hb_buffer_set_language(runs[run].buf, shaper->language);
432 hb_buffer_add_utf32(runs[run].buf, shaper->event_text + k, i - k + 1,
434 hb_shape(runs[run].font, runs[run].buf, shaper->features,
438 // Initialize: skip all glyphs, this is undone later as needed
439 for (i = 0; i < len; i++)
442 // Update glyph indexes, positions and advances from the shaped runs
443 for (i = 0; i < run; i++) {
444 int num_glyphs = hb_buffer_get_length(runs[i].buf);
445 hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(runs[i].buf, NULL);
446 hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(runs[i].buf, NULL);
448 for (j = 0; j < num_glyphs; j++) {
449 int idx = glyph_info[j].cluster + runs[i].offset;
450 GlyphInfo *info = glyphs + idx;
451 GlyphInfo *root = info;
453 // if we have more than one glyph per cluster, allocate a new one
454 // and attach to the root glyph
455 if (info->skip == 0) {
458 info->next = malloc(sizeof(GlyphInfo));
459 memcpy(info->next, info, sizeof(GlyphInfo));
464 // set position and advance
466 info->glyph_index = glyph_info[j].codepoint;
467 info->offset.x = pos[j].x_offset * info->scale_x;
468 info->offset.y = -pos[j].y_offset * info->scale_y;
469 info->advance.x = pos[j].x_advance * info->scale_x;
470 info->advance.y = -pos[j].y_advance * info->scale_y;
472 // accumulate advance in the root glyph
473 root->cluster_advance.x += info->advance.x;
474 root->cluster_advance.y += info->advance.y;
478 // Free runs and associated data
479 for (i = 0; i < run; i++) {
480 hb_buffer_destroy(runs[i].buf);
486 * \brief Shape event text with FriBidi. Does mirroring and simple
488 * \param len number of clusters
490 static void shape_fribidi(ASS_Shaper *shaper, size_t len)
492 FriBidiJoiningType *joins = calloc(sizeof(*joins), len);
494 fribidi_get_joining_types(shaper->event_text, len, joins);
495 fribidi_join_arabic(shaper->ctypes, len, shaper->emblevels, joins);
496 fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
497 shaper->emblevels, len, joins, shaper->event_text);
503 * \brief Toggle kerning for HarfBuzz shaping.
504 * NOTE: currently only works with OpenType fonts, the TrueType fallback *always*
505 * kerns. It's a bug in HarfBuzz.
507 void ass_shaper_set_kerning(ASS_Shaper *shaper, int kern)
509 shaper->features[KERN].value = !!kern;
513 * \brief Find shape runs according to the event's selected fonts
515 void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv,
516 GlyphInfo *glyphs, size_t len)
521 for (i = 0; i < len; i++) {
522 GlyphInfo *last = glyphs + i - 1;
523 GlyphInfo *info = glyphs + i;
525 if (info->symbol == 0xfffc)
527 // set size and get glyph index
528 ass_font_get_index(render_priv->fontconfig_priv, info->font,
529 info->symbol, &info->face_index, &info->glyph_index);
530 // shape runs share the same font face and size
531 if (i > 0 && (last->font != info->font ||
532 last->font_size != info->font_size ||
533 last->face_index != info->face_index))
535 info->shape_run_id = shape_run;
541 * \brief Set base direction (paragraph direction) of the text.
542 * \param dir base direction
544 void ass_shaper_set_base_direction(ASS_Shaper *shaper, FriBidiParType dir)
546 shaper->base_direction = dir;
550 * \brief Set language hint. Some languages have specific character variants,
551 * like Serbian Cyrillic.
552 * \param lang ISO 639-1 two-letter language code
554 void ass_shaper_set_language(ASS_Shaper *shaper, const char *code)
556 shaper->language = hb_language_from_string(code);
560 * \brief Shape an event's text. Calculates directional runs and shapes them.
561 * \param text_info event's text
563 void ass_shaper_shape(ASS_Shaper *shaper, TextInfo *text_info)
567 GlyphInfo *glyphs = text_info->glyphs;
569 check_allocations(shaper, text_info->length);
571 // Get bidi character types and embedding levels
573 for (i = 0; i < text_info->length; i++) {
574 shaper->event_text[i] = glyphs[i].symbol;
575 // embedding levels should be calculated paragraph by paragraph
576 if (glyphs[i].symbol == '\n' || i == text_info->length - 1) {
577 dir = shaper->base_direction;
578 fribidi_get_bidi_types(shaper->event_text + last_break,
579 i - last_break + 1, shaper->ctypes + last_break);
580 fribidi_get_par_embedding_levels(shaper->ctypes + last_break,
581 i - last_break + 1, &dir, shaper->emblevels + last_break);
586 // add embedding levels to shape runs for final runs
587 for (i = 0; i < text_info->length; i++) {
588 glyphs[i].shape_run_id += shaper->emblevels[i];
591 //shape_fribidi(shaper, text_info->length);
592 shape_harfbuzz(shaper, glyphs, text_info->length);
595 for (i = 0; i < text_info->length; i++) {
596 glyphs[i].symbol = shaper->event_text[i];
597 // Skip direction override control characters
598 // NOTE: Behdad said HarfBuzz is supposed to remove these, but this hasn't
599 // been implemented yet
600 if (glyphs[i].symbol <= 0x202F && glyphs[i].symbol >= 0x202a) {
601 glyphs[i].symbol = 0;
608 * \brief clean up additional data temporarily needed for shaping and
609 * (e.g. additional glyphs allocated)
611 void ass_shaper_cleanup(ASS_Shaper *shaper, TextInfo *text_info)
615 for (i = 0; i < text_info->length; i++) {
616 GlyphInfo *info = text_info->glyphs + i;
619 GlyphInfo *next = info->next;
627 * \brief Calculate reorder map to render glyphs in visual order
629 FriBidiStrIndex *ass_shaper_reorder(ASS_Shaper *shaper, TextInfo *text_info)
633 // Initialize reorder map
634 for (i = 0; i < text_info->length; i++)
637 // Create reorder map line-by-line
638 for (i = 0; i < text_info->n_lines; i++) {
639 LineInfo *line = text_info->lines + i;
641 FriBidiParType dir = FRIBIDI_PAR_ON;
643 level = fribidi_reorder_line(0,
644 shaper->ctypes + line->offset, line->len, 0, dir,
645 shaper->emblevels + line->offset, NULL,
646 shaper->cmap + line->offset);
653 * \brief Resolve a Windows font encoding number to a suitable
654 * base direction. 177 and 178 are Hebrew and Arabic respectively, and
655 * they map to RTL. 1 is autodetection and is mapped to just that.
656 * Everything else is mapped to LTR.
657 * \param enc Windows font encoding
659 FriBidiParType resolve_base_direction(int enc)
663 return FRIBIDI_PAR_ON;
666 return FRIBIDI_PAR_RTL;
668 return FRIBIDI_PAR_LTR;