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"
32 FriBidiChar *event_text;
33 FriBidiCharType *ctypes;
34 FriBidiLevel *emblevels;
35 FriBidiStrIndex *cmap;
39 * \brief Print version information
41 void ass_shaper_info(ASS_Library *lib)
43 ass_msg(lib, MSGL_V, "Complex text layout enabled, using FriBidi "
44 FRIBIDI_VERSION " HarfBuzz-ng %s", hb_version_string());
48 * \brief grow arrays, if needed
49 * \param new_size requested size
51 static void check_allocations(ASS_Shaper *shaper, size_t new_size)
53 if (new_size > shaper->n_glyphs) {
54 shaper->event_text = realloc(shaper->event_text, sizeof(FriBidiChar) * new_size);
55 shaper->ctypes = realloc(shaper->ctypes, sizeof(FriBidiCharType) * new_size);
56 shaper->emblevels = realloc(shaper->emblevels, sizeof(FriBidiLevel) * new_size);
57 shaper->cmap = realloc(shaper->cmap, sizeof(FriBidiStrIndex) * new_size);
62 * \brief Create a new shaper instance and preallocate data structures
63 * \param prealloc preallocation size
65 ASS_Shaper *ass_shaper_new(size_t prealloc)
67 ASS_Shaper *shaper = calloc(sizeof(*shaper), 1);
69 check_allocations(shaper, prealloc);
74 * \brief Free shaper and related data
76 void ass_shaper_free(ASS_Shaper *shaper)
78 free(shaper->event_text);
80 free(shaper->emblevels);
86 * \brief Shape event text with HarfBuzz. Full OpenType shaping.
87 * \param glyphs glyph clusters
88 * \param len number of clusters
90 static void shape_harfbuzz(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
102 for (i = 0; i < len && run < MAX_RUNS; i++, run++) {
103 // get length and level of the current run
105 int level = glyphs[i].shape_run_id;
106 int direction = shaper->emblevels[k] % 2;
107 while (i < (len - 1) && level == glyphs[i+1].shape_run_id)
109 //printf("run %d from %d to %d with level %d\n", run, k, i, level);
110 FT_Face run_font = glyphs[k].font->faces[glyphs[k].face_index];
111 runs[run].offset = k;
113 runs[run].buf = hb_buffer_create(i - k + 1);
114 runs[run].font = hb_ft_font_create(run_font, NULL);
115 hb_buffer_set_direction(runs[run].buf, direction ? HB_DIRECTION_RTL :
117 hb_buffer_add_utf32(runs[run].buf, shaper->event_text + k, i - k + 1,
119 hb_shape(runs[run].font, runs[run].buf, NULL, 0);
121 //printf("shaped %d runs\n", run);
123 // Initialize: skip all glyphs, this is undone later as needed
124 for (i = 0; i < len; i++)
127 // Update glyph indexes, positions and advances from the shaped runs
128 for (i = 0; i < run; i++) {
129 int num_glyphs = hb_buffer_get_length(runs[i].buf);
130 hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(runs[i].buf, NULL);
131 hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(runs[i].buf, NULL);
132 //printf("run text len %d num_glyphs %d\n", runs[i].end - runs[i].offset + 1,
135 for (j = 0; j < num_glyphs; j++) {
136 int idx = glyph_info[j].cluster + runs[i].offset;
137 GlyphInfo *info = glyphs + idx;
138 GlyphInfo *root = info;
140 printf("run %d cluster %d codepoint %d -> '%c'\n", i, idx,
141 glyph_info[j].codepoint, event_text[idx]);
142 printf("position %d %d advance %d %d\n",
143 pos[j].x_offset, pos[j].y_offset,
144 pos[j].x_advance, pos[j].y_advance);
147 // if we have more than one glyph per cluster, allocate a new one
148 // and attach to the root glyph
149 if (info->skip == 0) {
150 //printf("duplicate cluster entry, adding glyph\n");
153 info->next = malloc(sizeof(GlyphInfo));
154 memcpy(info->next, info, sizeof(GlyphInfo));
159 // set position and advance
161 info->glyph_index = glyph_info[j].codepoint;
162 info->offset.x = pos[j].x_offset * info->scale_x;
163 info->offset.y = -pos[j].y_offset * info->scale_y;
164 info->advance.x = pos[j].x_advance * info->scale_x;
165 info->advance.y = -pos[j].y_advance * info->scale_y;
167 // accumulate advance in the root glyph
168 root->cluster_advance.x += info->advance.x;
169 root->cluster_advance.y += info->advance.y;
173 // Free runs and associated data
174 for (i = 0; i < run; i++) {
175 hb_buffer_destroy(runs[i].buf);
176 hb_font_destroy(runs[i].font);
182 * \brief Shape event text with FriBidi. Does mirroring and simple
184 * \param len number of clusters
186 static void shape_fribidi(ASS_Shaper *shaper, size_t len)
188 FriBidiJoiningType *joins = calloc(sizeof(*joins), len);
190 fribidi_get_joining_types(shaper->event_text, len, joins);
191 fribidi_join_arabic(shaper->ctypes, len, shaper->emblevels, joins);
192 fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
193 shaper->emblevels, len, joins, shaper->event_text);
199 * \brief Find shape runs according to the event's selected fonts
201 void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv,
202 GlyphInfo *glyphs, size_t len)
207 for (i = 0; i < len; i++) {
208 GlyphInfo *last = glyphs + i - 1;
209 GlyphInfo *info = glyphs + i;
211 if (info->symbol == 0xfffc)
213 // initialize face_index to continue with the same face, if possible
214 // XXX: can be problematic in some cases, for example if a font misses
215 // a single glyph, like space (U+0020)
217 info->face_index = last->face_index;
218 // set size and get glyph index
219 double size_scaled = ensure_font_size(render_priv,
220 info->font_size * render_priv->font_scale);
221 ass_font_set_size(info->font, size_scaled);
222 ass_font_get_index(render_priv->fontconfig_priv, info->font,
223 info->symbol, &info->face_index, &info->glyph_index);
224 // shape runs share the same font face and size
225 if (i > 0 && (last->font != info->font ||
226 last->font_size != info->font_size ||
227 last->face_index != info->face_index))
229 info->shape_run_id = shape_run;
230 //printf("glyph '%c' shape run id %d face %d\n", info->symbol, info->shape_run_id,
231 // info->face_index);
237 * \brief Shape an event's text. Calculates directional runs and shapes them.
238 * \param text_info event's text
240 void ass_shaper_shape(ASS_Shaper *shaper, TextInfo *text_info)
244 GlyphInfo *glyphs = text_info->glyphs;
246 check_allocations(shaper, text_info->length);
248 // Get bidi character types and embedding levels
250 for (i = 0; i < text_info->length; i++) {
251 shaper->event_text[i] = glyphs[i].symbol;
252 // embedding levels should be calculated paragraph by paragraph
253 if (glyphs[i].symbol == '\n' || i == text_info->length - 1) {
254 //printf("paragraph from %d to %d\n", last_break, i);
255 dir = FRIBIDI_PAR_ON;
256 fribidi_get_bidi_types(shaper->event_text + last_break,
257 i - last_break + 1, shaper->ctypes + last_break);
258 fribidi_get_par_embedding_levels(shaper->ctypes + last_break,
259 i - last_break + 1, &dir, shaper->emblevels + last_break);
264 // add embedding levels to shape runs for final runs
265 for (i = 0; i < text_info->length; i++) {
266 glyphs[i].shape_run_id += shaper->emblevels[i];
271 for (i = 0; i < text_info->length; i++) {
272 printf("%d ", glyphs[i].shape_run_id);
277 //shape_fribidi(shaper, text_info->length);
278 shape_harfbuzz(shaper, glyphs, text_info->length);
281 for (i = 0; i < text_info->length; i++) {
282 glyphs[i].symbol = shaper->event_text[i];
283 // Skip direction override control characters
284 // NOTE: Behdad said HarfBuzz is supposed to remove these, but this hasn't
285 // been implemented yet
286 if (glyphs[i].symbol <= 0x202F && glyphs[i].symbol >= 0x202a) {
287 glyphs[i].symbol = 0;
294 * \brief clean up additional data temporarily needed for shaping and
295 * (e.g. additional glyphs allocated)
297 void ass_shaper_cleanup(ASS_Shaper *shaper, TextInfo *text_info)
301 for (i = 0; i < text_info->length; i++) {
302 GlyphInfo *info = text_info->glyphs + i;
305 GlyphInfo *next = info->next;
313 * \brief Calculate reorder map to render glyphs in visual order
315 FriBidiStrIndex *ass_shaper_reorder(ASS_Shaper *shaper, TextInfo *text_info)
320 // Initialize reorder map
321 for (i = 0; i < text_info->length; i++)
324 // Create reorder map line-by-line
325 for (i = 0; i < text_info->n_lines; i++) {
326 LineInfo *line = text_info->lines + i;
328 dir = FRIBIDI_PAR_ON;
330 // FIXME: we should actually specify
331 // the correct paragraph base direction
332 level = fribidi_reorder_line(FRIBIDI_FLAGS_DEFAULT,
333 shaper->ctypes + line->offset, line->len, 0, dir,
334 shaper->emblevels + line->offset, NULL,
335 shaper->cmap + line->offset);
336 //printf("reorder line %d to level %d\n", i, level);
341 for (i = 0; i < text_info->length; i++) {
342 printf("%d ", cmap[i]);