]> granicus.if.org Git - libass/blob - libass/ass_shaper.c
Introduce ASS_Shaper object
[libass] / libass / ass_shaper.c
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 <fribidi/fribidi.h>
20 #include <hb-ft.h>
21
22 #include "ass_shaper.h"
23 #include "ass_render.h"
24 #include "ass_font.h"
25 #include "ass_parse.h"
26
27 #define MAX_RUNS 50
28
29 struct ass_shaper {
30     // FriBidi log2vis
31     int n_glyphs;
32     FriBidiChar *event_text;
33     FriBidiCharType *ctypes;
34     FriBidiLevel *emblevels;
35     FriBidiStrIndex *cmap;
36 };
37
38 /**
39  * \brief Print version information
40  */
41 void ass_shaper_info(ASS_Library *lib)
42 {
43     ass_msg(lib, MSGL_V, "Complex text layout enabled, using FriBidi "
44             FRIBIDI_VERSION " HarfBuzz-ng %s", hb_version_string());
45 }
46
47 /**
48  * \brief grow arrays, if needed
49  * \param new_size requested size
50  */
51 static void check_allocations(ASS_Shaper *shaper, size_t new_size)
52 {
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);
58     }
59 }
60
61 /**
62  * \brief Create a new shaper instance and preallocate data structures
63  * \param prealloc preallocation size
64  */
65 ASS_Shaper *ass_shaper_new(size_t prealloc)
66 {
67     ASS_Shaper *shaper = calloc(sizeof(*shaper), 1);
68
69     check_allocations(shaper, prealloc);
70     return shaper;
71 }
72
73 /**
74  * \brief Free shaper and related data
75  */
76 void ass_shaper_free(ASS_Shaper *shaper)
77 {
78     free(shaper->event_text);
79     free(shaper->ctypes);
80     free(shaper->emblevels);
81     free(shaper->cmap);
82     free(shaper);
83 }
84
85 /**
86  * \brief Shape event text with HarfBuzz. Full OpenType shaping.
87  * \param glyphs glyph clusters
88  * \param len number of clusters
89  */
90 static void shape_harfbuzz(ASS_Shaper *shaper, GlyphInfo *glyphs, size_t len)
91 {
92     int i, j;
93     int run = 0;
94     struct {
95         int offset;
96         int end;
97         hb_buffer_t *buf;
98         hb_font_t *font;
99     } runs[MAX_RUNS];
100
101
102     for (i = 0; i < len && run < MAX_RUNS; i++, run++) {
103         // get length and level of the current run
104         int k = i;
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)
108             i++;
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;
112         runs[run].end    = i;
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 :
116                 HB_DIRECTION_LTR);
117         hb_buffer_add_utf32(runs[run].buf, shaper->event_text + k, i - k + 1,
118                 0, i - k + 1);
119         hb_shape(runs[run].font, runs[run].buf, NULL, 0);
120     }
121     //printf("shaped %d runs\n", run);
122
123     // Initialize: skip all glyphs, this is undone later as needed
124     for (i = 0; i < len; i++)
125         glyphs[i].skip = 1;
126
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,
133         //        num_glyphs);
134         // Update glyphs
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;
139 #if 0
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);
145 #endif
146
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");
151                 while (info->next)
152                     info = info->next;
153                 info->next = malloc(sizeof(GlyphInfo));
154                 memcpy(info->next, info, sizeof(GlyphInfo));
155                 info = info->next;
156                 info->next = NULL;
157             }
158
159             // set position and advance
160             info->skip = 0;
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;
166
167             // accumulate advance in the root glyph
168             root->cluster_advance.x += info->advance.x;
169             root->cluster_advance.y += info->advance.y;
170         }
171     }
172
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);
177     }
178
179 }
180
181 /**
182  * \brief Shape event text with FriBidi. Does mirroring and simple
183  * Arabic shaping.
184  * \param len number of clusters
185  */
186 static void shape_fribidi(ASS_Shaper *shaper, size_t len)
187 {
188     FriBidiJoiningType *joins = calloc(sizeof(*joins), len);
189
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);
194
195     free(joins);
196 }
197
198 /**
199  * \brief Find shape runs according to the event's selected fonts
200  */
201 void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv,
202                           GlyphInfo *glyphs, size_t len)
203 {
204     int i;
205     int shape_run = 0;
206
207     for (i = 0; i < len; i++) {
208         GlyphInfo *last = glyphs + i - 1;
209         GlyphInfo *info = glyphs + i;
210         // skip drawings
211         if (info->symbol == 0xfffc)
212             continue;
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)
216         if (i > 0)
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))
228             shape_run++;
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);
232     }
233
234 }
235
236 /**
237  * \brief Shape an event's text. Calculates directional runs and shapes them.
238  * \param text_info event's text
239  */
240 void ass_shaper_shape(ASS_Shaper *shaper, TextInfo *text_info)
241 {
242     int i, last_break;
243     FriBidiParType dir;
244     GlyphInfo *glyphs = text_info->glyphs;
245
246     check_allocations(shaper, text_info->length);
247
248     // Get bidi character types and embedding levels
249     last_break = 0;
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);
260             last_break = i + 1;
261         }
262     }
263
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];
267     }
268
269 #if 0
270     printf("levels ");
271     for (i = 0; i < text_info->length; i++) {
272         printf("%d ", glyphs[i].shape_run_id);
273     }
274     printf("\n");
275 #endif
276
277     //shape_fribidi(shaper, text_info->length);
278     shape_harfbuzz(shaper, glyphs, text_info->length);
279
280     // Update glyphs
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;
288             glyphs[i].skip++;
289         }
290     }
291 }
292
293 /**
294  * \brief clean up additional data temporarily needed for shaping and
295  * (e.g. additional glyphs allocated)
296  */
297 void ass_shaper_cleanup(ASS_Shaper *shaper, TextInfo *text_info)
298 {
299     int i;
300
301     for (i = 0; i < text_info->length; i++) {
302         GlyphInfo *info = text_info->glyphs + i;
303         info = info->next;
304         while (info) {
305             GlyphInfo *next = info->next;
306             free(info);
307             info = next;
308         }
309     }
310 }
311
312 /**
313  * \brief Calculate reorder map to render glyphs in visual order
314  */
315 FriBidiStrIndex *ass_shaper_reorder(ASS_Shaper *shaper, TextInfo *text_info)
316 {
317     int i;
318     FriBidiParType dir;
319
320     // Initialize reorder map
321     for (i = 0; i < text_info->length; i++)
322         shaper->cmap[i] = i;
323
324     // Create reorder map line-by-line
325     for (i = 0; i < text_info->n_lines; i++) {
326         LineInfo *line = text_info->lines + i;
327         int level;
328         dir = FRIBIDI_PAR_ON;
329
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);
337     }
338
339 #if 0
340     printf("map ");
341     for (i = 0; i < text_info->length; i++) {
342         printf("%d ", cmap[i]);
343     }
344     printf("\n");
345 #endif
346
347     return shaper->cmap;
348 }