Remove unused variable
[libass.git] / libass / ass_shaper.c
blobe6745da864da1ff2ff98461b1793bb7e11bd3ba1
1 /*
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>
20 #include <hb-ft.h>
22 #include "ass_shaper.h"
23 #include "ass_render.h"
24 #include "ass_font.h"
25 #include "ass_parse.h"
27 #define MAX_RUNS 50
29 enum {
30 VERT = 0,
31 VKNA,
32 KERN
34 #define NUM_FEATURES 3
36 struct ass_shaper {
37 // FriBidi log2vis
38 int n_glyphs;
39 FriBidiChar *event_text;
40 FriBidiCharType *ctypes;
41 FriBidiLevel *emblevels;
42 FriBidiStrIndex *cmap;
43 FriBidiParType base_direction;
44 // OpenType features
45 int n_features;
46 hb_feature_t *features;
49 struct ass_shaper_font_data {
50 hb_font_t *fonts[ASS_FONT_MAX_FACES];
53 /**
54 * \brief Print version information
56 void ass_shaper_info(ASS_Library *lib)
58 ass_msg(lib, MSGL_V, "Complex text layout enabled, using FriBidi "
59 FRIBIDI_VERSION " HarfBuzz-ng %s", hb_version_string());
62 /**
63 * \brief grow arrays, if needed
64 * \param new_size requested size
66 static void check_allocations(ASS_Shaper *shaper, size_t new_size)
68 if (new_size > shaper->n_glyphs) {
69 shaper->event_text = realloc(shaper->event_text, sizeof(FriBidiChar) * new_size);
70 shaper->ctypes = realloc(shaper->ctypes, sizeof(FriBidiCharType) * new_size);
71 shaper->emblevels = realloc(shaper->emblevels, sizeof(FriBidiLevel) * new_size);
72 shaper->cmap = realloc(shaper->cmap, sizeof(FriBidiStrIndex) * new_size);
76 /**
77 * \brief set up the HarfBuzz OpenType feature list with some
78 * standard features.
80 static void init_features(ASS_Shaper *shaper)
82 shaper->features = calloc(sizeof(hb_feature_t), NUM_FEATURES);
84 shaper->n_features = NUM_FEATURES;
85 shaper->features[VERT].tag = HB_TAG('v', 'e', 'r', 't');
86 shaper->features[VERT].end = INT_MAX;
87 shaper->features[VKNA].tag = HB_TAG('v', 'k', 'n', 'a');
88 shaper->features[VKNA].end = INT_MAX;
89 shaper->features[KERN].tag = HB_TAG('k', 'e', 'r', 'n');
90 shaper->features[KERN].end = INT_MAX;
93 /**
94 * \brief Create a new shaper instance and preallocate data structures
95 * \param prealloc preallocation size
97 ASS_Shaper *ass_shaper_new(size_t prealloc)
99 ASS_Shaper *shaper = calloc(sizeof(*shaper), 1);
101 shaper->base_direction = FRIBIDI_PAR_ON;
102 init_features(shaper);
103 check_allocations(shaper, prealloc);
105 return shaper;
109 * \brief Free shaper and related data
111 void ass_shaper_free(ASS_Shaper *shaper)
113 free(shaper->event_text);
114 free(shaper->ctypes);
115 free(shaper->emblevels);
116 free(shaper->cmap);
117 free(shaper->features);
118 free(shaper);
121 void ass_shaper_font_data_free(ASS_ShaperFontData *priv)
123 int i;
124 for (i = 0; i < ASS_FONT_MAX_FACES; i++)
125 if (priv->fonts[i])
126 hb_font_destroy(priv->fonts[i]);
127 free(priv);
131 * \brief Set features depending on properties of the run
133 static void set_run_features(ASS_Shaper *shaper, GlyphInfo *info)
135 // enable vertical substitutions for @font runs
136 if (info->font->desc.vertical)
137 shaper->features[VERT].value = shaper->features[VKNA].value = 1;
138 else
139 shaper->features[VERT].value = shaper->features[VKNA].value = 0;
143 * \brief Retrieve HarfBuzz font from cache.
144 * Create it from FreeType font, if needed.
145 * \param info glyph cluster
146 * \return HarfBuzz font
148 static hb_font_t *get_hb_font(GlyphInfo *info)
150 ASS_Font *font = info->font;
151 hb_font_t **hb_fonts;
153 if (!font->shaper_priv)
154 font->shaper_priv = calloc(sizeof(ASS_ShaperFontData), 1);
156 hb_fonts = font->shaper_priv->fonts;
157 if (!hb_fonts[info->face_index])
158 hb_fonts[info->face_index] =
159 hb_ft_font_create(font->faces[info->face_index], NULL);
161 ass_face_set_size(font->faces[info->face_index], info->font_size);
163 return hb_fonts[info->face_index];
167 * \brief Shape event text with HarfBuzz. Full OpenType shaping.
168 * \param glyphs glyph clusters
169 * \param len number of clusters
171 static void shape_harfbuzz(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
173 int i, j;
174 int run = 0;
175 struct {
176 int offset;
177 int end;
178 hb_buffer_t *buf;
179 hb_font_t *font;
180 } runs[MAX_RUNS];
183 for (i = 0; i < len && run < MAX_RUNS; i++, run++) {
184 // get length and level of the current run
185 int k = i;
186 int level = glyphs[i].shape_run_id;
187 int direction = shaper->emblevels[k] % 2;
188 while (i < (len - 1) && level == glyphs[i+1].shape_run_id)
189 i++;
190 //printf("run %d from %d to %d with level %d\n", run, k, i, level);
191 runs[run].offset = k;
192 runs[run].end = i;
193 runs[run].buf = hb_buffer_create(i - k + 1);
194 runs[run].font = get_hb_font(glyphs + k);
195 set_run_features(shaper, glyphs + k);
196 hb_buffer_set_direction(runs[run].buf, direction ? HB_DIRECTION_RTL :
197 HB_DIRECTION_LTR);
198 hb_buffer_add_utf32(runs[run].buf, shaper->event_text + k, i - k + 1,
199 0, i - k + 1);
200 hb_shape(runs[run].font, runs[run].buf, shaper->features,
201 shaper->n_features);
203 //printf("shaped %d runs\n", run);
205 // Initialize: skip all glyphs, this is undone later as needed
206 for (i = 0; i < len; i++)
207 glyphs[i].skip = 1;
209 // Update glyph indexes, positions and advances from the shaped runs
210 for (i = 0; i < run; i++) {
211 int num_glyphs = hb_buffer_get_length(runs[i].buf);
212 hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(runs[i].buf, NULL);
213 hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(runs[i].buf, NULL);
214 //printf("run text len %d num_glyphs %d\n", runs[i].end - runs[i].offset + 1,
215 // num_glyphs);
216 // Update glyphs
217 for (j = 0; j < num_glyphs; j++) {
218 int idx = glyph_info[j].cluster + runs[i].offset;
219 GlyphInfo *info = glyphs + idx;
220 GlyphInfo *root = info;
221 #if 0
222 printf("run %d cluster %d codepoint %d -> '%c'\n", i, idx,
223 glyph_info[j].codepoint, event_text[idx]);
224 printf("position %d %d advance %d %d\n",
225 pos[j].x_offset, pos[j].y_offset,
226 pos[j].x_advance, pos[j].y_advance);
227 #endif
229 // if we have more than one glyph per cluster, allocate a new one
230 // and attach to the root glyph
231 if (info->skip == 0) {
232 //printf("duplicate cluster entry, adding glyph\n");
233 while (info->next)
234 info = info->next;
235 info->next = malloc(sizeof(GlyphInfo));
236 memcpy(info->next, info, sizeof(GlyphInfo));
237 info = info->next;
238 info->next = NULL;
241 // set position and advance
242 info->skip = 0;
243 info->glyph_index = glyph_info[j].codepoint;
244 info->offset.x = pos[j].x_offset * info->scale_x;
245 info->offset.y = -pos[j].y_offset * info->scale_y;
246 info->advance.x = pos[j].x_advance * info->scale_x;
247 info->advance.y = -pos[j].y_advance * info->scale_y;
249 // accumulate advance in the root glyph
250 root->cluster_advance.x += info->advance.x;
251 root->cluster_advance.y += info->advance.y;
255 // Free runs and associated data
256 for (i = 0; i < run; i++) {
257 hb_buffer_destroy(runs[i].buf);
263 * \brief Shape event text with FriBidi. Does mirroring and simple
264 * Arabic shaping.
265 * \param len number of clusters
267 static void shape_fribidi(ASS_Shaper *shaper, size_t len)
269 FriBidiJoiningType *joins = calloc(sizeof(*joins), len);
271 fribidi_get_joining_types(shaper->event_text, len, joins);
272 fribidi_join_arabic(shaper->ctypes, len, shaper->emblevels, joins);
273 fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
274 shaper->emblevels, len, joins, shaper->event_text);
276 free(joins);
280 * \brief Toggle kerning for HarfBuzz shaping.
281 * NOTE: currently only works with OpenType fonts, the TrueType fallback *always*
282 * kerns. It's a bug in HarfBuzz.
284 void ass_shaper_set_kerning(ASS_Shaper *shaper, int kern)
286 shaper->features[KERN].value = !!kern;
290 * \brief Find shape runs according to the event's selected fonts
292 void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv,
293 GlyphInfo *glyphs, size_t len)
295 int i;
296 int shape_run = 0;
298 for (i = 0; i < len; i++) {
299 GlyphInfo *last = glyphs + i - 1;
300 GlyphInfo *info = glyphs + i;
301 // skip drawings
302 if (info->symbol == 0xfffc)
303 continue;
304 // initialize face_index to continue with the same face, if possible
305 // XXX: can be problematic in some cases, for example if a font misses
306 // a single glyph, like space (U+0020)
307 if (i > 0)
308 info->face_index = last->face_index;
309 // set size and get glyph index
310 ass_font_get_index(render_priv->fontconfig_priv, info->font,
311 info->symbol, &info->face_index, &info->glyph_index);
312 // shape runs share the same font face and size
313 if (i > 0 && (last->font != info->font ||
314 last->font_size != info->font_size ||
315 last->face_index != info->face_index))
316 shape_run++;
317 info->shape_run_id = shape_run;
318 //printf("glyph '%c' shape run id %d face %d\n", info->symbol, info->shape_run_id,
319 // info->face_index);
325 * \brief Set base direction (paragraph direction) of the text.
326 * \param dir base direction
328 void ass_shaper_set_base_direction(ASS_Shaper *shaper, FriBidiParType dir)
330 shaper->base_direction = dir;
334 * \brief Shape an event's text. Calculates directional runs and shapes them.
335 * \param text_info event's text
337 void ass_shaper_shape(ASS_Shaper *shaper, TextInfo *text_info)
339 int i, last_break;
340 FriBidiParType dir;
341 GlyphInfo *glyphs = text_info->glyphs;
343 check_allocations(shaper, text_info->length);
345 // Get bidi character types and embedding levels
346 last_break = 0;
347 for (i = 0; i < text_info->length; i++) {
348 shaper->event_text[i] = glyphs[i].symbol;
349 // embedding levels should be calculated paragraph by paragraph
350 if (glyphs[i].symbol == '\n' || i == text_info->length - 1) {
351 //printf("paragraph from %d to %d\n", last_break, i);
352 dir = shaper->base_direction;
353 fribidi_get_bidi_types(shaper->event_text + last_break,
354 i - last_break + 1, shaper->ctypes + last_break);
355 fribidi_get_par_embedding_levels(shaper->ctypes + last_break,
356 i - last_break + 1, &dir, shaper->emblevels + last_break);
357 last_break = i + 1;
361 // add embedding levels to shape runs for final runs
362 for (i = 0; i < text_info->length; i++) {
363 glyphs[i].shape_run_id += shaper->emblevels[i];
366 #if 0
367 printf("levels ");
368 for (i = 0; i < text_info->length; i++) {
369 printf("%d ", glyphs[i].shape_run_id);
371 printf("\n");
372 #endif
374 //shape_fribidi(shaper, text_info->length);
375 shape_harfbuzz(shaper, glyphs, text_info->length);
377 // Update glyphs
378 for (i = 0; i < text_info->length; i++) {
379 glyphs[i].symbol = shaper->event_text[i];
380 // Skip direction override control characters
381 // NOTE: Behdad said HarfBuzz is supposed to remove these, but this hasn't
382 // been implemented yet
383 if (glyphs[i].symbol <= 0x202F && glyphs[i].symbol >= 0x202a) {
384 glyphs[i].symbol = 0;
385 glyphs[i].skip++;
391 * \brief clean up additional data temporarily needed for shaping and
392 * (e.g. additional glyphs allocated)
394 void ass_shaper_cleanup(ASS_Shaper *shaper, TextInfo *text_info)
396 int i;
398 for (i = 0; i < text_info->length; i++) {
399 GlyphInfo *info = text_info->glyphs + i;
400 info = info->next;
401 while (info) {
402 GlyphInfo *next = info->next;
403 free(info);
404 info = next;
410 * \brief Calculate reorder map to render glyphs in visual order
412 FriBidiStrIndex *ass_shaper_reorder(ASS_Shaper *shaper, TextInfo *text_info)
414 int i;
416 // Initialize reorder map
417 for (i = 0; i < text_info->length; i++)
418 shaper->cmap[i] = i;
420 // Create reorder map line-by-line
421 for (i = 0; i < text_info->n_lines; i++) {
422 LineInfo *line = text_info->lines + i;
423 int level;
424 FriBidiParType dir = FRIBIDI_PAR_ON;
426 // FIXME: we should actually specify
427 // the correct paragraph base direction
428 level = fribidi_reorder_line(FRIBIDI_FLAGS_DEFAULT,
429 shaper->ctypes + line->offset, line->len, 0, dir,
430 shaper->emblevels + line->offset, NULL,
431 shaper->cmap + line->offset);
432 //printf("reorder line %d to level %d\n", i, level);
435 #if 0
436 printf("map ");
437 for (i = 0; i < text_info->length; i++) {
438 printf("%d ", cmap[i]);
440 printf("\n");
441 #endif
443 return shaper->cmap;
447 * \brief Resolve a Windows font encoding number to a suitable
448 * base direction. 177 and 178 are Hebrew and Arabic respectively, and
449 * they map to RTL. 1 is autodetection and is mapped to just that.
450 * Everything else is mapped to LTR.
451 * \param enc Windows font encoding
453 FriBidiParType resolve_base_direction(int enc)
455 switch (enc) {
456 case 1:
457 return FRIBIDI_PAR_ON;
458 case 177:
459 case 178:
460 return FRIBIDI_PAR_RTL;
461 default:
462 return FRIBIDI_PAR_LTR;