]> granicus.if.org Git - libass/blob - libass/ass_shaper.c
HarfBuzz shaping support
[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         while (i < (text_info->length - 1) && level == glyphs[i+1].shape_run_id)
102             i++;
103         //printf("run %d from %d to %d with level %d\n", run, k, i, level);
104         FT_Face run_font = glyphs[k].font->faces[glyphs[k].face_index];
105         runs[run].offset = k;
106         runs[run].end    = i;
107         runs[run].buf    = hb_buffer_create(i - k + 1);
108         runs[run].font   = hb_ft_font_create(run_font, NULL);
109         hb_buffer_set_direction(runs[run].buf, (level % 2) ? HB_DIRECTION_RTL :
110                 HB_DIRECTION_LTR);
111         hb_buffer_add_utf32(runs[run].buf, event_text + k, i - k + 1,
112                 0, i - k + 1);
113         hb_shape(runs[run].font, runs[run].buf, NULL, 0);
114     }
115     //printf("shaped %d runs\n", run);
116
117     // Initialize: skip all glyphs, this is undone later as needed
118     for (i = 0; i < text_info->length; i++)
119         glyphs[i].skip = 1;
120
121     // Update glyph indexes, positions and advances from the shaped runs
122     for (i = 0; i < run; i++) {
123         int num_glyphs = hb_buffer_get_length(runs[i].buf);
124         hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(runs[i].buf, NULL);
125         hb_glyph_position_t *pos    = hb_buffer_get_glyph_positions(runs[i].buf, NULL);
126         //printf("run text len %d num_glyphs %d\n", runs[i].end - runs[i].offset + 1,
127         //        num_glyphs);
128         // Update glyphs
129         for (j = 0; j < num_glyphs; j++) {
130             int idx = glyph_info[j].cluster + runs[i].offset;
131 #if 0
132             printf("run %d cluster %d codepoint %d -> '%c'\n", i, idx,
133                     glyph_info[j].codepoint, event_text[idx]);
134             printf("position %d %d advance %d %d\n",
135                     pos[j].x_offset, pos[j].y_offset,
136                     pos[j].x_advance, pos[j].y_advance);
137 #endif
138             glyphs[idx].skip = 0;
139             glyphs[idx].glyph_index = glyph_info[j].codepoint;
140             glyphs[idx].offset.x    = pos[j].x_offset * glyphs[idx].scale_x;
141             glyphs[idx].offset.y    = pos[j].y_offset * glyphs[idx].scale_y;
142             glyphs[idx].advance.x   = pos[j].x_advance * glyphs[idx].scale_x;
143             glyphs[idx].advance.y   = pos[j].y_advance * glyphs[idx].scale_y;
144         }
145     }
146
147     // Free runs and associated data
148     for (i = 0; i < run; i++) {
149         hb_buffer_destroy(runs[i].buf);
150         hb_font_destroy(runs[i].font);
151     }
152
153     // Update glyphs
154     for (i = 0; i < text_info->length; i++) {
155         glyphs[i].symbol = event_text[i];
156         // Skip direction override control characters
157         // NOTE: Behdad said HarfBuzz is supposed to remove these, but this hasn't
158         // been implemented yet
159         if (glyphs[i].symbol <= 0x202F && glyphs[i].symbol >= 0x202a) {
160             glyphs[i].symbol = 0;
161             glyphs[i].skip++;
162         }
163     }
164
165     free(joins);
166     free(event_text);
167 }
168
169 void ass_shaper_reorder(TextInfo *text_info, FriBidiCharType *ctypes,
170                         FriBidiLevel *emblevels, FriBidiStrIndex *cmap)
171 {
172     int i;
173     FriBidiParType dir;
174
175     // Initialize reorder map
176     for (i = 0; i < text_info->length; i++)
177         cmap[i] = i;
178
179     // Create reorder map line-by-line
180     for (i = 0; i < text_info->n_lines; i++) {
181         LineInfo *line = text_info->lines + i;
182         int level;
183         dir = FRIBIDI_PAR_ON;
184
185         // FIXME: we should actually specify
186         // the correct paragraph base direction
187         level = fribidi_reorder_line(FRIBIDI_FLAGS_DEFAULT,
188                 ctypes + line->offset, line->len, 0, dir,
189                 emblevels + line->offset, NULL, cmap + line->offset);
190         //printf("reorder line %d to level %d\n", i, level);
191     }
192
193 #if 0
194     printf("map ");
195     for (i = 0; i < text_info->length; i++) {
196         printf("%d ", cmap[i]);
197     }
198     printf("\n");
199 #endif
200
201 }