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"
34 #define NUM_FEATURES 3
39 FriBidiChar
*event_text
;
40 FriBidiCharType
*ctypes
;
41 FriBidiLevel
*emblevels
;
42 FriBidiStrIndex
*cmap
;
43 FriBidiParType base_direction
;
46 hb_feature_t
*features
;
49 struct ass_shaper_font_data
{
50 hb_font_t
*fonts
[ASS_FONT_MAX_FACES
];
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());
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
);
77 * \brief set up the HarfBuzz OpenType feature list with some
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
;
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
);
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
);
117 free(shaper
->features
);
121 void ass_shaper_font_data_free(ASS_ShaperFontData
*priv
)
124 for (i
= 0; i
< ASS_FONT_MAX_FACES
; i
++)
126 hb_font_destroy(priv
->fonts
[i
]);
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;
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
)
183 for (i
= 0; i
< len
&& run
< MAX_RUNS
; i
++, run
++) {
184 // get length and level of the current run
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
)
190 //printf("run %d from %d to %d with level %d\n", run, k, i, level);
191 runs
[run
].offset
= k
;
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
:
198 hb_buffer_add_utf32(runs
[run
].buf
, shaper
->event_text
+ k
, i
- k
+ 1,
200 hb_shape(runs
[run
].font
, runs
[run
].buf
, shaper
->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
++)
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,
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
;
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
);
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");
235 info
->next
= malloc(sizeof(GlyphInfo
));
236 memcpy(info
->next
, info
, sizeof(GlyphInfo
));
241 // set position and advance
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
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
);
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
)
298 for (i
= 0; i
< len
; i
++) {
299 GlyphInfo
*last
= glyphs
+ i
- 1;
300 GlyphInfo
*info
= glyphs
+ i
;
302 if (info
->symbol
== 0xfffc)
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)
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
))
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
)
341 GlyphInfo
*glyphs
= text_info
->glyphs
;
343 check_allocations(shaper
, text_info
->length
);
345 // Get bidi character types and embedding levels
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
);
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
];
368 for (i
= 0; i
< text_info
->length
; i
++) {
369 printf("%d ", glyphs
[i
].shape_run_id
);
374 //shape_fribidi(shaper, text_info->length);
375 shape_harfbuzz(shaper
, glyphs
, text_info
->length
);
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;
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
)
398 for (i
= 0; i
< text_info
->length
; i
++) {
399 GlyphInfo
*info
= text_info
->glyphs
+ i
;
402 GlyphInfo
*next
= info
->next
;
410 * \brief Calculate reorder map to render glyphs in visual order
412 FriBidiStrIndex
*ass_shaper_reorder(ASS_Shaper
*shaper
, TextInfo
*text_info
)
416 // Initialize reorder map
417 for (i
= 0; i
< text_info
->length
; 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
;
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);
437 for (i
= 0; i
< text_info
->length
; i
++) {
438 printf("%d ", cmap
[i
]);
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
)
457 return FRIBIDI_PAR_ON
;
460 return FRIBIDI_PAR_RTL
;
462 return FRIBIDI_PAR_LTR
;