harfrust/hb/
ot_shaper_hangul.rs1use alloc::boxed::Box;
2
3use super::buffer::*;
4use super::font_funcs::FontFuncsDispatch;
5use super::ot_map::*;
6use super::ot_shape::*;
7use super::ot_shape_normalize::HB_OT_SHAPE_NORMALIZATION_MODE_NONE;
8use super::ot_shape_plan::hb_ot_shape_plan_t;
9use super::ot_shaper::*;
10use super::*;
11use crate::hb::unicode::Codepoint;
12use crate::BufferFlags;
13
14const LJMO: u8 = 1;
15const VJMO: u8 = 2;
16const TJMO: u8 = 3;
17
18impl GlyphInfo {
19 declare_buffer_var_alias!(
20 OT_SHAPER_VAR_U8_AUXILIARY_VAR,
21 u8,
22 HANGUL_SHAPING_FEATURE_VAR,
23 hangul_shaping_feature,
24 set_hangul_shaping_feature
25 );
26}
27
28fn collect_features_hangul(planner: &mut hb_ot_shape_planner_t) {
29 planner
30 .ot_map
31 .add_feature(hb_tag_t::new(b"ljmo"), F_NONE, 1);
32 planner
33 .ot_map
34 .add_feature(hb_tag_t::new(b"vjmo"), F_NONE, 1);
35 planner
36 .ot_map
37 .add_feature(hb_tag_t::new(b"tjmo"), F_NONE, 1);
38}
39
40fn override_features_hangul(planner: &mut hb_ot_shape_planner_t) {
41 planner.ot_map.disable_feature(hb_tag_t::new(b"calt"));
45}
46
47struct hangul_shape_plan_t {
48 mask_array: [hb_mask_t; 4],
49}
50
51fn data_create_hangul(map: &hb_ot_map_t) -> hangul_shape_plan_t {
52 hangul_shape_plan_t {
53 mask_array: [
54 0,
55 map.get_1_mask(hb_tag_t::new(b"ljmo")),
56 map.get_1_mask(hb_tag_t::new(b"vjmo")),
57 map.get_1_mask(hb_tag_t::new(b"tjmo")),
58 ],
59 }
60}
61
62const L_BASE: u32 = 0x1100;
63const V_BASE: u32 = 0x1161;
64const T_BASE: u32 = 0x11A7;
65const L_COUNT: u32 = 19;
66const V_COUNT: u32 = 21;
67const T_COUNT: u32 = 28;
68const N_COUNT: u32 = V_COUNT * T_COUNT;
69const S_COUNT: u32 = L_COUNT * N_COUNT;
70const S_BASE: u32 = 0xAC00;
71
72fn is_combining_l(u: u32) -> bool {
73 (L_BASE..L_BASE + L_COUNT).contains(&u)
74}
75
76fn is_combining_v(u: u32) -> bool {
77 (V_BASE..V_BASE + V_COUNT).contains(&u)
78}
79
80fn is_combining_t(u: u32) -> bool {
81 (T_BASE + 1..T_BASE + T_COUNT).contains(&u)
82}
83
84fn is_combined_s(u: u32) -> bool {
85 (S_BASE..S_BASE + S_COUNT).contains(&u)
86}
87
88fn is_l(u: u32) -> bool {
89 (0x1100..=0x115F).contains(&u) || (0xA960..=0xA97C).contains(&u)
90}
91
92fn is_v(u: u32) -> bool {
93 (0x1160..=0x11A7).contains(&u) || (0xD7B0..=0xD7C6).contains(&u)
94}
95
96fn is_t(u: u32) -> bool {
97 (0x11A8..=0x11FF).contains(&u) || (0xD7CB..=0xD7FB).contains(&u)
98}
99
100fn is_hangul_tone(u: u32) -> bool {
101 (0x302E..=0x302F).contains(&u)
102}
103
104fn is_zero_width_char(face: &mut FontFuncsDispatch, c: Codepoint) -> bool {
105 if let Some(glyph) = face.nominal_glyph(c) {
106 face.advance_width(glyph) == 0
107 } else {
108 false
109 }
110}
111
112fn preprocess_text_hangul(
113 _: &hb_ot_shape_plan_t,
114 face: &mut FontFuncsDispatch,
115 buffer: &mut hb_buffer_t,
116) {
117 buffer.allocate_var(GlyphInfo::HANGUL_SHAPING_FEATURE_VAR);
118
119 buffer.clear_output();
167 let mut start = 0;
169 let mut end = 0;
170 buffer.idx = 0;
171 while buffer.idx < buffer.len {
172 let u = buffer.cur(0).glyph_id;
173 let c = buffer.cur(0).as_codepoint();
174
175 if is_hangul_tone(u) {
176 if start < end && end == buffer.out_len {
180 buffer.unsafe_to_break_from_outbuffer(Some(start), Some(buffer.idx));
182 buffer.next_glyph();
183 if !is_zero_width_char(face, c) {
184 buffer.merge_out_grapheme_clusters(start, end + 1);
185 let out_info = buffer.out_info_mut();
186 let tone = out_info[end];
187 for i in (0..end - start).rev() {
188 out_info[i + start + 1] = out_info[i + start];
189 }
190 out_info[start] = tone;
191 }
192 } else {
193 if !buffer
195 .flags
196 .contains(BufferFlags::DO_NOT_INSERT_DOTTED_CIRCLE)
197 && face.has_glyph(0x25CC)
198 {
199 let mut chars = [0; 2];
200 if !is_zero_width_char(face, c) {
201 chars[0] = u;
202 chars[1] = 0x25CC;
203 } else {
204 chars[0] = 0x25CC;
205 chars[1] = u;
206 }
207
208 buffer.replace_glyphs(1, 2, &chars);
209 } else {
210 buffer.next_glyph();
212 }
213 }
214
215 start = buffer.out_len;
216 end = buffer.out_len;
217 continue;
218 }
219
220 start = buffer.out_len;
223
224 if is_l(u) && buffer.idx + 1 < buffer.len {
225 let l = u;
226 let v = buffer.cur(1).glyph_id;
227 if is_v(v) {
228 let mut t = 0;
230 let mut tindex = 0;
231 if buffer.idx + 2 < buffer.len {
232 t = buffer.cur(2).glyph_id;
233 if is_t(t) {
234 tindex = t - T_BASE;
236 } else {
237 t = 0;
239 }
240 }
241
242 let offset = if t != 0 { 3 } else { 2 };
243 buffer.unsafe_to_break(Some(buffer.idx), Some(buffer.idx + offset));
244
245 if is_combining_l(l) && is_combining_v(v) && (t == 0 || is_combining_t(t)) {
247 let s = S_BASE + (l - L_BASE) * N_COUNT + (v - V_BASE) * T_COUNT + tindex;
249 if face.has_glyph(s) {
250 let n = if t != 0 { 3 } else { 2 };
251 buffer.replace_glyphs(n, 1, &[s]);
252 end = start + 1;
253 continue;
254 }
255 }
256
257 buffer.cur_mut(0).set_hangul_shaping_feature(LJMO);
262 buffer.next_glyph();
263 buffer.cur_mut(0).set_hangul_shaping_feature(VJMO);
264 buffer.next_glyph();
265 if t != 0 {
266 buffer.cur_mut(0).set_hangul_shaping_feature(TJMO);
267 buffer.next_glyph();
268 end = start + 3;
269 } else {
270 end = start + 2;
271 }
272
273 buffer.merge_out_grapheme_clusters(start, end);
274
275 continue;
276 }
277 } else if is_combined_s(u) {
278 let s = u;
280 let has_glyph = face.has_glyph(s);
281
282 let lindex = (s - S_BASE) / N_COUNT;
283 let nindex = (s - S_BASE) % N_COUNT;
284 let vindex = nindex / T_COUNT;
285 let tindex = nindex % T_COUNT;
286
287 if tindex == 0 && buffer.idx + 1 < buffer.len && is_combining_t(buffer.cur(1).glyph_id)
288 {
289 let new_tindex = buffer.cur(1).glyph_id - T_BASE;
291 let new_s = s + new_tindex;
292
293 if face.has_glyph(new_s) {
294 buffer.replace_glyphs(2, 1, &[new_s]);
295 end = start + 1;
296 continue;
297 } else {
298 buffer.unsafe_to_break(Some(buffer.idx), Some(buffer.idx + 2));
300 }
301 }
302
303 if !has_glyph
307 || (tindex == 0 && buffer.idx + 1 < buffer.len && is_t(buffer.cur(1).glyph_id))
308 {
309 let decomposed = [L_BASE + lindex, V_BASE + vindex, T_BASE + tindex];
310 if face.has_glyph(decomposed[0])
311 && face.has_glyph(decomposed[1])
312 && (tindex == 0 || face.has_glyph(decomposed[2]))
313 {
314 let mut s_len = if tindex != 0 { 3 } else { 2 };
315 buffer.replace_glyphs(1, s_len, &decomposed);
316
317 if has_glyph && tindex == 0 {
320 buffer.next_glyph();
321 s_len += 1;
322 }
323
324 end = start + s_len;
327
328 buffer.out_info_mut()[start + 0].set_hangul_shaping_feature(LJMO);
329 buffer.out_info_mut()[start + 1].set_hangul_shaping_feature(VJMO);
330 if start + 2 < end {
331 buffer.out_info_mut()[start + 2].set_hangul_shaping_feature(TJMO);
332 }
333
334 buffer.merge_out_grapheme_clusters(start, end);
335
336 continue;
337 } else if tindex == 0 && buffer.idx + 1 > buffer.len && is_t(buffer.cur(1).glyph_id)
338 {
339 buffer.unsafe_to_break(Some(buffer.idx), Some(buffer.idx + 2));
341 }
342 }
343
344 if has_glyph {
345 end = start + 1;
347 buffer.next_glyph();
348 continue;
349 }
350 }
351
352 buffer.next_glyph();
355 }
356
357 buffer.sync();
358}
359
360fn setup_masks_hangul(
361 plan: &hb_ot_shape_plan_t,
362 _: &mut FontFuncsDispatch,
363 buffer: &mut hb_buffer_t,
364) {
365 let hangul_plan = plan.data::<hangul_shape_plan_t>();
366 for info in buffer.info_slice_mut() {
367 info.mask |= hangul_plan.mask_array[info.hangul_shaping_feature() as usize];
368 }
369
370 buffer.deallocate_var(GlyphInfo::HANGUL_SHAPING_FEATURE_VAR);
371}
372
373pub const HANGUL_SHAPER: hb_ot_shaper_t = hb_ot_shaper_t {
374 collect_features: Some(collect_features_hangul),
375 override_features: Some(override_features_hangul),
376 create_data: Some(|plan| Box::new(data_create_hangul(&plan.ot_map))),
377 preprocess_text: Some(preprocess_text_hangul),
378 postprocess_glyphs: None,
379 normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_NONE,
380 decompose: None,
381 compose: None,
382 setup_masks: Some(setup_masks_hangul),
383 gpos_tag: None,
384 reorder_marks: None,
385 zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE,
386 fallback_position: true,
387};