34300
|
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
|
34342
|
21 #ifdef CONFIG_FRIBIDI
|
34300
|
22 #include <fribidi/fribidi.h>
|
34342
|
23 #endif
|
34300
|
24
|
|
25 #include "ass_shaper.h"
|
|
26 #include "ass_render.h"
|
|
27 #include "ass_font.h"
|
|
28 #include "ass_parse.h"
|
|
29 #include "ass_cache.h"
|
|
30
|
|
31 #define MAX_RUNS 50
|
|
32
|
|
33 #ifdef CONFIG_HARFBUZZ
|
|
34 #include <hb-ft.h>
|
|
35 enum {
|
|
36 VERT = 0,
|
|
37 VKNA,
|
|
38 KERN
|
|
39 };
|
|
40 #define NUM_FEATURES 3
|
|
41 #endif
|
|
42
|
|
43 struct ass_shaper {
|
|
44 ASS_ShapingLevel shaping_level;
|
|
45
|
|
46 // FriBidi log2vis
|
|
47 int n_glyphs;
|
34342
|
48 #ifdef CONFIG_FRIBIDI
|
34300
|
49 FriBidiChar *event_text;
|
|
50 FriBidiCharType *ctypes;
|
|
51 FriBidiLevel *emblevels;
|
34342
|
52 #endif
|
34300
|
53 FriBidiStrIndex *cmap;
|
|
54 FriBidiParType base_direction;
|
|
55
|
|
56 #ifdef CONFIG_HARFBUZZ
|
|
57 // OpenType features
|
|
58 int n_features;
|
|
59 hb_feature_t *features;
|
|
60 hb_language_t language;
|
|
61
|
|
62 // Glyph metrics cache, to speed up shaping
|
|
63 Cache *metrics_cache;
|
|
64 #endif
|
|
65 };
|
|
66
|
|
67 #ifdef CONFIG_HARFBUZZ
|
|
68 struct ass_shaper_metrics_data {
|
|
69 Cache *metrics_cache;
|
|
70 GlyphMetricsHashKey hash_key;
|
|
71 int vertical;
|
|
72 };
|
|
73
|
|
74 struct ass_shaper_font_data {
|
|
75 hb_font_t *fonts[ASS_FONT_MAX_FACES];
|
|
76 hb_font_funcs_t *font_funcs[ASS_FONT_MAX_FACES];
|
|
77 struct ass_shaper_metrics_data *metrics_data[ASS_FONT_MAX_FACES];
|
|
78 };
|
|
79 #endif
|
|
80
|
|
81 /**
|
|
82 * \brief Print version information
|
|
83 */
|
|
84 void ass_shaper_info(ASS_Library *lib)
|
|
85 {
|
34342
|
86 ass_msg(lib, MSGL_V, "Shaper:"
|
|
87 #ifdef CONFIG_FRIBIDI
|
|
88 " FriBidi " FRIBIDI_VERSION " (SIMPLE)"
|
|
89 #endif
|
34300
|
90 #ifdef CONFIG_HARFBUZZ
|
|
91 " HarfBuzz-ng %s (COMPLEX)", hb_version_string()
|
|
92 #endif
|
|
93 );
|
|
94 }
|
|
95
|
|
96 /**
|
|
97 * \brief grow arrays, if needed
|
|
98 * \param new_size requested size
|
|
99 */
|
|
100 static void check_allocations(ASS_Shaper *shaper, size_t new_size)
|
|
101 {
|
|
102 if (new_size > shaper->n_glyphs) {
|
34342
|
103 #ifdef CONFIG_FRIBIDI
|
34300
|
104 shaper->event_text = realloc(shaper->event_text, sizeof(FriBidiChar) * new_size);
|
|
105 shaper->ctypes = realloc(shaper->ctypes, sizeof(FriBidiCharType) * new_size);
|
|
106 shaper->emblevels = realloc(shaper->emblevels, sizeof(FriBidiLevel) * new_size);
|
34342
|
107 #endif
|
34300
|
108 shaper->cmap = realloc(shaper->cmap, sizeof(FriBidiStrIndex) * new_size);
|
|
109 }
|
|
110 }
|
|
111
|
|
112 /**
|
|
113 * \brief Free shaper and related data
|
|
114 */
|
|
115 void ass_shaper_free(ASS_Shaper *shaper)
|
|
116 {
|
|
117 #ifdef CONFIG_HARFBUZZ
|
|
118 ass_cache_done(shaper->metrics_cache);
|
|
119 free(shaper->features);
|
|
120 #endif
|
34342
|
121 #ifdef CONFIG_FRIBIDI
|
34300
|
122 free(shaper->event_text);
|
|
123 free(shaper->ctypes);
|
|
124 free(shaper->emblevels);
|
34342
|
125 #endif
|
34300
|
126 free(shaper->cmap);
|
|
127 free(shaper);
|
|
128 }
|
|
129
|
|
130 void ass_shaper_font_data_free(ASS_ShaperFontData *priv)
|
|
131 {
|
|
132 #ifdef CONFIG_HARFBUZZ
|
|
133 int i;
|
|
134 for (i = 0; i < ASS_FONT_MAX_FACES; i++)
|
|
135 if (priv->fonts[i]) {
|
|
136 free(priv->metrics_data[i]);
|
|
137 hb_font_destroy(priv->fonts[i]);
|
|
138 hb_font_funcs_destroy(priv->font_funcs[i]);
|
|
139 }
|
|
140 free(priv);
|
|
141 #endif
|
|
142 }
|
|
143
|
|
144 #ifdef CONFIG_HARFBUZZ
|
|
145 /**
|
|
146 * \brief set up the HarfBuzz OpenType feature list with some
|
|
147 * standard features.
|
|
148 */
|
|
149 static void init_features(ASS_Shaper *shaper)
|
|
150 {
|
|
151 shaper->features = calloc(sizeof(hb_feature_t), NUM_FEATURES);
|
|
152
|
|
153 shaper->n_features = NUM_FEATURES;
|
|
154 shaper->features[VERT].tag = HB_TAG('v', 'e', 'r', 't');
|
|
155 shaper->features[VERT].end = INT_MAX;
|
|
156 shaper->features[VKNA].tag = HB_TAG('v', 'k', 'n', 'a');
|
|
157 shaper->features[VKNA].end = INT_MAX;
|
|
158 shaper->features[KERN].tag = HB_TAG('k', 'e', 'r', 'n');
|
|
159 shaper->features[KERN].end = INT_MAX;
|
|
160 }
|
|
161
|
|
162 /**
|
|
163 * \brief Set features depending on properties of the run
|
|
164 */
|
|
165 static void set_run_features(ASS_Shaper *shaper, GlyphInfo *info)
|
|
166 {
|
|
167 // enable vertical substitutions for @font runs
|
|
168 if (info->font->desc.vertical)
|
|
169 shaper->features[VERT].value = shaper->features[VKNA].value = 1;
|
|
170 else
|
|
171 shaper->features[VERT].value = shaper->features[VKNA].value = 0;
|
|
172 }
|
|
173
|
|
174 /**
|
|
175 * \brief Update HarfBuzz's idea of font metrics
|
|
176 * \param hb_font HarfBuzz font
|
|
177 * \param face associated FreeType font face
|
|
178 */
|
|
179 static void update_hb_size(hb_font_t *hb_font, FT_Face face)
|
|
180 {
|
|
181 hb_font_set_scale (hb_font,
|
|
182 ((uint64_t) face->size->metrics.x_scale * (uint64_t) face->units_per_EM) >> 16,
|
|
183 ((uint64_t) face->size->metrics.y_scale * (uint64_t) face->units_per_EM) >> 16);
|
|
184 hb_font_set_ppem (hb_font, face->size->metrics.x_ppem,
|
|
185 face->size->metrics.y_ppem);
|
|
186 }
|
|
187
|
|
188
|
|
189 /*
|
|
190 * Cached glyph metrics getters follow
|
|
191 *
|
|
192 * These functions replace HarfBuzz' standard FreeType font functions
|
|
193 * and provide cached access to essential glyph metrics. This usually
|
|
194 * speeds up shaping a lot. It also allows us to use custom load flags.
|
|
195 *
|
|
196 */
|
|
197
|
|
198 GlyphMetricsHashValue *
|
|
199 get_cached_metrics(struct ass_shaper_metrics_data *metrics, FT_Face face,
|
|
200 hb_codepoint_t glyph)
|
|
201 {
|
|
202 GlyphMetricsHashValue *val;
|
|
203
|
|
204 metrics->hash_key.glyph_index = glyph;
|
|
205 val = ass_cache_get(metrics->metrics_cache, &metrics->hash_key);
|
|
206
|
|
207 if (!val) {
|
|
208 int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
|
|
209 | FT_LOAD_IGNORE_TRANSFORM;
|
|
210 GlyphMetricsHashValue new_val;
|
|
211
|
|
212 if (FT_Load_Glyph(face, glyph, load_flags))
|
|
213 return NULL;
|
|
214
|
|
215 memcpy(&new_val.metrics, &face->glyph->metrics, sizeof(FT_Glyph_Metrics));
|
36363
|
216
|
|
217 // if @font rendering is enabled and the glyph should be rotated,
|
|
218 // make cached_h_advance pick up the right advance later
|
|
219 if (metrics->vertical && glyph >= VERTICAL_LOWER_BOUND)
|
|
220 new_val.metrics.horiAdvance = new_val.metrics.vertAdvance;
|
|
221
|
34300
|
222 val = ass_cache_put(metrics->metrics_cache, &metrics->hash_key, &new_val);
|
|
223 }
|
|
224
|
|
225 return val;
|
|
226 }
|
|
227
|
|
228 static hb_bool_t
|
|
229 get_glyph(hb_font_t *font, void *font_data, hb_codepoint_t unicode,
|
|
230 hb_codepoint_t variation, hb_codepoint_t *glyph, void *user_data)
|
|
231 {
|
|
232 FT_Face face = font_data;
|
|
233
|
|
234 if (variation)
|
|
235 *glyph = FT_Face_GetCharVariantIndex(face, unicode, variation);
|
|
236 else
|
|
237 *glyph = FT_Get_Char_Index(face, unicode);
|
|
238
|
|
239 return *glyph != 0;
|
|
240 }
|
|
241
|
|
242 static hb_position_t
|
|
243 cached_h_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
|
|
244 void *user_data)
|
|
245 {
|
|
246 FT_Face face = font_data;
|
|
247 struct ass_shaper_metrics_data *metrics_priv = user_data;
|
|
248 GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
|
|
249
|
|
250 if (!metrics)
|
|
251 return 0;
|
|
252
|
|
253 return metrics->metrics.horiAdvance;
|
|
254 }
|
|
255
|
|
256 static hb_position_t
|
|
257 cached_v_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
|
|
258 void *user_data)
|
|
259 {
|
|
260 FT_Face face = font_data;
|
|
261 struct ass_shaper_metrics_data *metrics_priv = user_data;
|
|
262 GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
|
|
263
|
|
264 if (!metrics)
|
|
265 return 0;
|
|
266
|
|
267 return metrics->metrics.vertAdvance;
|
|
268
|
|
269 }
|
|
270
|
|
271 static hb_bool_t
|
|
272 cached_h_origin(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
|
|
273 hb_position_t *x, hb_position_t *y, void *user_data)
|
|
274 {
|
|
275 return 1;
|
|
276 }
|
|
277
|
|
278 static hb_bool_t
|
|
279 cached_v_origin(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
|
|
280 hb_position_t *x, hb_position_t *y, void *user_data)
|
|
281 {
|
|
282 FT_Face face = font_data;
|
|
283 struct ass_shaper_metrics_data *metrics_priv = user_data;
|
|
284 GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
|
|
285
|
|
286 if (!metrics)
|
|
287 return 0;
|
|
288
|
|
289 *x = metrics->metrics.horiBearingX - metrics->metrics.vertBearingX;
|
|
290 *y = metrics->metrics.horiBearingY - (-metrics->metrics.vertBearingY);
|
|
291
|
|
292 return 1;
|
|
293 }
|
|
294
|
|
295 static hb_position_t
|
|
296 get_h_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
|
|
297 hb_codepoint_t second, void *user_data)
|
|
298 {
|
|
299 FT_Face face = font_data;
|
|
300 FT_Vector kern;
|
|
301
|
|
302 if (FT_Get_Kerning (face, first, second, FT_KERNING_DEFAULT, &kern))
|
|
303 return 0;
|
|
304
|
|
305 return kern.x;
|
|
306 }
|
|
307
|
|
308 static hb_position_t
|
|
309 get_v_kerning(hb_font_t *font, void *font_data, hb_codepoint_t first,
|
|
310 hb_codepoint_t second, void *user_data)
|
|
311 {
|
|
312 return 0;
|
|
313 }
|
|
314
|
|
315 static hb_bool_t
|
|
316 cached_extents(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
|
|
317 hb_glyph_extents_t *extents, void *user_data)
|
|
318 {
|
|
319 FT_Face face = font_data;
|
|
320 struct ass_shaper_metrics_data *metrics_priv = user_data;
|
|
321 GlyphMetricsHashValue *metrics = get_cached_metrics(metrics_priv, face, glyph);
|
|
322
|
|
323 if (!metrics)
|
|
324 return 0;
|
|
325
|
|
326 extents->x_bearing = metrics->metrics.horiBearingX;
|
|
327 extents->y_bearing = metrics->metrics.horiBearingY;
|
|
328 extents->width = metrics->metrics.width;
|
|
329 extents->height = metrics->metrics.height;
|
|
330
|
|
331 return 1;
|
|
332 }
|
|
333
|
|
334 static hb_bool_t
|
|
335 get_contour_point(hb_font_t *font, void *font_data, hb_codepoint_t glyph,
|
|
336 unsigned int point_index, hb_position_t *x,
|
|
337 hb_position_t *y, void *user_data)
|
|
338 {
|
|
339 FT_Face face = font_data;
|
|
340 int load_flags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
|
|
341 | FT_LOAD_IGNORE_TRANSFORM;
|
|
342
|
|
343 if (FT_Load_Glyph(face, glyph, load_flags))
|
|
344 return 0;
|
|
345
|
|
346 if (point_index >= (unsigned)face->glyph->outline.n_points)
|
|
347 return 0;
|
|
348
|
|
349 *x = face->glyph->outline.points[point_index].x;
|
|
350 *y = face->glyph->outline.points[point_index].y;
|
|
351
|
|
352 return 1;
|
|
353 }
|
|
354
|
|
355 /**
|
|
356 * \brief Retrieve HarfBuzz font from cache.
|
|
357 * Create it from FreeType font, if needed.
|
|
358 * \param info glyph cluster
|
|
359 * \return HarfBuzz font
|
|
360 */
|
|
361 static hb_font_t *get_hb_font(ASS_Shaper *shaper, GlyphInfo *info)
|
|
362 {
|
|
363 ASS_Font *font = info->font;
|
|
364 hb_font_t **hb_fonts;
|
|
365
|
|
366 if (!font->shaper_priv)
|
|
367 font->shaper_priv = calloc(sizeof(ASS_ShaperFontData), 1);
|
|
368
|
|
369
|
|
370 hb_fonts = font->shaper_priv->fonts;
|
|
371 if (!hb_fonts[info->face_index]) {
|
|
372 hb_fonts[info->face_index] =
|
|
373 hb_ft_font_create(font->faces[info->face_index], NULL);
|
|
374
|
|
375 // set up cached metrics access
|
|
376 font->shaper_priv->metrics_data[info->face_index] =
|
|
377 calloc(sizeof(struct ass_shaper_metrics_data), 1);
|
|
378 struct ass_shaper_metrics_data *metrics =
|
|
379 font->shaper_priv->metrics_data[info->face_index];
|
|
380 metrics->metrics_cache = shaper->metrics_cache;
|
|
381 metrics->vertical = info->font->desc.vertical;
|
|
382
|
|
383 hb_font_funcs_t *funcs = hb_font_funcs_create();
|
|
384 font->shaper_priv->font_funcs[info->face_index] = funcs;
|
|
385 hb_font_funcs_set_glyph_func(funcs, get_glyph,
|
|
386 metrics, NULL);
|
|
387 hb_font_funcs_set_glyph_h_advance_func(funcs, cached_h_advance,
|
|
388 metrics, NULL);
|
|
389 hb_font_funcs_set_glyph_v_advance_func(funcs, cached_v_advance,
|
|
390 metrics, NULL);
|
|
391 hb_font_funcs_set_glyph_h_origin_func(funcs, cached_h_origin,
|
|
392 metrics, NULL);
|
|
393 hb_font_funcs_set_glyph_v_origin_func(funcs, cached_v_origin,
|
|
394 metrics, NULL);
|
|
395 hb_font_funcs_set_glyph_h_kerning_func(funcs, get_h_kerning,
|
|
396 metrics, NULL);
|
|
397 hb_font_funcs_set_glyph_v_kerning_func(funcs, get_v_kerning,
|
|
398 metrics, NULL);
|
|
399 hb_font_funcs_set_glyph_extents_func(funcs, cached_extents,
|
|
400 metrics, NULL);
|
|
401 hb_font_funcs_set_glyph_contour_point_func(funcs, get_contour_point,
|
|
402 metrics, NULL);
|
|
403 hb_font_set_funcs(hb_fonts[info->face_index], funcs,
|
|
404 font->faces[info->face_index], NULL);
|
|
405 }
|
|
406
|
35262
|
407 // XXX: this is a rather crude hack
|
|
408 const double ft_size = 256.0;
|
|
409 ass_face_set_size(font->faces[info->face_index], ft_size);
|
34300
|
410 update_hb_size(hb_fonts[info->face_index], font->faces[info->face_index]);
|
|
411
|
|
412 // update hash key for cached metrics
|
|
413 struct ass_shaper_metrics_data *metrics =
|
|
414 font->shaper_priv->metrics_data[info->face_index];
|
|
415 metrics->hash_key.font = info->font;
|
|
416 metrics->hash_key.face_index = info->face_index;
|
|
417 metrics->hash_key.size = info->font_size;
|
|
418 metrics->hash_key.scale_x = double_to_d6(info->scale_x);
|
|
419 metrics->hash_key.scale_y = double_to_d6(info->scale_y);
|
|
420
|
|
421 return hb_fonts[info->face_index];
|
|
422 }
|
|
423
|
|
424 /**
|
36363
|
425 * \brief Map script to default language.
|
|
426 *
|
|
427 * This maps a script to a language, if a script has a representative
|
|
428 * language it is typically used with. Otherwise, the invalid language
|
|
429 * is returned.
|
|
430 *
|
|
431 * The mapping is similar to Pango's pango-language.c.
|
|
432 *
|
|
433 * \param script script tag
|
|
434 * \return language tag
|
|
435 */
|
|
436 static hb_language_t script_to_language(hb_script_t script)
|
|
437 {
|
|
438 switch (script) {
|
|
439 // Unicode 1.1
|
|
440 case HB_SCRIPT_ARABIC: return hb_language_from_string("ar", -1); break;
|
|
441 case HB_SCRIPT_ARMENIAN: return hb_language_from_string("hy", -1); break;
|
|
442 case HB_SCRIPT_BENGALI: return hb_language_from_string("bn", -1); break;
|
|
443 case HB_SCRIPT_CANADIAN_ABORIGINAL: return hb_language_from_string("iu", -1); break;
|
|
444 case HB_SCRIPT_CHEROKEE: return hb_language_from_string("chr", -1); break;
|
|
445 case HB_SCRIPT_COPTIC: return hb_language_from_string("cop", -1); break;
|
|
446 case HB_SCRIPT_CYRILLIC: return hb_language_from_string("ru", -1); break;
|
|
447 case HB_SCRIPT_DEVANAGARI: return hb_language_from_string("hi", -1); break;
|
|
448 case HB_SCRIPT_GEORGIAN: return hb_language_from_string("ka", -1); break;
|
|
449 case HB_SCRIPT_GREEK: return hb_language_from_string("el", -1); break;
|
|
450 case HB_SCRIPT_GUJARATI: return hb_language_from_string("gu", -1); break;
|
|
451 case HB_SCRIPT_GURMUKHI: return hb_language_from_string("pa", -1); break;
|
|
452 case HB_SCRIPT_HANGUL: return hb_language_from_string("ko", -1); break;
|
|
453 case HB_SCRIPT_HEBREW: return hb_language_from_string("he", -1); break;
|
|
454 case HB_SCRIPT_HIRAGANA: return hb_language_from_string("ja", -1); break;
|
|
455 case HB_SCRIPT_KANNADA: return hb_language_from_string("kn", -1); break;
|
|
456 case HB_SCRIPT_KATAKANA: return hb_language_from_string("ja", -1); break;
|
|
457 case HB_SCRIPT_LAO: return hb_language_from_string("lo", -1); break;
|
|
458 case HB_SCRIPT_LATIN: return hb_language_from_string("en", -1); break;
|
|
459 case HB_SCRIPT_MALAYALAM: return hb_language_from_string("ml", -1); break;
|
|
460 case HB_SCRIPT_MONGOLIAN: return hb_language_from_string("mn", -1); break;
|
|
461 case HB_SCRIPT_ORIYA: return hb_language_from_string("or", -1); break;
|
|
462 case HB_SCRIPT_SYRIAC: return hb_language_from_string("syr", -1); break;
|
|
463 case HB_SCRIPT_TAMIL: return hb_language_from_string("ta", -1); break;
|
|
464 case HB_SCRIPT_TELUGU: return hb_language_from_string("te", -1); break;
|
|
465 case HB_SCRIPT_THAI: return hb_language_from_string("th", -1); break;
|
|
466
|
|
467 // Unicode 2.0
|
|
468 case HB_SCRIPT_TIBETAN: return hb_language_from_string("bo", -1); break;
|
|
469
|
|
470 // Unicode 3.0
|
|
471 case HB_SCRIPT_ETHIOPIC: return hb_language_from_string("am", -1); break;
|
|
472 case HB_SCRIPT_KHMER: return hb_language_from_string("km", -1); break;
|
|
473 case HB_SCRIPT_MYANMAR: return hb_language_from_string("my", -1); break;
|
|
474 case HB_SCRIPT_SINHALA: return hb_language_from_string("si", -1); break;
|
|
475 case HB_SCRIPT_THAANA: return hb_language_from_string("dv", -1); break;
|
|
476
|
|
477 // Unicode 3.2
|
|
478 case HB_SCRIPT_BUHID: return hb_language_from_string("bku", -1); break;
|
|
479 case HB_SCRIPT_HANUNOO: return hb_language_from_string("hnn", -1); break;
|
|
480 case HB_SCRIPT_TAGALOG: return hb_language_from_string("tl", -1); break;
|
|
481 case HB_SCRIPT_TAGBANWA: return hb_language_from_string("tbw", -1); break;
|
|
482
|
|
483 // Unicode 4.0
|
|
484 case HB_SCRIPT_UGARITIC: return hb_language_from_string("uga", -1); break;
|
|
485
|
|
486 // Unicode 4.1
|
|
487 case HB_SCRIPT_BUGINESE: return hb_language_from_string("bug", -1); break;
|
|
488 case HB_SCRIPT_OLD_PERSIAN: return hb_language_from_string("peo", -1); break;
|
|
489 case HB_SCRIPT_SYLOTI_NAGRI: return hb_language_from_string("syl", -1); break;
|
|
490
|
|
491 // Unicode 5.0
|
|
492 case HB_SCRIPT_NKO: return hb_language_from_string("nko", -1); break;
|
|
493
|
|
494 // no representative language exists
|
|
495 default: return HB_LANGUAGE_INVALID; break;
|
|
496 }
|
|
497 }
|
|
498
|
|
499 /**
|
|
500 * \brief Determine language to be used for shaping a run.
|
|
501 *
|
|
502 * \param shaper shaper instance
|
|
503 * \param script script tag associated with run
|
|
504 * \return language tag
|
|
505 */
|
|
506 static hb_language_t
|
|
507 hb_shaper_get_run_language(ASS_Shaper *shaper, hb_script_t script)
|
|
508 {
|
|
509 hb_language_t lang;
|
|
510
|
|
511 // override set, use it
|
|
512 if (shaper->language != HB_LANGUAGE_INVALID)
|
|
513 return shaper->language;
|
|
514
|
|
515 // get default language for given script
|
|
516 lang = script_to_language(script);
|
|
517
|
|
518 // no dice, use system default
|
|
519 if (lang == HB_LANGUAGE_INVALID)
|
|
520 lang = hb_language_get_default();
|
|
521
|
|
522 return lang;
|
|
523 }
|
|
524
|
|
525 /**
|
34300
|
526 * \brief Shape event text with HarfBuzz. Full OpenType shaping.
|
|
527 * \param glyphs glyph clusters
|
|
528 * \param len number of clusters
|
|
529 */
|
|
530 static void shape_harfbuzz(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
|
|
531 {
|
|
532 int i, j;
|
|
533 int run = 0;
|
|
534 struct {
|
|
535 int offset;
|
|
536 int end;
|
|
537 hb_buffer_t *buf;
|
|
538 hb_font_t *font;
|
|
539 } runs[MAX_RUNS];
|
35262
|
540 const double ft_size = 256.0;
|
34300
|
541
|
|
542 for (i = 0; i < len && run < MAX_RUNS; i++, run++) {
|
|
543 // get length and level of the current run
|
|
544 int k = i;
|
|
545 int level = glyphs[i].shape_run_id;
|
|
546 int direction = shaper->emblevels[k] % 2;
|
36363
|
547 hb_script_t script = glyphs[i].script;
|
34300
|
548 while (i < (len - 1) && level == glyphs[i+1].shape_run_id)
|
|
549 i++;
|
|
550 runs[run].offset = k;
|
|
551 runs[run].end = i;
|
|
552 runs[run].buf = hb_buffer_create();
|
|
553 runs[run].font = get_hb_font(shaper, glyphs + k);
|
|
554 set_run_features(shaper, glyphs + k);
|
|
555 hb_buffer_pre_allocate(runs[run].buf, i - k + 1);
|
|
556 hb_buffer_set_direction(runs[run].buf, direction ? HB_DIRECTION_RTL :
|
|
557 HB_DIRECTION_LTR);
|
36363
|
558 hb_buffer_set_language(runs[run].buf,
|
|
559 hb_shaper_get_run_language(shaper, script));
|
|
560 hb_buffer_set_script(runs[run].buf, script);
|
34300
|
561 hb_buffer_add_utf32(runs[run].buf, shaper->event_text + k, i - k + 1,
|
|
562 0, i - k + 1);
|
|
563 hb_shape(runs[run].font, runs[run].buf, shaper->features,
|
|
564 shaper->n_features);
|
|
565 }
|
|
566
|
|
567 // Initialize: skip all glyphs, this is undone later as needed
|
|
568 for (i = 0; i < len; i++)
|
|
569 glyphs[i].skip = 1;
|
|
570
|
|
571 // Update glyph indexes, positions and advances from the shaped runs
|
|
572 for (i = 0; i < run; i++) {
|
|
573 int num_glyphs = hb_buffer_get_length(runs[i].buf);
|
|
574 hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(runs[i].buf, NULL);
|
|
575 hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(runs[i].buf, NULL);
|
|
576
|
|
577 for (j = 0; j < num_glyphs; j++) {
|
|
578 int idx = glyph_info[j].cluster + runs[i].offset;
|
|
579 GlyphInfo *info = glyphs + idx;
|
|
580 GlyphInfo *root = info;
|
|
581
|
|
582 // if we have more than one glyph per cluster, allocate a new one
|
|
583 // and attach to the root glyph
|
|
584 if (info->skip == 0) {
|
|
585 while (info->next)
|
|
586 info = info->next;
|
|
587 info->next = malloc(sizeof(GlyphInfo));
|
|
588 memcpy(info->next, info, sizeof(GlyphInfo));
|
|
589 info = info->next;
|
|
590 info->next = NULL;
|
|
591 }
|
|
592
|
|
593 // set position and advance
|
|
594 info->skip = 0;
|
|
595 info->glyph_index = glyph_info[j].codepoint;
|
35262
|
596 info->offset.x = pos[j].x_offset * info->scale_x * (info->font_size / ft_size);
|
|
597 info->offset.y = -pos[j].y_offset * info->scale_y * (info->font_size / ft_size);
|
|
598 info->advance.x = pos[j].x_advance * info->scale_x * (info->font_size / ft_size);
|
|
599 info->advance.y = -pos[j].y_advance * info->scale_y * (info->font_size / ft_size);
|
34300
|
600
|
|
601 // accumulate advance in the root glyph
|
|
602 root->cluster_advance.x += info->advance.x;
|
|
603 root->cluster_advance.y += info->advance.y;
|
|
604 }
|
|
605 }
|
|
606
|
|
607 // Free runs and associated data
|
|
608 for (i = 0; i < run; i++) {
|
|
609 hb_buffer_destroy(runs[i].buf);
|
|
610 }
|
|
611
|
|
612 }
|
36363
|
613
|
|
614 /**
|
|
615 * \brief Determine script property of all characters. Characters of script
|
|
616 * common and inherited get their script from their context.
|
|
617 *
|
|
618 */
|
|
619 void ass_shaper_determine_script(ASS_Shaper *shaper, GlyphInfo *glyphs,
|
|
620 size_t len)
|
|
621 {
|
|
622 int i;
|
|
623 int backwards_scan = 0;
|
|
624 hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default();
|
|
625 hb_script_t last_script = HB_SCRIPT_UNKNOWN;
|
|
626
|
|
627 // determine script (forward scan)
|
|
628 for (i = 0; i < len; i++) {
|
|
629 GlyphInfo *info = glyphs + i;
|
|
630 info->script = hb_unicode_script(ufuncs, info->symbol);
|
|
631
|
|
632 // common/inherit codepoints inherit script from context
|
|
633 if (info->script == HB_SCRIPT_COMMON ||
|
|
634 info->script == HB_SCRIPT_INHERITED) {
|
|
635 // unknown is not a valid context
|
|
636 if (last_script != HB_SCRIPT_UNKNOWN)
|
|
637 info->script = last_script;
|
|
638 else
|
|
639 // do a backwards scan to check if next codepoint
|
|
640 // contains a valid script for context
|
|
641 backwards_scan = 1;
|
|
642 } else {
|
|
643 last_script = info->script;
|
|
644 }
|
|
645 }
|
|
646
|
|
647 // determine script (backwards scan, if needed)
|
|
648 last_script = HB_SCRIPT_UNKNOWN;
|
|
649 for (i = len - 1; i >= 0 && backwards_scan; i--) {
|
|
650 GlyphInfo *info = glyphs + i;
|
|
651
|
|
652 // common/inherit codepoints inherit script from context
|
|
653 if (info->script == HB_SCRIPT_COMMON ||
|
|
654 info->script == HB_SCRIPT_INHERITED) {
|
|
655 // unknown script is not a valid context
|
|
656 if (last_script != HB_SCRIPT_UNKNOWN)
|
|
657 info->script = last_script;
|
|
658 } else {
|
|
659 last_script = info->script;
|
|
660 }
|
|
661 }
|
|
662 }
|
34300
|
663 #endif
|
|
664
|
34342
|
665 #ifdef CONFIG_FRIBIDI
|
34300
|
666 /**
|
|
667 * \brief Shape event text with FriBidi. Does mirroring and simple
|
|
668 * Arabic shaping.
|
|
669 * \param len number of clusters
|
|
670 */
|
|
671 static void shape_fribidi(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
|
|
672 {
|
|
673 int i;
|
|
674 FriBidiJoiningType *joins = calloc(sizeof(*joins), len);
|
|
675
|
|
676 // shape on codepoint level
|
|
677 fribidi_get_joining_types(shaper->event_text, len, joins);
|
|
678 fribidi_join_arabic(shaper->ctypes, len, shaper->emblevels, joins);
|
|
679 fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
|
|
680 shaper->emblevels, len, joins, shaper->event_text);
|
|
681
|
|
682 // update indexes
|
|
683 for (i = 0; i < len; i++) {
|
|
684 GlyphInfo *info = glyphs + i;
|
|
685 FT_Face face = info->font->faces[info->face_index];
|
|
686 info->symbol = shaper->event_text[i];
|
|
687 info->glyph_index = FT_Get_Char_Index(face, shaper->event_text[i]);
|
|
688 }
|
|
689
|
|
690 free(joins);
|
|
691 }
|
34342
|
692 #endif
|
34300
|
693
|
|
694 /**
|
|
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.
|
|
698 */
|
|
699 void ass_shaper_set_kerning(ASS_Shaper *shaper, int kern)
|
|
700 {
|
|
701 #ifdef CONFIG_HARFBUZZ
|
|
702 shaper->features[KERN].value = !!kern;
|
|
703 #endif
|
|
704 }
|
|
705
|
|
706 /**
|
|
707 * \brief Find shape runs according to the event's selected fonts
|
|
708 */
|
|
709 void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv,
|
|
710 GlyphInfo *glyphs, size_t len)
|
|
711 {
|
|
712 int i;
|
|
713 int shape_run = 0;
|
|
714
|
36363
|
715 #ifdef CONFIG_HARFBUZZ
|
|
716 ass_shaper_determine_script(shaper, glyphs, len);
|
|
717 #endif
|
|
718
|
|
719 // find appropriate fonts for the shape runs
|
34300
|
720 for (i = 0; i < len; i++) {
|
|
721 GlyphInfo *last = glyphs + i - 1;
|
|
722 GlyphInfo *info = glyphs + i;
|
|
723 // skip drawings
|
|
724 if (info->symbol == 0xfffc)
|
|
725 continue;
|
|
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 share the same font face and size
|
|
730 if (i > 0 && (last->font != info->font ||
|
|
731 last->font_size != info->font_size ||
|
36363
|
732 last->face_index != info->face_index ||
|
|
733 last->script != info->script))
|
34300
|
734 shape_run++;
|
|
735 info->shape_run_id = shape_run;
|
|
736 }
|
|
737 }
|
|
738
|
|
739 /**
|
|
740 * \brief Set base direction (paragraph direction) of the text.
|
|
741 * \param dir base direction
|
|
742 */
|
|
743 void ass_shaper_set_base_direction(ASS_Shaper *shaper, FriBidiParType dir)
|
|
744 {
|
|
745 shaper->base_direction = dir;
|
|
746 }
|
|
747
|
|
748 /**
|
|
749 * \brief Set language hint. Some languages have specific character variants,
|
|
750 * like Serbian Cyrillic.
|
|
751 * \param lang ISO 639-1 two-letter language code
|
|
752 */
|
|
753 void ass_shaper_set_language(ASS_Shaper *shaper, const char *code)
|
|
754 {
|
|
755 #ifdef CONFIG_HARFBUZZ
|
36363
|
756 hb_language_t lang;
|
|
757
|
|
758 if (code)
|
|
759 lang = hb_language_from_string(code, -1);
|
|
760 else
|
|
761 lang = HB_LANGUAGE_INVALID;
|
|
762
|
|
763 shaper->language = lang;
|
34300
|
764 #endif
|
|
765 }
|
|
766
|
|
767 /**
|
|
768 * Set shaping level. Essentially switches between FriBidi and HarfBuzz.
|
|
769 */
|
|
770 void ass_shaper_set_level(ASS_Shaper *shaper, ASS_ShapingLevel level)
|
|
771 {
|
|
772 shaper->shaping_level = level;
|
|
773 }
|
|
774
|
|
775 /**
|
35262
|
776 * \brief Remove all zero-width invisible characters from the text.
|
|
777 * \param text_info text
|
|
778 */
|
|
779 static void ass_shaper_skip_characters(TextInfo *text_info)
|
|
780 {
|
|
781 int i;
|
|
782 GlyphInfo *glyphs = text_info->glyphs;
|
|
783
|
|
784 for (i = 0; i < text_info->length; i++) {
|
|
785 // Skip direction override control characters
|
|
786 if ((glyphs[i].symbol <= 0x202e && glyphs[i].symbol >= 0x202a)
|
|
787 || (glyphs[i].symbol <= 0x200f && glyphs[i].symbol >= 0x200b)
|
|
788 || (glyphs[i].symbol <= 0x2063 && glyphs[i].symbol >= 0x2060)
|
|
789 || glyphs[i].symbol == 0xfeff
|
|
790 || glyphs[i].symbol == 0x00ad
|
|
791 || glyphs[i].symbol == 0x034f) {
|
|
792 glyphs[i].symbol = 0;
|
|
793 glyphs[i].skip++;
|
|
794 }
|
|
795 }
|
|
796 }
|
|
797
|
|
798 /**
|
34300
|
799 * \brief Shape an event's text. Calculates directional runs and shapes them.
|
|
800 * \param text_info event's text
|
|
801 */
|
|
802 void ass_shaper_shape(ASS_Shaper *shaper, TextInfo *text_info)
|
|
803 {
|
34342
|
804 #ifndef CONFIG_FRIBIDI
|
|
805 check_allocations(shaper, text_info->length);
|
|
806 #else
|
34300
|
807 int i, last_break;
|
|
808 FriBidiParType dir;
|
|
809 GlyphInfo *glyphs = text_info->glyphs;
|
|
810
|
|
811 check_allocations(shaper, text_info->length);
|
|
812
|
|
813 // Get bidi character types and embedding levels
|
|
814 last_break = 0;
|
|
815 for (i = 0; i < text_info->length; i++) {
|
|
816 shaper->event_text[i] = glyphs[i].symbol;
|
|
817 // embedding levels should be calculated paragraph by paragraph
|
|
818 if (glyphs[i].symbol == '\n' || i == text_info->length - 1) {
|
|
819 dir = shaper->base_direction;
|
|
820 fribidi_get_bidi_types(shaper->event_text + last_break,
|
|
821 i - last_break + 1, shaper->ctypes + last_break);
|
|
822 fribidi_get_par_embedding_levels(shaper->ctypes + last_break,
|
|
823 i - last_break + 1, &dir, shaper->emblevels + last_break);
|
|
824 last_break = i + 1;
|
|
825 }
|
|
826 }
|
|
827
|
|
828 // add embedding levels to shape runs for final runs
|
|
829 for (i = 0; i < text_info->length; i++) {
|
|
830 glyphs[i].shape_run_id += shaper->emblevels[i];
|
|
831 }
|
|
832
|
|
833 #ifdef CONFIG_HARFBUZZ
|
|
834 switch (shaper->shaping_level) {
|
|
835 case ASS_SHAPING_SIMPLE:
|
35274
|
836 shape_fribidi(shaper, glyphs, text_info->length);
|
35262
|
837 ass_shaper_skip_characters(text_info);
|
34300
|
838 break;
|
|
839 case ASS_SHAPING_COMPLEX:
|
|
840 shape_harfbuzz(shaper, glyphs, text_info->length);
|
|
841 break;
|
|
842 }
|
|
843 #else
|
35274
|
844 shape_fribidi(shaper, glyphs, text_info->length);
|
35262
|
845 ass_shaper_skip_characters(text_info);
|
34300
|
846 #endif
|
34342
|
847 #endif
|
34300
|
848 }
|
|
849
|
|
850 /**
|
|
851 * \brief Create a new shaper instance and preallocate data structures
|
|
852 * \param prealloc preallocation size
|
|
853 */
|
|
854 ASS_Shaper *ass_shaper_new(size_t prealloc)
|
|
855 {
|
|
856 ASS_Shaper *shaper = calloc(sizeof(*shaper), 1);
|
|
857
|
34342
|
858 #ifdef CONFIG_FRIBIDI
|
34300
|
859 shaper->base_direction = FRIBIDI_PAR_ON;
|
34342
|
860 #endif
|
34300
|
861 check_allocations(shaper, prealloc);
|
|
862
|
|
863 #ifdef CONFIG_HARFBUZZ
|
|
864 init_features(shaper);
|
|
865 shaper->metrics_cache = ass_glyph_metrics_cache_create();
|
|
866 #endif
|
|
867
|
|
868 return shaper;
|
|
869 }
|
|
870
|
|
871
|
|
872 /**
|
|
873 * \brief clean up additional data temporarily needed for shaping and
|
|
874 * (e.g. additional glyphs allocated)
|
|
875 */
|
|
876 void ass_shaper_cleanup(ASS_Shaper *shaper, TextInfo *text_info)
|
|
877 {
|
|
878 int i;
|
|
879
|
|
880 for (i = 0; i < text_info->length; i++) {
|
|
881 GlyphInfo *info = text_info->glyphs + i;
|
|
882 info = info->next;
|
|
883 while (info) {
|
|
884 GlyphInfo *next = info->next;
|
|
885 free(info);
|
|
886 info = next;
|
|
887 }
|
|
888 }
|
|
889 }
|
|
890
|
|
891 /**
|
|
892 * \brief Calculate reorder map to render glyphs in visual order
|
|
893 */
|
|
894 FriBidiStrIndex *ass_shaper_reorder(ASS_Shaper *shaper, TextInfo *text_info)
|
|
895 {
|
|
896 int i;
|
|
897
|
|
898 // Initialize reorder map
|
|
899 for (i = 0; i < text_info->length; i++)
|
|
900 shaper->cmap[i] = i;
|
|
901
|
34342
|
902 #ifdef CONFIG_FRIBIDI
|
34300
|
903 // Create reorder map line-by-line
|
|
904 for (i = 0; i < text_info->n_lines; i++) {
|
|
905 LineInfo *line = text_info->lines + i;
|
|
906 int level;
|
|
907 FriBidiParType dir = FRIBIDI_PAR_ON;
|
|
908
|
|
909 level = fribidi_reorder_line(0,
|
|
910 shaper->ctypes + line->offset, line->len, 0, dir,
|
|
911 shaper->emblevels + line->offset, NULL,
|
|
912 shaper->cmap + line->offset);
|
|
913 }
|
34342
|
914 #endif
|
34300
|
915
|
|
916 return shaper->cmap;
|
|
917 }
|
|
918
|
|
919 /**
|
35262
|
920 * \brief Resolve a Windows font charset number to a suitable
|
34300
|
921 * base direction. 177 and 178 are Hebrew and Arabic respectively, and
|
35262
|
922 * they map to RTL. Everything else maps to LTR for compatibility
|
|
923 * reasons. The special value -1, which is not a legal Windows font charset
|
|
924 * number, can be used for autodetection.
|
34300
|
925 * \param enc Windows font encoding
|
|
926 */
|
|
927 FriBidiParType resolve_base_direction(int enc)
|
|
928 {
|
34342
|
929 #ifdef CONFIG_FRIBIDI
|
34300
|
930 switch (enc) {
|
35262
|
931 case -1:
|
34300
|
932 return FRIBIDI_PAR_ON;
|
|
933 case 177:
|
|
934 case 178:
|
|
935 return FRIBIDI_PAR_RTL;
|
|
936 default:
|
|
937 return FRIBIDI_PAR_LTR;
|
|
938 }
|
34342
|
939 #else
|
|
940 return 0;
|
|
941 #endif
|
34300
|
942 }
|