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