OLD | NEW |
1 /* | 1 /* |
2 * Copyright © 2010 Google, Inc. | 2 * Copyright © 2010,2012 Google, Inc. |
3 * | 3 * |
4 * This is part of HarfBuzz, a text shaping library. | 4 * This is part of HarfBuzz, a text shaping library. |
5 * | 5 * |
6 * Permission is hereby granted, without written agreement and without | 6 * Permission is hereby granted, without written agreement and without |
7 * license or royalty fees, to use, copy, modify, and distribute this | 7 * license or royalty fees, to use, copy, modify, and distribute this |
8 * software and its documentation for any purpose, provided that the | 8 * software and its documentation for any purpose, provided that the |
9 * above copyright notice and the following two paragraphs appear in | 9 * above copyright notice and the following two paragraphs appear in |
10 * all copies of this software. | 10 * all copies of this software. |
11 * | 11 * |
12 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR | 12 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR |
13 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES | 13 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES |
14 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN | 14 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN |
15 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH | 15 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
16 * DAMAGE. | 16 * DAMAGE. |
17 * | 17 * |
18 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, | 18 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
19 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | 19 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
20 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS | 20 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS |
21 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO | 21 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO |
22 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | 22 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
23 * | 23 * |
24 * Google Author(s): Behdad Esfahbod | 24 * Google Author(s): Behdad Esfahbod |
25 */ | 25 */ |
26 | 26 |
27 #include "hb-ot-shape-complex-private.hh" | 27 #include "hb-ot-shape-complex-private.hh" |
28 #include "hb-ot-shape-private.hh" | 28 #include "hb-ot-shape-private.hh" |
29 | 29 |
30 | 30 |
31 | |
32 /* buffer var allocations */ | 31 /* buffer var allocations */ |
33 #define arabic_shaping_action() complex_var_temporary_u8() /* arabic shaping act
ion */ | 32 #define arabic_shaping_action() complex_var_u8_0() /* arabic shaping action */ |
34 | 33 |
35 | 34 |
36 /* | 35 /* |
37 * Bits used in the joining tables | 36 * Bits used in the joining tables |
38 */ | 37 */ |
39 enum { | 38 enum { |
40 JOINING_TYPE_U = 0, | 39 JOINING_TYPE_U = 0, |
41 JOINING_TYPE_R = 1, | 40 JOINING_TYPE_R = 1, |
42 JOINING_TYPE_D = 2, | 41 JOINING_TYPE_D = 2, |
43 JOINING_TYPE_C = JOINING_TYPE_D, | 42 JOINING_TYPE_C = JOINING_TYPE_D, |
(...skipping 30 matching lines...) Expand all Loading... |
74 } | 73 } |
75 | 74 |
76 if (unlikely (hb_in_range<hb_codepoint_t> (u, 0x200C, 0x200D))) { | 75 if (unlikely (hb_in_range<hb_codepoint_t> (u, 0x200C, 0x200D))) { |
77 return u == 0x200C ? JOINING_TYPE_U : JOINING_TYPE_C; | 76 return u == 0x200C ? JOINING_TYPE_U : JOINING_TYPE_C; |
78 } | 77 } |
79 | 78 |
80 return (FLAG(gen_cat) & (FLAG(HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) |
FLAG(HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK) | FLAG(HB_UNICODE_GENERAL_CATEG
ORY_FORMAT))) ? | 79 return (FLAG(gen_cat) & (FLAG(HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) |
FLAG(HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK) | FLAG(HB_UNICODE_GENERAL_CATEG
ORY_FORMAT))) ? |
81 JOINING_TYPE_T : JOINING_TYPE_U; | 80 JOINING_TYPE_T : JOINING_TYPE_U; |
82 } | 81 } |
83 | 82 |
84 static hb_codepoint_t get_arabic_shape (hb_codepoint_t u, unsigned int shape) | 83 static const hb_tag_t arabic_features[] = |
85 { | |
86 if (likely (hb_in_range<hb_codepoint_t> (u, SHAPING_TABLE_FIRST, SHAPING_TABLE
_LAST)) && shape < 4) | |
87 return shaping_table[u - SHAPING_TABLE_FIRST][shape]; | |
88 return u; | |
89 } | |
90 | |
91 static uint16_t get_ligature (hb_codepoint_t first, hb_codepoint_t second) | |
92 { | |
93 if (unlikely (!second)) return 0; | |
94 for (unsigned i = 0; i < ARRAY_LENGTH (ligature_table); i++) | |
95 if (ligature_table[i].first == first) | |
96 for (unsigned j = 0; j < ARRAY_LENGTH (ligature_table[i].ligatures); j++) | |
97 » if (ligature_table[i].ligatures[j].second == second) | |
98 » return ligature_table[i].ligatures[j].ligature; | |
99 return 0; | |
100 } | |
101 | |
102 static const hb_tag_t arabic_syriac_features[] = | |
103 { | 84 { |
104 HB_TAG('i','n','i','t'), | 85 HB_TAG('i','n','i','t'), |
105 HB_TAG('m','e','d','i'), | 86 HB_TAG('m','e','d','i'), |
106 HB_TAG('f','i','n','a'), | 87 HB_TAG('f','i','n','a'), |
107 HB_TAG('i','s','o','l'), | 88 HB_TAG('i','s','o','l'), |
108 /* Syriac */ | 89 /* Syriac */ |
109 HB_TAG('m','e','d','2'), | 90 HB_TAG('m','e','d','2'), |
110 HB_TAG('f','i','n','2'), | 91 HB_TAG('f','i','n','2'), |
111 HB_TAG('f','i','n','3'), | 92 HB_TAG('f','i','n','3'), |
112 HB_TAG_NONE | 93 HB_TAG_NONE |
113 }; | 94 }; |
114 | 95 |
115 | 96 |
116 /* Same order as the feature array */ | 97 /* Same order as the feature array */ |
117 enum { | 98 enum { |
118 INIT, | 99 INIT, |
119 MEDI, | 100 MEDI, |
120 FINA, | 101 FINA, |
121 ISOL, | 102 ISOL, |
122 | 103 |
123 /* Syriac */ | 104 /* Syriac */ |
124 MED2, | 105 MED2, |
125 FIN2, | 106 FIN2, |
126 FIN3, | 107 FIN3, |
127 | 108 |
128 NONE, | 109 NONE, |
129 | 110 |
130 COMMON_NUM_FEATURES = 4, | 111 ARABIC_NUM_FEATURES = NONE |
131 SYRIAC_NUM_FEATURES = 7, | |
132 TOTAL_NUM_FEATURES = NONE | |
133 }; | 112 }; |
134 | 113 |
135 static const struct arabic_state_table_entry { | 114 static const struct arabic_state_table_entry { |
136 uint8_t prev_action; | 115 uint8_t prev_action; |
137 uint8_t curr_action; | 116 uint8_t curr_action; |
138 uint16_t next_state; | 117 uint16_t next_state; |
139 } arabic_state_table[][NUM_STATE_MACHINE_COLS] = | 118 } arabic_state_table[][NUM_STATE_MACHINE_COLS] = |
140 { | 119 { |
141 /* jt_U, jt_R, jt_D, jg_ALAPH, jg_DALATH_RIS
H */ | 120 /* jt_U, jt_R, jt_D, jg_ALAPH, jg_DALATH_RIS
H */ |
142 | 121 |
(...skipping 13 matching lines...) Expand all Loading... |
156 { {NONE,NONE,0}, {MED2,ISOL,1}, {MED2,ISOL,2}, {MED2,FIN2,5}, {MED2,ISOL,6}, }
, | 135 { {NONE,NONE,0}, {MED2,ISOL,1}, {MED2,ISOL,2}, {MED2,FIN2,5}, {MED2,ISOL,6}, }
, |
157 | 136 |
158 /* State 5: prev was FIN2/FIN3 ALAPH, not willing to join. */ | 137 /* State 5: prev was FIN2/FIN3 ALAPH, not willing to join. */ |
159 { {NONE,NONE,0}, {ISOL,ISOL,1}, {ISOL,ISOL,2}, {ISOL,FIN2,5}, {ISOL,ISOL,6}, }
, | 138 { {NONE,NONE,0}, {ISOL,ISOL,1}, {ISOL,ISOL,2}, {ISOL,FIN2,5}, {ISOL,ISOL,6}, }
, |
160 | 139 |
161 /* State 6: prev was DALATH/RISH, not willing to join. */ | 140 /* State 6: prev was DALATH/RISH, not willing to join. */ |
162 { {NONE,NONE,0}, {NONE,ISOL,1}, {NONE,ISOL,2}, {NONE,FIN3,5}, {NONE,ISOL,6}, } | 141 { {NONE,NONE,0}, {NONE,ISOL,1}, {NONE,ISOL,2}, {NONE,FIN3,5}, {NONE,ISOL,6}, } |
163 }; | 142 }; |
164 | 143 |
165 | 144 |
| 145 static void |
| 146 arabic_fallback_shape (const hb_ot_shape_plan_t *plan, |
| 147 hb_font_t *font, |
| 148 hb_buffer_t *buffer); |
166 | 149 |
167 void | 150 static void |
168 _hb_ot_shape_complex_collect_features_arabic (hb_ot_map_builder_t *map, | 151 collect_features_arabic (hb_ot_shape_planner_t *plan) |
169 » » » » » const hb_segment_properties_t *pro
ps) | |
170 { | 152 { |
| 153 hb_ot_map_builder_t *map = &plan->map; |
| 154 |
171 /* For Language forms (in ArabicOT speak), we do the iso/fina/medi/init togeth
er, | 155 /* For Language forms (in ArabicOT speak), we do the iso/fina/medi/init togeth
er, |
172 * then rlig and calt each in their own stage. This makes IranNastaliq's ALLA
H | 156 * then rlig and calt each in their own stage. This makes IranNastaliq's ALLA
H |
173 * ligature work correctly. It's unfortunate though... | 157 * ligature work correctly. It's unfortunate though... |
174 * | 158 * |
175 * This also makes Arial Bold in Windows7 work. See: | 159 * This also makes Arial Bold in Windows7 work. See: |
176 * https://bugzilla.mozilla.org/show_bug.cgi?id=644184 | 160 * https://bugzilla.mozilla.org/show_bug.cgi?id=644184 |
177 * | 161 * |
178 * TODO: Add test cases for these two. | 162 * TODO: Add test cases for these two. |
179 */ | 163 */ |
180 | 164 |
181 map->add_bool_feature (HB_TAG('c','c','m','p')); | 165 map->add_bool_feature (HB_TAG('c','c','m','p')); |
182 map->add_bool_feature (HB_TAG('l','o','c','l')); | 166 map->add_bool_feature (HB_TAG('l','o','c','l')); |
183 | 167 |
184 map->add_gsub_pause (NULL, NULL); | 168 map->add_gsub_pause (NULL); |
185 | 169 |
186 unsigned int num_features = props->script == HB_SCRIPT_SYRIAC ? SYRIAC_NUM_FEA
TURES : COMMON_NUM_FEATURES; | 170 for (unsigned int i = 0; i < ARABIC_NUM_FEATURES; i++) |
187 for (unsigned int i = 0; i < num_features; i++) | 171 map->add_bool_feature (arabic_features[i], false, i < 4); /* The first four
features have fallback. */ |
188 map->add_bool_feature (arabic_syriac_features[i], false); | |
189 | 172 |
190 map->add_gsub_pause (NULL, NULL); | 173 map->add_gsub_pause (NULL); |
191 | 174 |
192 map->add_bool_feature (HB_TAG('r','l','i','g')); | 175 map->add_bool_feature (HB_TAG('r','l','i','g'), true, true); |
193 map->add_gsub_pause (NULL, NULL); | 176 map->add_gsub_pause (arabic_fallback_shape); |
194 | 177 |
195 map->add_bool_feature (HB_TAG('c','a','l','t')); | 178 map->add_bool_feature (HB_TAG('c','a','l','t')); |
196 map->add_gsub_pause (NULL, NULL); | 179 map->add_gsub_pause (NULL); |
197 | 180 |
198 /* ArabicOT spec enables 'cswh' for Arabic where as for basic shaper it's disa
bled by default. */ | 181 /* ArabicOT spec enables 'cswh' for Arabic where as for basic shaper it's disa
bled by default. */ |
199 map->add_bool_feature (HB_TAG('c','s','w','h')); | 182 map->add_bool_feature (HB_TAG('c','s','w','h')); |
200 } | 183 } |
201 | 184 |
202 hb_ot_shape_normalization_mode_t | 185 #include "hb-ot-shape-complex-arabic-fallback.hh" |
203 _hb_ot_shape_complex_normalization_preference_arabic (void) | 186 |
| 187 struct arabic_shape_plan_t |
204 { | 188 { |
205 return HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS; | 189 ASSERT_POD (); |
| 190 |
| 191 /* The "+ 1" in the next array is to accommodate for the "NONE" command, |
| 192 * which is not an OpenType feature, but this simplifies the code by not |
| 193 * having to do a "if (... < NONE) ..." and just rely on the fact that |
| 194 * mask_array[NONE] == 0. */ |
| 195 hb_mask_t mask_array[ARABIC_NUM_FEATURES + 1]; |
| 196 |
| 197 bool do_fallback; |
| 198 arabic_fallback_plan_t *fallback_plan; |
| 199 }; |
| 200 |
| 201 static void * |
| 202 data_create_arabic (const hb_ot_shape_plan_t *plan) |
| 203 { |
| 204 arabic_shape_plan_t *arabic_plan = (arabic_shape_plan_t *) calloc (1, sizeof (
arabic_shape_plan_t)); |
| 205 if (unlikely (!arabic_plan)) |
| 206 return NULL; |
| 207 |
| 208 arabic_plan->do_fallback = plan->props.script == HB_SCRIPT_ARABIC; |
| 209 for (unsigned int i = 0; i < ARABIC_NUM_FEATURES; i++) { |
| 210 arabic_plan->mask_array[i] = plan->map.get_1_mask (arabic_features[i]); |
| 211 if (i < 4) |
| 212 arabic_plan->do_fallback = arabic_plan->do_fallback && plan->map.needs_fal
lback (arabic_features[i]); |
| 213 } |
| 214 |
| 215 return arabic_plan; |
206 } | 216 } |
207 | 217 |
| 218 static void |
| 219 data_destroy_arabic (void *data) |
| 220 { |
| 221 arabic_shape_plan_t *arabic_plan = (arabic_shape_plan_t *) data; |
| 222 |
| 223 arabic_fallback_plan_destroy (arabic_plan->fallback_plan); |
| 224 |
| 225 free (data); |
| 226 } |
208 | 227 |
209 static void | 228 static void |
210 arabic_fallback_shape (hb_font_t *font, hb_buffer_t *buffer) | 229 arabic_joining (hb_buffer_t *buffer) |
211 { | |
212 unsigned int count = buffer->len; | |
213 hb_codepoint_t glyph; | |
214 | |
215 /* Shape to presentation forms */ | |
216 for (unsigned int i = 0; i < count; i++) { | |
217 hb_codepoint_t u = buffer->info[i].codepoint; | |
218 hb_codepoint_t shaped = get_arabic_shape (u, buffer->info[i].arabic_shaping_
action()); | |
219 if (shaped != u && hb_font_get_glyph (font, shaped, 0, &glyph)) | |
220 buffer->info[i].codepoint = shaped; | |
221 } | |
222 | |
223 /* Mandatory ligatures */ | |
224 buffer->clear_output (); | |
225 for (buffer->idx = 0; buffer->idx + 1 < count;) { | |
226 hb_codepoint_t ligature = get_ligature (buffer->cur().codepoint, | |
227 » » » » » buffer->cur(+1).codepoint); | |
228 if (likely (!ligature) || !(hb_font_get_glyph (font, ligature, 0, &glyph)))
{ | |
229 buffer->next_glyph (); | |
230 continue; | |
231 } | |
232 | |
233 buffer->replace_glyphs (2, 1, &ligature); | |
234 | |
235 /* Technically speaking we can skip marks and stuff, like the GSUB path does
. | |
236 * But who cares, we're in fallback! */ | |
237 } | |
238 for (; buffer->idx < count;) | |
239 buffer->next_glyph (); | |
240 buffer->swap_buffers (); | |
241 } | |
242 | |
243 void | |
244 _hb_ot_shape_complex_setup_masks_arabic (hb_ot_map_t *map, | |
245 » » » » » hb_buffer_t *buffer, | |
246 » » » » » hb_font_t *font) | |
247 { | 230 { |
248 unsigned int count = buffer->len; | 231 unsigned int count = buffer->len; |
249 unsigned int prev = 0, state = 0; | 232 unsigned int prev = 0, state = 0; |
250 | 233 |
251 HB_BUFFER_ALLOCATE_VAR (buffer, arabic_shaping_action); | 234 HB_BUFFER_ALLOCATE_VAR (buffer, arabic_shaping_action); |
252 | 235 |
253 for (unsigned int i = 0; i < count; i++) | 236 for (unsigned int i = 0; i < count; i++) |
254 { | 237 { |
255 unsigned int this_type = get_joining_type (buffer->info[i].codepoint, _hb_gl
yph_info_get_general_category (&buffer->info[i])); | 238 unsigned int this_type = get_joining_type (buffer->info[i].codepoint, _hb_gl
yph_info_get_general_category (&buffer->info[i])); |
256 | 239 |
257 if (unlikely (this_type == JOINING_TYPE_T)) { | 240 if (unlikely (this_type == JOINING_TYPE_T)) { |
258 buffer->info[i].arabic_shaping_action() = NONE; | 241 buffer->info[i].arabic_shaping_action() = NONE; |
259 continue; | 242 continue; |
260 } | 243 } |
261 | 244 |
262 const arabic_state_table_entry *entry = &arabic_state_table[state][this_type
]; | 245 const arabic_state_table_entry *entry = &arabic_state_table[state][this_type
]; |
263 | 246 |
264 if (entry->prev_action != NONE) | 247 if (entry->prev_action != NONE) |
265 buffer->info[prev].arabic_shaping_action() = entry->prev_action; | 248 buffer->info[prev].arabic_shaping_action() = entry->prev_action; |
266 | 249 |
267 buffer->info[i].arabic_shaping_action() = entry->curr_action; | 250 buffer->info[i].arabic_shaping_action() = entry->curr_action; |
268 | 251 |
269 prev = i; | 252 prev = i; |
270 state = entry->next_state; | 253 state = entry->next_state; |
271 } | 254 } |
272 | 255 |
273 hb_mask_t mask_array[TOTAL_NUM_FEATURES + 1] = {0}; | |
274 hb_mask_t total_masks = 0; | |
275 unsigned int num_masks = buffer->props.script == HB_SCRIPT_SYRIAC ? SYRIAC_NUM
_FEATURES : COMMON_NUM_FEATURES; | |
276 for (unsigned int i = 0; i < num_masks; i++) { | |
277 mask_array[i] = map->get_1_mask (arabic_syriac_features[i]); | |
278 total_masks |= mask_array[i]; | |
279 } | |
280 | |
281 if (total_masks) { | |
282 /* Has OpenType tables */ | |
283 for (unsigned int i = 0; i < count; i++) | |
284 buffer->info[i].mask |= mask_array[buffer->info[i].arabic_shaping_action()
]; | |
285 } else if (buffer->props.script == HB_SCRIPT_ARABIC) { | |
286 /* Fallback Arabic shaping to Presentation Forms */ | |
287 /* Pitfalls: | |
288 * - This path fires if user force-set init/medi/fina/isol off, | |
289 * - If font does not declare script 'arab', well, what to do? | |
290 * Most probably it's safe to assume that init/medi/fina/isol | |
291 * still mean Arabic shaping, although they do not have to. | |
292 */ | |
293 arabic_fallback_shape (font, buffer); | |
294 } | |
295 | |
296 HB_BUFFER_DEALLOCATE_VAR (buffer, arabic_shaping_action); | 256 HB_BUFFER_DEALLOCATE_VAR (buffer, arabic_shaping_action); |
297 } | 257 } |
298 | 258 |
| 259 static void |
| 260 setup_masks_arabic (const hb_ot_shape_plan_t *plan, |
| 261 hb_buffer_t *buffer, |
| 262 hb_font_t *font) |
| 263 { |
| 264 const arabic_shape_plan_t *arabic_plan = (const arabic_shape_plan_t *) plan->d
ata; |
299 | 265 |
| 266 arabic_joining (buffer); |
| 267 unsigned int count = buffer->len; |
| 268 for (unsigned int i = 0; i < count; i++) |
| 269 buffer->info[i].mask |= arabic_plan->mask_array[buffer->info[i].arabic_shapi
ng_action()]; |
| 270 } |
| 271 |
| 272 |
| 273 static void |
| 274 arabic_fallback_shape (const hb_ot_shape_plan_t *plan, |
| 275 hb_font_t *font, |
| 276 hb_buffer_t *buffer) |
| 277 { |
| 278 const arabic_shape_plan_t *arabic_plan = (const arabic_shape_plan_t *) plan->d
ata; |
| 279 |
| 280 if (!arabic_plan->do_fallback) |
| 281 return; |
| 282 |
| 283 retry: |
| 284 arabic_fallback_plan_t *fallback_plan = (arabic_fallback_plan_t *) hb_atomic_p
tr_get (&arabic_plan->fallback_plan); |
| 285 if (unlikely (!fallback_plan)) |
| 286 { |
| 287 /* This sucks. We need a font to build the fallback plan... */ |
| 288 fallback_plan = arabic_fallback_plan_create (plan, font); |
| 289 if (unlikely (!hb_atomic_ptr_cmpexch (&(const_cast<arabic_shape_plan_t *> (a
rabic_plan))->fallback_plan, NULL, fallback_plan))) { |
| 290 arabic_fallback_plan_destroy (fallback_plan); |
| 291 goto retry; |
| 292 } |
| 293 } |
| 294 |
| 295 arabic_fallback_plan_shape (fallback_plan, font, buffer); |
| 296 } |
| 297 |
| 298 |
| 299 const hb_ot_complex_shaper_t _hb_ot_complex_shaper_arabic = |
| 300 { |
| 301 "arabic", |
| 302 collect_features_arabic, |
| 303 NULL, /* override_features */ |
| 304 data_create_arabic, |
| 305 data_destroy_arabic, |
| 306 NULL, /* preprocess_text_arabic */ |
| 307 NULL, /* normalization_preference */ |
| 308 setup_masks_arabic, |
| 309 true, /* zero_width_attached_marks */ |
| 310 }; |
OLD | NEW |