1use super::metrics::BlueZones;
4use super::shape::{ShaperCoverageKind, VisitedLookupSet};
5use alloc::vec::Vec;
6use raw::types::{GlyphId, Tag};
7
8#[derive(Copy, Clone, PartialEq, Eq, Debug)]
10#[repr(transparent)]
11pub struct GlyphStyle(pub(super) u16);
12
13impl GlyphStyle {
14 const STYLE_INDEX_MASK: u16 = 0xFF;
19 const UNASSIGNED: u16 = Self::STYLE_INDEX_MASK;
20 const NON_BASE: u16 = 0x100;
22 const DIGIT: u16 = 0x200;
23 const FROM_GSUB_OUTPUT: u16 = 0x8000;
26
27 pub const fn from_raw_parts(style_index: u16, is_non_base: bool, is_digit: bool) -> Self {
29 let base = is_non_base as u16 * Self::NON_BASE;
30 let digit = is_digit as u16 * Self::DIGIT;
31 Self((style_index & Self::STYLE_INDEX_MASK) | base | digit)
32 }
33
34 pub const fn is_unassigned(self) -> bool {
36 self.0 & Self::STYLE_INDEX_MASK == Self::UNASSIGNED
37 }
38
39 pub const fn is_non_base(self) -> bool {
41 self.0 & Self::NON_BASE != 0
42 }
43
44 pub const fn is_digit(self) -> bool {
46 self.0 & Self::DIGIT != 0
47 }
48
49 pub fn style_class(self) -> Option<&'static StyleClass> {
51 StyleClass::from_index(self.style_index()?)
52 }
53
54 pub const fn style_index(self) -> Option<u16> {
56 let ix = self.0 & Self::STYLE_INDEX_MASK;
57 if ix != Self::UNASSIGNED {
58 Some(ix)
59 } else {
60 None
61 }
62 }
63
64 fn maybe_assign(&mut self, other: Self) {
65 if other.0 & Self::STYLE_INDEX_MASK <= self.0 & Self::STYLE_INDEX_MASK {
75 self.0 = (self.0 & !Self::STYLE_INDEX_MASK) | other.0;
76 }
77 }
78
79 pub(super) fn set_from_gsub_output(&mut self) {
80 self.0 |= Self::FROM_GSUB_OUTPUT
81 }
82
83 pub(super) fn clear_from_gsub(&mut self) {
84 self.0 &= !Self::FROM_GSUB_OUTPUT;
85 }
86
87 pub(super) fn maybe_assign_gsub_output_style(&mut self, style: &StyleClass) -> bool {
94 let style_ix = style.index as u16;
95 if self.0 & Self::FROM_GSUB_OUTPUT != 0 && self.is_unassigned() {
96 self.clear_from_gsub();
97 self.0 = (self.0 & !Self::STYLE_INDEX_MASK) | style_ix;
98 true
99 } else {
100 false
101 }
102 }
103}
104
105impl Default for GlyphStyle {
106 fn default() -> Self {
107 Self(Self::UNASSIGNED)
108 }
109}
110
111const UNMAPPED_STYLE: u8 = 0xFF;
113
114#[derive(Debug)]
119pub(crate) struct GlyphStyleMap {
120 styles: Vec<GlyphStyle>,
122 metrics_map: [u8; MAX_STYLES],
127 metrics_count: u8,
129}
130
131impl GlyphStyleMap {
132 pub fn new(glyph_count: u32, shaper: &Shaper) -> Self {
137 let lookup_count = shaper.lookup_count() as usize;
138 if lookup_count > 0 {
139 let lookup_set_byte_size = lookup_count.div_ceil(8);
142 super::super::memory::with_temporary_memory(lookup_set_byte_size, |bytes| {
143 Self::new_inner(glyph_count, shaper, VisitedLookupSet::new(bytes))
144 })
145 } else {
146 Self::new_inner(glyph_count, shaper, VisitedLookupSet::new(&mut []))
147 }
148 }
149
150 fn new_inner(glyph_count: u32, shaper: &Shaper, mut visited_set: VisitedLookupSet) -> Self {
151 let mut map = Self {
152 styles: vec![GlyphStyle::default(); glyph_count as usize],
153 metrics_map: [UNMAPPED_STYLE; MAX_STYLES],
154 metrics_count: 0,
155 };
156 for style in super::style::STYLE_CLASSES {
159 if style.feature.is_some()
160 && shaper.compute_coverage(
161 style,
162 ShaperCoverageKind::Script,
163 &mut map.styles,
164 &mut visited_set,
165 )
166 {
167 map.use_style(style.index);
168 }
169 }
170 let mut last_range: Option<(usize, StyleRange)> = None;
174 for (ch, gid) in shaper.charmap().mappings() {
175 let Some(style) = map.styles.get_mut(gid.to_u32() as usize) else {
176 continue;
177 };
178 if let Some(last) = last_range {
181 if last.1.contains(ch) {
182 style.maybe_assign(last.1.style);
183 continue;
184 }
185 }
186 let ix = match STYLE_RANGES.binary_search_by(|x| x.first.cmp(&ch)) {
187 Ok(i) => i,
188 Err(i) => i.saturating_sub(1),
189 };
190 let Some(range) = STYLE_RANGES.get(ix).copied() else {
191 continue;
192 };
193 if range.contains(ch) {
194 style.maybe_assign(range.style);
195 if let Some(style_ix) = range.style.style_index() {
196 map.use_style(style_ix as usize);
197 }
198 last_range = Some((ix, range));
199 }
200 }
201 for style in super::style::STYLE_CLASSES {
204 if style.feature.is_none()
205 && shaper.compute_coverage(
206 style,
207 ShaperCoverageKind::Script,
208 &mut map.styles,
209 &mut visited_set,
210 )
211 {
212 map.use_style(style.index);
213 }
214 }
215 let default_style = &STYLE_CLASSES[StyleClass::LATN];
219 if shaper.compute_coverage(
220 default_style,
221 ShaperCoverageKind::Default,
222 &mut map.styles,
223 &mut visited_set,
224 ) {
225 map.use_style(default_style.index);
226 }
227 let mut need_hani = false;
232 for style in map.styles.iter_mut() {
233 if style.is_unassigned() {
234 style.0 &= !GlyphStyle::STYLE_INDEX_MASK;
235 style.0 |= StyleClass::HANI as u16;
236 need_hani = true;
237 }
238 }
239 if need_hani {
240 map.use_style(StyleClass::HANI);
241 }
242 for digit_char in '0'..='9' {
245 if let Some(style) = shaper
246 .charmap()
247 .map(digit_char)
248 .and_then(|gid| map.styles.get_mut(gid.to_u32() as usize))
249 {
250 style.0 |= GlyphStyle::DIGIT;
251 }
252 }
253 map
254 }
255
256 pub fn style(&self, glyph_id: GlyphId) -> Option<GlyphStyle> {
257 self.styles.get(glyph_id.to_u32() as usize).copied()
258 }
259
260 pub fn metrics_index(&self, style: GlyphStyle) -> Option<usize> {
262 let ix = style.style_index()? as usize;
263 let metrics_ix = *self.metrics_map.get(ix)? as usize;
264 if metrics_ix != UNMAPPED_STYLE as usize {
265 Some(metrics_ix)
266 } else {
267 None
268 }
269 }
270
271 pub fn metrics_count(&self) -> usize {
273 self.metrics_count as usize
274 }
275
276 pub fn metrics_styles(&self) -> impl Iterator<Item = &'static StyleClass> + '_ {
279 let mut reverse_map = [UNMAPPED_STYLE; MAX_STYLES];
281 for (ix, &entry) in self.metrics_map.iter().enumerate() {
282 if entry != UNMAPPED_STYLE {
283 reverse_map[entry as usize] = ix as u8;
284 }
285 }
286 reverse_map
287 .into_iter()
288 .enumerate()
289 .filter_map(move |(mapped, style_ix)| {
290 if mapped == UNMAPPED_STYLE as usize {
291 None
292 } else {
293 STYLE_CLASSES.get(style_ix as usize)
294 }
295 })
296 }
297
298 fn use_style(&mut self, style_ix: usize) {
299 let mapped = &mut self.metrics_map[style_ix];
300 if *mapped == UNMAPPED_STYLE {
301 *mapped = self.metrics_count;
304 self.metrics_count += 1;
305 }
306 }
307}
308
309impl Default for GlyphStyleMap {
310 fn default() -> Self {
311 Self {
312 styles: Default::default(),
313 metrics_map: [UNMAPPED_STYLE; MAX_STYLES],
314 metrics_count: 0,
315 }
316 }
317}
318
319#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
322pub enum ScriptGroup {
323 #[default]
327 Default,
328 Cjk,
329 Indic,
330}
331
332#[derive(Clone, Debug)]
335pub struct ScriptClass {
336 pub name: &'static str,
338 pub group: ScriptGroup,
340 pub tag: Tag,
342 pub hint_top_to_bottom: bool,
344 pub std_chars: &'static str,
346 pub blues: &'static [(&'static str, BlueZones)],
348}
349
350#[derive(Clone, Debug)]
357pub struct StyleClass {
358 pub name: &'static str,
360 pub index: usize,
362 pub script: &'static ScriptClass,
364 #[allow(unused)]
367 pub feature: Option<Tag>,
368}
369
370impl StyleClass {
371 pub(crate) fn from_index(index: u16) -> Option<&'static StyleClass> {
372 STYLE_CLASSES.get(index as usize)
373 }
374}
375
376#[derive(Copy, Clone, Debug)]
378pub(super) struct StyleRange {
379 pub first: u32,
380 pub last: u32,
381 pub style: GlyphStyle,
382}
383
384impl StyleRange {
385 pub fn contains(&self, ch: u32) -> bool {
386 (self.first..=self.last).contains(&ch)
387 }
388}
389
390const fn base_range(first: u32, last: u32, style_index: u16) -> StyleRange {
392 StyleRange {
393 first,
394 last,
395 style: GlyphStyle(style_index),
396 }
397}
398
399const fn non_base_range(first: u32, last: u32, style_index: u16) -> StyleRange {
400 StyleRange {
401 first,
402 last,
403 style: GlyphStyle(style_index | GlyphStyle::NON_BASE),
404 }
405}
406
407const MAX_STYLES: usize = STYLE_CLASSES.len();
408
409use super::shape::Shaper;
410
411include!("../../../generated/generated_autohint_styles.rs");
412
413#[cfg(test)]
414mod tests {
415 use super::{super::shape::ShaperMode, *};
416 use crate::{raw::TableProvider, FontRef, MetadataProvider};
417
418 #[test]
421 fn capture_digit_styles() {
422 let font = FontRef::new(font_test_data::AHEM).unwrap();
423 let shaper = Shaper::new(&font, ShaperMode::Nominal);
424 let num_glyphs = font.maxp().unwrap().num_glyphs() as u32;
425 let style_map = GlyphStyleMap::new(num_glyphs, &shaper);
426 let charmap = font.charmap();
427 let mut digit_count = 0;
428 for (ch, gid) in charmap.mappings() {
429 let style = style_map.style(gid).unwrap();
430 let is_char_digit = char::from_u32(ch).unwrap().is_ascii_digit();
431 assert_eq!(style.is_digit(), is_char_digit);
432 digit_count += is_char_digit as u32;
433 }
434 assert_eq!(digit_count, 10);
436 }
437
438 #[test]
439 fn glyph_styles() {
440 let expected = &[
444 (0, Some(("CJKV ideographs", false))),
445 (1, Some(("Latin", true))),
446 (2, Some(("Armenian", true))),
447 (3, Some(("Hebrew", true))),
448 (4, Some(("Arabic", false))),
449 (5, Some(("Arabic", false))),
450 (6, Some(("Arabic", true))),
451 (7, Some(("Devanagari", true))),
452 (8, Some(("Devanagari", false))),
453 (9, Some(("Bengali", true))),
454 (10, Some(("Bengali", false))),
455 (11, Some(("Gurmukhi", true))),
456 (12, Some(("Gurmukhi", false))),
457 (13, Some(("Gujarati", true))),
458 (14, Some(("Gujarati", true))),
459 (15, Some(("Oriya", true))),
460 (16, Some(("Oriya", false))),
461 (17, Some(("Tamil", true))),
462 (18, Some(("Tamil", false))),
463 (19, Some(("Telugu", true))),
464 (20, Some(("Telugu", false))),
465 (21, Some(("Kannada", true))),
466 (22, Some(("Kannada", false))),
467 (23, Some(("Malayalam", true))),
468 (24, Some(("Malayalam", false))),
469 (25, Some(("Sinhala", true))),
470 (26, Some(("Sinhala", false))),
471 (27, Some(("Thai", true))),
472 (28, Some(("Thai", false))),
473 (29, Some(("Lao", true))),
474 (30, Some(("Lao", false))),
475 (31, Some(("Tibetan", true))),
476 (32, Some(("Tibetan", false))),
477 (33, Some(("Myanmar", true))),
478 (34, Some(("Ethiopic", true))),
479 (35, Some(("Buhid", true))),
480 (36, Some(("Buhid", false))),
481 (37, Some(("Khmer", true))),
482 (38, Some(("Khmer", false))),
483 (39, Some(("Mongolian", true))),
484 (40, Some(("Canadian Syllabics", false))),
485 (41, Some(("Limbu", true))),
486 (42, Some(("Limbu", false))),
487 (43, Some(("Khmer Symbols", false))),
488 (44, Some(("Sundanese", true))),
489 (45, Some(("Ol Chiki", false))),
490 (46, Some(("Georgian (Mkhedruli)", false))),
491 (47, Some(("Sundanese", false))),
492 (48, Some(("Latin Superscript Fallback", false))),
493 (49, Some(("Latin", true))),
494 (50, Some(("Greek", true))),
495 (51, Some(("Greek", false))),
496 (52, Some(("Latin Subscript Fallback", false))),
497 (53, Some(("Coptic", true))),
498 (54, Some(("Coptic", false))),
499 (55, Some(("Georgian (Khutsuri)", false))),
500 (56, Some(("Tifinagh", false))),
501 (57, Some(("Ethiopic", false))),
502 (58, Some(("Cyrillic", true))),
503 (59, Some(("CJKV ideographs", true))),
504 (60, Some(("CJKV ideographs", false))),
505 (61, Some(("Lisu", false))),
506 (62, Some(("Vai", false))),
507 (63, Some(("Cyrillic", true))),
508 (64, Some(("Bamum", true))),
509 (65, Some(("Syloti Nagri", true))),
510 (66, Some(("Syloti Nagri", false))),
511 (67, Some(("Saurashtra", true))),
512 (68, Some(("Saurashtra", false))),
513 (69, Some(("Kayah Li", true))),
514 (70, Some(("Kayah Li", false))),
515 (71, Some(("Myanmar", false))),
516 (72, Some(("Tai Viet", true))),
517 (73, Some(("Tai Viet", false))),
518 (74, Some(("Cherokee", false))),
519 (75, Some(("Armenian", false))),
520 (76, Some(("Hebrew", false))),
521 (77, Some(("Arabic", false))),
522 (78, Some(("Carian", false))),
523 (79, Some(("Gothic", false))),
524 (80, Some(("Deseret", false))),
525 (81, Some(("Shavian", false))),
526 (82, Some(("Osmanya", false))),
527 (83, Some(("Osage", false))),
528 (84, Some(("Cypriot", false))),
529 (85, Some(("Avestan", true))),
530 (86, Some(("Avestan", true))),
531 (87, Some(("Old Turkic", false))),
532 (88, Some(("Hanifi Rohingya", false))),
533 (89, Some(("Chakma", true))),
534 (90, Some(("Chakma", false))),
535 (91, Some(("Mongolian", false))),
536 (92, Some(("CJKV ideographs", false))),
537 (93, Some(("Medefaidrin", false))),
538 (94, Some(("Glagolitic", true))),
539 (95, Some(("Glagolitic", true))),
540 (96, Some(("Adlam", true))),
541 (97, Some(("Adlam", false))),
542 ];
543 check_styles(font_test_data::AUTOHINT_CMAP, ShaperMode::Nominal, expected);
544 }
545
546 #[test]
547 fn shaped_glyph_styles() {
548 let expected = &[
552 (0, Some(("CJKV ideographs", false))),
553 (1, Some(("Latin", false))),
554 (2, Some(("Latin", false))),
555 (3, Some(("Latin", false))),
556 (4, Some(("Latin", false))),
557 (5, Some(("Cyrillic", false))),
560 (6, Some(("Cyrillic", false))),
561 (7, Some(("Cyrillic", false))),
562 (8, Some(("Latin small capitals from capitals", false))),
564 ];
565 check_styles(
566 font_test_data::NOTOSERIF_AUTOHINT_SHAPING,
567 ShaperMode::BestEffort,
568 expected,
569 );
570 }
571
572 fn check_styles(font_data: &[u8], mode: ShaperMode, expected: &[(u32, Option<(&str, bool)>)]) {
573 let font = FontRef::new(font_data).unwrap();
574 let shaper = Shaper::new(&font, mode);
575 let num_glyphs = font.maxp().unwrap().num_glyphs() as u32;
576 let style_map = GlyphStyleMap::new(num_glyphs, &shaper);
577 let results = style_map
578 .styles
579 .iter()
580 .enumerate()
581 .map(|(gid, style)| {
582 (
583 gid as u32,
584 style
585 .style_class()
586 .map(|style_class| (style_class.name, style.is_non_base())),
587 )
588 })
589 .collect::<Vec<_>>();
590 for (i, result) in results.iter().enumerate() {
591 assert_eq!(result, &expected[i]);
592 }
593 for style in &style_map.styles {
595 style_map.metrics_index(*style).unwrap();
596 }
597 }
598}