]> granicus.if.org Git - libass/blob - libass/ass_shaper.c
Various small fixes to HarfBuzz rendering
[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_render.h"
23 #include "ass_shaper.h"
24
25 #define MAX_RUNS 30
26
27 /**
28  * \brief Print version information
29  */
30 void ass_shaper_info(ASS_Library *lib)
31 {
32     ass_msg(lib, MSGL_V, "Complex text layout enabled, using FriBidi "
33             FRIBIDI_VERSION " HarfBuzz-ng %s", hb_version_string());
34 }
35
36 /**
37  * \brief Shape an event's text. Calculates directional runs and shapes them.
38  * \param text_info event's text
39  * \param ctypes returns character types
40  * \param emblevels returns embedding levels (directional runs)
41  */
42 void ass_shaper_shape(TextInfo *text_info, FriBidiCharType *ctypes,
43                       FriBidiLevel *emblevels)
44 {
45     int i, j, last_break;
46     FriBidiParType dir;
47     FriBidiChar *event_text = calloc(sizeof(*event_text), text_info->length);
48     FriBidiJoiningType *joins = calloc(sizeof(*joins), text_info->length);
49     GlyphInfo *glyphs = text_info->glyphs;
50     // XXX: dynamically allocate
51     struct {
52         int offset;
53         int end;
54         hb_buffer_t *buf;
55         hb_font_t *font;
56     } runs[MAX_RUNS];
57
58     // Get bidi character types and embedding levels
59     last_break = 0;
60     for (i = 0; i < text_info->length; i++) {
61         event_text[i] = glyphs[i].symbol;
62         // embedding levels should be calculated paragraph by paragraph
63         if (glyphs[i].symbol == '\n' || i == text_info->length - 1) {
64             //printf("paragraph from %d to %d\n", last_break, i);
65             dir = FRIBIDI_PAR_ON;
66             fribidi_get_bidi_types(event_text + last_break, i - last_break + 1,
67                     ctypes + last_break);
68             fribidi_get_par_embedding_levels(ctypes + last_break,
69                     i - last_break + 1, &dir, emblevels + last_break);
70             last_break = i + 1;
71         }
72     }
73
74     // add embedding levels to shape runs for final runs
75     for (i = 0; i < text_info->length; i++) {
76         glyphs[i].shape_run_id += emblevels[i];
77     }
78
79 #if 0
80     printf("levels ");
81     for (i = 0; i < text_info->length; i++) {
82         printf("%d ", glyphs[i].shape_run_id);
83     }
84     printf("\n");
85 #endif
86
87 #if 0
88     // Use FriBidi's shaper for mirroring and simple Arabic shaping
89     fribidi_get_joining_types(event_text, text_info->length, joins);
90     fribidi_join_arabic(ctypes, text_info->length, emblevels, joins);
91     fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC, emblevels,
92             text_info->length, joins, event_text);
93 #endif
94
95     // Shape runs with HarfBuzz-ng
96     int run = 0;
97     for (i = 0; i < text_info->length && run < MAX_RUNS; i++, run++) {
98         // get length and level of the current run
99         int k = i;
100         int level = glyphs[i].shape_run_id;
101         int direction = emblevels[k] % 2;
102         while (i < (text_info->length - 1) && level == glyphs[i+1].shape_run_id)
103             i++;
104         //printf("run %d from %d to %d with level %d\n", run, k, i, level);
105         FT_Face run_font = glyphs[k].font->faces[glyphs[k].face_index];
106         runs[run].offset = k;
107         runs[run].end    = i;
108         runs[run].buf    = hb_buffer_create(i - k + 1);
109         runs[run].font   = hb_ft_font_create(run_font, NULL);
110         hb_buffer_set_direction(runs[run].buf, direction ? HB_DIRECTION_RTL :
111                 HB_DIRECTION_LTR);
112         hb_buffer_add_utf32(runs[run].buf, event_text + k, i - k + 1,
113                 0, i - k + 1);
114         hb_shape(runs[run].font, runs[run].buf, NULL, 0);
115     }
116     //printf("shaped %d runs\n", run);
117
118     // Initialize: skip all glyphs, this is undone later as needed
119     for (i = 0; i < text_info->length; i++)
120         glyphs[i].skip = 1;
121
122     // Update glyph indexes, positions and advances from the shaped runs
123     for (i = 0; i < run; i++) {
124         int num_glyphs = hb_buffer_get_length(runs[i].buf);
125         hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(runs[i].buf, NULL);
126         hb_glyph_position_t *pos    = hb_buffer_get_glyph_positions(runs[i].buf, NULL);
127         //printf("run text len %d num_glyphs %d\n", runs[i].end - runs[i].offset + 1,
128         //        num_glyphs);
129         // Update glyphs
130         for (j = 0; j < num_glyphs; j++) {
131             int idx = glyph_info[j].cluster + runs[i].offset;
132             GlyphInfo *info = glyphs + idx;
133             GlyphInfo *root = info;
134 #if 0
135             printf("run %d cluster %d codepoint %d -> '%c'\n", i, idx,
136                     glyph_info[j].codepoint, event_text[idx]);
137             printf("position %d %d advance %d %d\n",
138                     pos[j].x_offset, pos[j].y_offset,
139                     pos[j].x_advance, pos[j].y_advance);
140 #endif
141
142             // if we have more than one glyph per cluster, allocate a new one
143             // and attach to the root glyph
144             if (info->skip == 0) {
145                 //printf("duplicate cluster entry, adding glyph\n");
146                 while (info->next)
147                     info = info->next;
148                 info->next = malloc(sizeof(GlyphInfo));
149                 memcpy(info->next, info, sizeof(GlyphInfo));
150                 info = info->next;
151                 info->next = NULL;
152             }
153
154             // set position and advance
155             info->skip = 0;
156             info->glyph_index = glyph_info[j].codepoint;
157             info->offset.x    = pos[j].x_offset * info->scale_x;
158             info->offset.y    = -pos[j].y_offset * info->scale_y;
159             info->advance.x   = pos[j].x_advance * info->scale_x;
160             info->advance.y   = -pos[j].y_advance * info->scale_y;
161
162             // accumulate advance in the root glyph
163             root->cluster_advance.x += info->advance.x;
164             root->cluster_advance.y += info->advance.y;
165         }
166     }
167
168     // Free runs and associated data
169     for (i = 0; i < run; i++) {
170         hb_buffer_destroy(runs[i].buf);
171         hb_font_destroy(runs[i].font);
172     }
173
174     // Update glyphs
175     for (i = 0; i < text_info->length; i++) {
176         glyphs[i].symbol = event_text[i];
177         // Skip direction override control characters
178         // NOTE: Behdad said HarfBuzz is supposed to remove these, but this hasn't
179         // been implemented yet
180         if (glyphs[i].symbol <= 0x202F && glyphs[i].symbol >= 0x202a) {
181             glyphs[i].symbol = 0;
182             glyphs[i].skip++;
183         }
184     }
185
186     free(joins);
187     free(event_text);
188 }
189
190 /**
191  * \brief clean up additional data temporarily needed for shaping and
192  * (e.g. additional glyphs allocated)
193  */
194 void ass_shaper_cleanup(TextInfo *text_info)
195 {
196     int i;
197
198     for (i = 0; i < text_info->length; i++) {
199         GlyphInfo *info = text_info->glyphs + i;
200         info = info->next;
201         while (info) {
202             GlyphInfo *next = info->next;
203             free(info);
204             info = next;
205         }
206     }
207 }
208
209 void ass_shaper_reorder(TextInfo *text_info, FriBidiCharType *ctypes,
210                         FriBidiLevel *emblevels, FriBidiStrIndex *cmap)
211 {
212     int i;
213     FriBidiParType dir;
214
215     // Initialize reorder map
216     for (i = 0; i < text_info->length; i++)
217         cmap[i] = i;
218
219     // Create reorder map line-by-line
220     for (i = 0; i < text_info->n_lines; i++) {
221         LineInfo *line = text_info->lines + i;
222         int level;
223         dir = FRIBIDI_PAR_ON;
224
225         // FIXME: we should actually specify
226         // the correct paragraph base direction
227         level = fribidi_reorder_line(FRIBIDI_FLAGS_DEFAULT,
228                 ctypes + line->offset, line->len, 0, dir,
229                 emblevels + line->offset, NULL, cmap + line->offset);
230         //printf("reorder line %d to level %d\n", i, level);
231     }
232
233 #if 0
234     printf("map ");
235     for (i = 0; i < text_info->length; i++) {
236         printf("%d ", cmap[i]);
237     }
238     printf("\n");
239 #endif
240
241 }