1use super::layout::{CoverageTable, DeviceOrVariationIndex};
4
5include!("../../generated/generated_math.rs");
6
7#[derive(Clone, Copy, PartialEq, Eq, Debug)]
9pub enum MathKernCorner {
10 TopRight,
12 TopLeft,
14 BottomRight,
16 BottomLeft,
18}
19
20#[derive(Clone, Copy, PartialEq, Eq, Debug)]
22pub enum StretchAxis {
23 Horizontal,
25 Vertical,
27}
28
29#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
37pub struct MathValue {
38 pub value: i32,
40 pub delta_px: i32,
44}
45
46#[derive(Clone, Copy, PartialEq, Eq, Debug)]
48pub struct MathKernEntry {
49 pub max_height: Option<MathValue>,
52 pub kern: MathValue,
54}
55
56impl Math<'_> {
57 pub fn has_swapped_min_heights(&self) -> bool {
76 const TABLE_LEN: usize = 25722;
81 const DELIMITED_SUB_FORMULA_MIN_HEIGHT: u16 = 3000;
82 const DISPLAY_OPERATOR_MIN_HEIGHT: u16 = 2500;
83
84 if self.offset_data().len() != TABLE_LEN {
85 return false;
86 }
87 let Ok(constants) = self.math_constants() else {
88 return false;
89 };
90 constants.delimited_sub_formula_min_height().to_u16() == DELIMITED_SUB_FORMULA_MIN_HEIGHT
91 && constants.display_operator_min_height().to_u16() == DISPLAY_OPERATOR_MIN_HEIGHT
92 }
93}
94
95impl MathValueRecord {
96 pub fn value_for_ppem(&self, data: FontData<'_>, ppem: u16) -> MathValue {
101 let delta_px = match self.device(data) {
104 Some(Ok(DeviceOrVariationIndex::Device(device))) => device.delta_for_ppem(ppem),
105 _ => 0,
106 };
107 MathValue {
108 value: self.value().to_i16() as i32,
109 delta_px,
110 }
111 }
112}
113
114macro_rules! math_constants {
124 ($($variant:ident = $discriminant:literal, $kind:ident $field:ident;)+) => {
125 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
132 #[repr(u8)]
133 pub enum MathConstant {
134 $(
135 #[doc = concat!("See [`MathConstants::", stringify!($field), "`].")]
136 $variant = $discriminant,
137 )+
138 }
139
140 impl MathConstant {
141 pub const ALL: &'static [MathConstant] = &[$(MathConstant::$variant,)+];
145
146 pub fn new(value: u8) -> Option<Self> {
149 Self::ALL.get(value as usize).copied()
150 }
151 }
152
153 impl MathConstants<'_> {
154 pub fn constant(&self, constant: MathConstant) -> i32 {
156 match constant {
157 $(
158 MathConstant::$variant => math_constants!(@value self, $kind $field),
159 )+
160 }
161 }
162
163 pub fn constant_for_ppem(&self, constant: MathConstant, ppem: u16) -> MathValue {
169 match constant {
170 $(
171 MathConstant::$variant => math_constants!(@for_ppem self, $kind $field, ppem),
172 )+
173 }
174 }
175 }
176 };
177 (@value $self:ident, record $field:ident) => {
178 $self.$field().value().to_i16() as i32
179 };
180 (@value $self:ident, int $field:ident) => {
181 $self.$field() as i32
182 };
183 (@value $self:ident, uint $field:ident) => {
184 $self.$field().to_u16() as i32
185 };
186 (@for_ppem $self:ident, record $field:ident, $ppem:ident) => {
187 $self.$field().value_for_ppem($self.offset_data(), $ppem)
188 };
189 (@for_ppem $self:ident, int $field:ident, $ppem:ident) => {
190 MathValue { value: $self.$field() as i32, delta_px: 0 }
191 };
192 (@for_ppem $self:ident, uint $field:ident, $ppem:ident) => {
193 MathValue { value: $self.$field().to_u16() as i32, delta_px: 0 }
194 };
195}
196
197math_constants! {
198 ScriptPercentScaleDown = 0, int script_percent_scale_down;
199 ScriptScriptPercentScaleDown = 1, int script_script_percent_scale_down;
200 DelimitedSubFormulaMinHeight = 2, uint delimited_sub_formula_min_height;
201 DisplayOperatorMinHeight = 3, uint display_operator_min_height;
202 MathLeading = 4, record math_leading;
203 AxisHeight = 5, record axis_height;
204 AccentBaseHeight = 6, record accent_base_height;
205 FlattenedAccentBaseHeight = 7, record flattened_accent_base_height;
206 SubscriptShiftDown = 8, record subscript_shift_down;
207 SubscriptTopMax = 9, record subscript_top_max;
208 SubscriptBaselineDropMin = 10, record subscript_baseline_drop_min;
209 SuperscriptShiftUp = 11, record superscript_shift_up;
210 SuperscriptShiftUpCramped = 12, record superscript_shift_up_cramped;
211 SuperscriptBottomMin = 13, record superscript_bottom_min;
212 SuperscriptBaselineDropMax = 14, record superscript_baseline_drop_max;
213 SubSuperscriptGapMin = 15, record sub_superscript_gap_min;
214 SuperscriptBottomMaxWithSubscript = 16, record superscript_bottom_max_with_subscript;
215 SpaceAfterScript = 17, record space_after_script;
216 UpperLimitGapMin = 18, record upper_limit_gap_min;
217 UpperLimitBaselineRiseMin = 19, record upper_limit_baseline_rise_min;
218 LowerLimitGapMin = 20, record lower_limit_gap_min;
219 LowerLimitBaselineDropMin = 21, record lower_limit_baseline_drop_min;
220 StackTopShiftUp = 22, record stack_top_shift_up;
221 StackTopDisplayStyleShiftUp = 23, record stack_top_display_style_shift_up;
222 StackBottomShiftDown = 24, record stack_bottom_shift_down;
223 StackBottomDisplayStyleShiftDown = 25, record stack_bottom_display_style_shift_down;
224 StackGapMin = 26, record stack_gap_min;
225 StackDisplayStyleGapMin = 27, record stack_display_style_gap_min;
226 StretchStackTopShiftUp = 28, record stretch_stack_top_shift_up;
227 StretchStackBottomShiftDown = 29, record stretch_stack_bottom_shift_down;
228 StretchStackGapAboveMin = 30, record stretch_stack_gap_above_min;
229 StretchStackGapBelowMin = 31, record stretch_stack_gap_below_min;
230 FractionNumeratorShiftUp = 32, record fraction_numerator_shift_up;
231 FractionNumeratorDisplayStyleShiftUp = 33, record fraction_numerator_display_style_shift_up;
232 FractionDenominatorShiftDown = 34, record fraction_denominator_shift_down;
233 FractionDenominatorDisplayStyleShiftDown = 35, record fraction_denominator_display_style_shift_down;
234 FractionNumeratorGapMin = 36, record fraction_numerator_gap_min;
235 FractionNumDisplayStyleGapMin = 37, record fraction_num_display_style_gap_min;
236 FractionRuleThickness = 38, record fraction_rule_thickness;
237 FractionDenominatorGapMin = 39, record fraction_denominator_gap_min;
238 FractionDenomDisplayStyleGapMin = 40, record fraction_denom_display_style_gap_min;
239 SkewedFractionHorizontalGap = 41, record skewed_fraction_horizontal_gap;
240 SkewedFractionVerticalGap = 42, record skewed_fraction_vertical_gap;
241 OverbarVerticalGap = 43, record overbar_vertical_gap;
242 OverbarRuleThickness = 44, record overbar_rule_thickness;
243 OverbarExtraAscender = 45, record overbar_extra_ascender;
244 UnderbarVerticalGap = 46, record underbar_vertical_gap;
245 UnderbarRuleThickness = 47, record underbar_rule_thickness;
246 UnderbarExtraDescender = 48, record underbar_extra_descender;
247 RadicalVerticalGap = 49, record radical_vertical_gap;
248 RadicalDisplayStyleVerticalGap = 50, record radical_display_style_vertical_gap;
249 RadicalRuleThickness = 51, record radical_rule_thickness;
250 RadicalExtraAscender = 52, record radical_extra_ascender;
251 RadicalKernBeforeDegree = 53, record radical_kern_before_degree;
252 RadicalKernAfterDegree = 54, record radical_kern_after_degree;
253 RadicalDegreeBottomRaisePercent = 55, int radical_degree_bottom_raise_percent;
254}
255
256impl MathGlyphInfo<'_> {
257 pub fn is_extended_shape(&self, glyph: GlyphId) -> bool {
260 self.extended_shape_coverage()
261 .and_then(|coverage| coverage.ok())
262 .and_then(|coverage| coverage.get(glyph))
263 .is_some()
264 }
265}
266
267impl MathItalicsCorrectionInfo<'_> {
268 pub fn correction(&self, glyph: GlyphId) -> Option<i32> {
271 Some(self.record(glyph)?.value().to_i16() as i32)
272 }
273
274 pub fn correction_for_ppem(&self, glyph: GlyphId, ppem: u16) -> Option<MathValue> {
277 Some(self.record(glyph)?.value_for_ppem(self.offset_data(), ppem))
278 }
279
280 fn record(&self, glyph: GlyphId) -> Option<&MathValueRecord> {
281 let index = self.coverage().ok()?.get(glyph)?;
282 self.italics_correction().get(index as usize)
283 }
284}
285
286impl MathTopAccentAttachment<'_> {
287 pub fn attachment(&self, glyph: GlyphId) -> Option<i32> {
290 Some(self.record(glyph)?.value().to_i16() as i32)
291 }
292
293 pub fn attachment_for_ppem(&self, glyph: GlyphId, ppem: u16) -> Option<MathValue> {
296 Some(self.record(glyph)?.value_for_ppem(self.offset_data(), ppem))
297 }
298
299 fn record(&self, glyph: GlyphId) -> Option<&MathValueRecord> {
300 let index = self.top_accent_coverage().ok()?.get(glyph)?;
301 self.top_accent_attachment().get(index as usize)
302 }
303}
304
305impl<'a> MathKernInfo<'a> {
306 pub fn kern(&self, glyph: GlyphId, corner: MathKernCorner) -> Option<MathKern<'a>> {
309 let index = self.math_kern_coverage().ok()?.get(glyph)?;
310 let record = self.math_kern_info_records().get(index as usize)?;
311 let data = self.offset_data();
312 match corner {
313 MathKernCorner::TopRight => record.top_right_math_kern(data),
314 MathKernCorner::TopLeft => record.top_left_math_kern(data),
315 MathKernCorner::BottomRight => record.bottom_right_math_kern(data),
316 MathKernCorner::BottomLeft => record.bottom_left_math_kern(data),
317 }?
318 .ok()
319 }
320}
321
322impl MathKern<'_> {
323 pub fn kerning(&self, correction_height: i32) -> Option<i32> {
339 let value = self.kern_values().get(self.band(correction_height))?;
340 Some(value.value().to_i16() as i32)
341 }
342
343 pub fn entries_for_ppem(&self, ppem: u16) -> impl Iterator<Item = MathKernEntry> + '_ {
353 let data = self.offset_data();
354 let heights = self.correction_height();
355 self.kern_values()
356 .iter()
357 .enumerate()
358 .map(move |(i, kern)| MathKernEntry {
359 max_height: heights
360 .get(i)
361 .map(|height| height.value_for_ppem(data, ppem)),
362 kern: kern.value_for_ppem(data, ppem),
363 })
364 }
365
366 fn band(&self, correction_height: i32) -> usize {
368 let heights = self.correction_height();
369 heights
370 .iter()
371 .position(|height| correction_height < height.value().to_i16() as i32)
372 .unwrap_or(heights.len())
373 }
374}
375
376impl<'a> MathVariants<'a> {
377 pub fn glyph_construction(
380 &self,
381 glyph: GlyphId,
382 axis: StretchAxis,
383 ) -> Option<MathGlyphConstruction<'a>> {
384 let index = match axis {
385 StretchAxis::Vertical => self.vert_glyph_coverage(),
386 StretchAxis::Horizontal => self.horiz_glyph_coverage(),
387 }?
388 .ok()?
389 .get(glyph)?;
390 match axis {
391 StretchAxis::Vertical => self.vert_glyph_constructions(),
392 StretchAxis::Horizontal => self.horiz_glyph_constructions(),
393 }
394 .get(index as usize)
395 .ok()
396 }
397}
398
399impl GlyphAssembly<'_> {
400 pub fn italics_correction_for_ppem(&self, ppem: u16) -> MathValue {
402 self.italics_correction()
403 .value_for_ppem(self.offset_data(), ppem)
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410 use font_test_data::bebuffer::BeBuffer;
411
412 const MATH_LEADING_INDEX: usize = 0;
417
418 const AXIS_HEIGHT_INDEX: usize = 1;
422
423 const VALUE_RECORD_ORDER: &[&str] = &[
429 "math_leading",
430 "axis_height",
431 "accent_base_height",
432 "flattened_accent_base_height",
433 "subscript_shift_down",
434 "subscript_top_max",
435 "subscript_baseline_drop_min",
436 "superscript_shift_up",
437 "superscript_shift_up_cramped",
438 "superscript_bottom_min",
439 "superscript_baseline_drop_max",
440 "sub_superscript_gap_min",
441 "superscript_bottom_max_with_subscript",
442 "space_after_script",
443 "upper_limit_gap_min",
444 "upper_limit_baseline_rise_min",
445 "lower_limit_gap_min",
446 "lower_limit_baseline_drop_min",
447 "stack_top_shift_up",
448 "stack_top_display_style_shift_up",
449 "stack_bottom_shift_down",
450 "stack_bottom_display_style_shift_down",
451 "stack_gap_min",
452 "stack_display_style_gap_min",
453 "stretch_stack_top_shift_up",
454 "stretch_stack_bottom_shift_down",
455 "stretch_stack_gap_above_min",
456 "stretch_stack_gap_below_min",
457 "fraction_numerator_shift_up",
458 "fraction_numerator_display_style_shift_up",
459 "fraction_denominator_shift_down",
460 "fraction_denominator_display_style_shift_down",
461 "fraction_numerator_gap_min",
462 "fraction_num_display_style_gap_min",
463 "fraction_rule_thickness",
464 "fraction_denominator_gap_min",
465 "fraction_denom_display_style_gap_min",
466 "skewed_fraction_horizontal_gap",
467 "skewed_fraction_vertical_gap",
468 "overbar_vertical_gap",
469 "overbar_rule_thickness",
470 "overbar_extra_ascender",
471 "underbar_vertical_gap",
472 "underbar_rule_thickness",
473 "underbar_extra_descender",
474 "radical_vertical_gap",
475 "radical_display_style_vertical_gap",
476 "radical_rule_thickness",
477 "radical_extra_ascender",
478 "radical_kern_before_degree",
479 "radical_kern_after_degree",
480 ];
481
482 fn math_table() -> BeBuffer {
485 let mut buf = BeBuffer::new()
486 .push(MajorMinor::VERSION_1_0)
488 .push_with_tag(0u16, "constants_offset")
489 .push_with_tag(0u16, "glyph_info_offset")
490 .push_with_tag(0u16, "variants_offset");
491
492 let constants = buf.len();
494 buf = buf
495 .push(1i16) .push(2i16) .push_with_tag(3u16, "delimited_sub_formula_min_height")
498 .push_with_tag(4u16, "display_operator_min_height");
499 for i in 0..VALUE_RECORD_ORDER.len() {
500 buf = buf.push(100i16 + i as i16); buf = match i {
502 MATH_LEADING_INDEX => buf.push_with_tag(0u16, "math_leading_device"),
503 AXIS_HEIGHT_INDEX => buf.push_with_tag(0u16, "axis_height_device"),
504 _ => buf.push(0u16), };
506 }
507 buf = buf.push(5i16); let glyph_info = buf.len();
511 buf = buf
512 .push_with_tag(0u16, "italics_offset")
513 .push(0u16) .push(0u16) .push_with_tag(0u16, "kern_info_offset");
516
517 let italics = buf.len();
519 buf = buf
520 .push_with_tag(0u16, "italics_coverage_offset")
521 .push(1u16) .push(37i16) .push_with_tag(0u16, "italics_device_offset");
524 let italics_coverage = buf.len();
525 buf = buf
526 .push(1u16) .push(1u16) .push(9u16); let italics_device = buf.len();
530 buf = buf
531 .push(11u16) .push(12u16) .push(3u16) .push(0x05FFu16); let kern_info = buf.len();
538 buf = buf
539 .push_with_tag(0u16, "kern_coverage_offset")
540 .push(1u16) .push_with_tag(0u16, "top_right_kern_offset")
542 .push(0u16) .push(0u16) .push(0u16); let kern_coverage = buf.len();
546 buf = buf.push(1u16).push(1u16).push(9u16);
547 let math_kern = buf.len();
548 buf = buf
549 .push(2u16) .push(20i16) .push(0u16)
552 .push(40i16) .push_with_tag(0u16, "kern_height_device")
554 .push(-1i16) .push(0u16)
556 .push(-2i16) .push_with_tag(0u16, "kern_value_device")
558 .push(-3i16) .push(0u16);
560
561 let variants = buf.len();
563 buf = buf
564 .push(6u16) .push_with_tag(0u16, "vert_coverage_offset")
566 .push(0u16) .push(1u16) .push(0u16) .push_with_tag(0u16, "vert_construction_offset");
570 let vert_coverage = buf.len();
571 buf = buf.push(1u16).push(1u16).push(9u16);
572 let construction = buf.len();
573 buf = buf
574 .push_with_tag(0u16, "assembly_offset")
575 .push(2u16) .push(21u16) .push(300u16) .push(22u16)
579 .push(400u16);
580 let assembly = buf.len();
581 buf = buf
582 .push(17i16) .push(0u16) .push(1u16) .push(23u16) .push(10u16) .push(11u16) .push(50u16) .push(1u16); let kern_height_device = buf.len();
594 buf = buf
595 .push(10u16) .push(11u16) .push(3u16) .push(0x04FAu16); let kern_value_device = buf.len();
600 buf = buf
601 .push(10u16) .push(11u16) .push(3u16) .push(0x0102u16); let axis_height_device = buf.len();
608 buf = buf
609 .push(10u16) .push(12u16) .push(3u16) .push(0xFE03u16) .push(0x0700u16); let math_leading_varidx = buf.len();
619 buf = buf
620 .push(0u16) .push(0u16) .push(0x8000u16); buf.write_at("constants_offset", constants as u16);
625 buf.write_at("glyph_info_offset", glyph_info as u16);
626 buf.write_at("variants_offset", variants as u16);
627 buf.write_at("italics_offset", (italics - glyph_info) as u16);
628 buf.write_at("kern_info_offset", (kern_info - glyph_info) as u16);
629 buf.write_at(
630 "italics_coverage_offset",
631 (italics_coverage - italics) as u16,
632 );
633 buf.write_at("italics_device_offset", (italics_device - italics) as u16);
634 buf.write_at("kern_coverage_offset", (kern_coverage - kern_info) as u16);
635 buf.write_at("top_right_kern_offset", (math_kern - kern_info) as u16);
636 buf.write_at(
638 "kern_height_device",
639 (kern_height_device - math_kern) as u16,
640 );
641 buf.write_at("kern_value_device", (kern_value_device - math_kern) as u16);
642 buf.write_at("vert_coverage_offset", (vert_coverage - variants) as u16);
643 buf.write_at("vert_construction_offset", (construction - variants) as u16);
644 buf.write_at("assembly_offset", (assembly - construction) as u16);
645 buf.write_at(
647 "axis_height_device",
648 (axis_height_device - constants) as u16,
649 );
650 buf.write_at(
651 "math_leading_device",
652 (math_leading_varidx - constants) as u16,
653 );
654 buf
655 }
656
657 fn plain(value: i32) -> MathValue {
659 MathValue { value, delta_px: 0 }
660 }
661
662 fn adjusted(value: i32, delta_px: i32) -> MathValue {
664 MathValue { value, delta_px }
665 }
666
667 fn entry(max_height: Option<MathValue>, kern: MathValue) -> MathKernEntry {
669 MathKernEntry { max_height, kern }
670 }
671
672 const CAMBRIA_TABLE_LEN: usize = 25722;
674
675 fn cambria_table(len: usize, delimited: u16, display_operator: u16) -> BeBuffer {
677 let mut buf = math_table();
678 buf.write_at("delimited_sub_formula_min_height", delimited);
679 buf.write_at("display_operator_min_height", display_operator);
680 let padding = len - buf.len();
681 buf.extend(vec![0u8; padding])
682 }
683
684 fn math(buf: &BeBuffer) -> Math<'_> {
685 Math::read(buf.data().into()).unwrap()
686 }
687
688 #[test]
689 fn constants_are_in_spec_order() {
690 let buf = math_table();
691 let math = math(&buf);
692 let constants = math.math_constants().unwrap();
693
694 assert_eq!(constants.script_percent_scale_down(), 1);
695 assert_eq!(constants.script_script_percent_scale_down(), 2);
696 assert_eq!(constants.delimited_sub_formula_min_height(), UfWord::new(3));
697 assert_eq!(constants.display_operator_min_height(), UfWord::new(4));
698 assert_eq!(constants.radical_degree_bottom_raise_percent(), 5);
699
700 let values = [
703 constants.math_leading(),
704 constants.axis_height(),
705 constants.accent_base_height(),
706 constants.flattened_accent_base_height(),
707 constants.subscript_shift_down(),
708 constants.subscript_top_max(),
709 constants.subscript_baseline_drop_min(),
710 constants.superscript_shift_up(),
711 constants.superscript_shift_up_cramped(),
712 constants.superscript_bottom_min(),
713 constants.superscript_baseline_drop_max(),
714 constants.sub_superscript_gap_min(),
715 constants.superscript_bottom_max_with_subscript(),
716 constants.space_after_script(),
717 constants.upper_limit_gap_min(),
718 constants.upper_limit_baseline_rise_min(),
719 constants.lower_limit_gap_min(),
720 constants.lower_limit_baseline_drop_min(),
721 constants.stack_top_shift_up(),
722 constants.stack_top_display_style_shift_up(),
723 constants.stack_bottom_shift_down(),
724 constants.stack_bottom_display_style_shift_down(),
725 constants.stack_gap_min(),
726 constants.stack_display_style_gap_min(),
727 constants.stretch_stack_top_shift_up(),
728 constants.stretch_stack_bottom_shift_down(),
729 constants.stretch_stack_gap_above_min(),
730 constants.stretch_stack_gap_below_min(),
731 constants.fraction_numerator_shift_up(),
732 constants.fraction_numerator_display_style_shift_up(),
733 constants.fraction_denominator_shift_down(),
734 constants.fraction_denominator_display_style_shift_down(),
735 constants.fraction_numerator_gap_min(),
736 constants.fraction_num_display_style_gap_min(),
737 constants.fraction_rule_thickness(),
738 constants.fraction_denominator_gap_min(),
739 constants.fraction_denom_display_style_gap_min(),
740 constants.skewed_fraction_horizontal_gap(),
741 constants.skewed_fraction_vertical_gap(),
742 constants.overbar_vertical_gap(),
743 constants.overbar_rule_thickness(),
744 constants.overbar_extra_ascender(),
745 constants.underbar_vertical_gap(),
746 constants.underbar_rule_thickness(),
747 constants.underbar_extra_descender(),
748 constants.radical_vertical_gap(),
749 constants.radical_display_style_vertical_gap(),
750 constants.radical_rule_thickness(),
751 constants.radical_extra_ascender(),
752 constants.radical_kern_before_degree(),
753 constants.radical_kern_after_degree(),
754 ];
755 assert_eq!(values.len(), VALUE_RECORD_ORDER.len());
756 for (i, (value, name)) in values.iter().zip(VALUE_RECORD_ORDER).enumerate() {
757 assert_eq!(
758 value.value(),
759 FWord::new(100 + i as i16),
760 "{name} is not at spec index {i}"
761 );
762 }
763 }
764
765 #[test]
766 fn cambria_math_is_recognized_by_all_three_values() {
767 let buf = cambria_table(CAMBRIA_TABLE_LEN, 3000, 2500);
769 assert!(math(&buf).has_swapped_min_heights());
770
771 let buf = cambria_table(CAMBRIA_TABLE_LEN + 1, 3000, 2500);
773 assert!(!math(&buf).has_swapped_min_heights());
774
775 let buf = cambria_table(CAMBRIA_TABLE_LEN, 2500, 3000);
777 assert!(!math(&buf).has_swapped_min_heights());
778
779 let buf = math_table();
781 assert!(!math(&buf).has_swapped_min_heights());
782 }
783
784 #[test]
785 fn constant_numbering_matches_harfbuzz() {
786 assert_eq!(MathConstant::ALL.len(), 56);
789 for (i, constant) in MathConstant::ALL.iter().enumerate() {
790 assert_eq!(*constant as usize, i);
791 assert_eq!(MathConstant::new(i as u8), Some(*constant));
792 }
793 assert_eq!(MathConstant::ScriptPercentScaleDown as u8, 0);
794 assert_eq!(MathConstant::MathLeading as u8, 4);
795 assert_eq!(MathConstant::AxisHeight as u8, 5);
796 assert_eq!(MathConstant::RadicalKernAfterDegree as u8, 54);
797 assert_eq!(MathConstant::RadicalDegreeBottomRaisePercent as u8, 55);
798 assert_eq!(MathConstant::new(56), None);
799 }
800
801 #[test]
802 fn every_constant_reads_its_own_field() {
803 let buf = math_table();
804 let math = math(&buf);
805 let constants = math.math_constants().unwrap();
806 let first_record = MathConstant::MathLeading as i32;
807
808 for constant in MathConstant::ALL {
811 let expected = match constant {
812 MathConstant::ScriptPercentScaleDown => 1,
813 MathConstant::ScriptScriptPercentScaleDown => 2,
814 MathConstant::DelimitedSubFormulaMinHeight => 3,
815 MathConstant::DisplayOperatorMinHeight => 4,
816 MathConstant::RadicalDegreeBottomRaisePercent => 5,
817 record => 100 + (*record as i32 - first_record),
818 };
819 assert_eq!(
820 constants.constant(*constant),
821 expected,
822 "{constant:?} does not read its own field"
823 );
824 assert_eq!(constants.constant_for_ppem(*constant, 12).value, expected);
827 }
828 }
829
830 #[test]
831 fn constant_device_deltas_track_ppem() {
832 let buf = math_table();
833 let math = math(&buf);
834 let constants = math.math_constants().unwrap();
835 let stored = 100 + AXIS_HEIGHT_INDEX as i32;
836
837 assert_eq!(
839 constants.constant_for_ppem(MathConstant::AxisHeight, 10),
840 adjusted(stored, -2)
841 );
842 assert_eq!(
843 constants.constant_for_ppem(MathConstant::AxisHeight, 11),
844 adjusted(stored, 3)
845 );
846 assert_eq!(
847 constants.constant_for_ppem(MathConstant::AxisHeight, 12),
848 adjusted(stored, 7)
849 );
850 assert_eq!(
852 constants.constant_for_ppem(MathConstant::AxisHeight, 9),
853 plain(stored)
854 );
855 assert_eq!(
856 constants.constant_for_ppem(MathConstant::AxisHeight, 13),
857 plain(stored)
858 );
859 assert_eq!(
860 constants.constant_for_ppem(MathConstant::AxisHeight, 0),
861 plain(stored)
862 );
863 }
864
865 #[test]
866 fn variation_index_yields_no_adjustment() {
867 let buf = math_table();
868 let math = math(&buf);
869 let constants = math.math_constants().unwrap();
870 let stored = 100 + MATH_LEADING_INDEX as i32;
871
872 assert_eq!(constants.constant(MathConstant::MathLeading), stored);
876 for ppem in [0u16, 10, 11, 12, 100] {
877 assert_eq!(
878 constants.constant_for_ppem(MathConstant::MathLeading, ppem),
879 plain(stored)
880 );
881 }
882 }
883
884 #[test]
885 fn italics_correction_resolves_against_its_parent() {
886 let buf = math_table();
887 let math = math(&buf);
888 let info = math
889 .math_glyph_info()
890 .unwrap()
891 .math_italics_correction_info()
892 .unwrap()
893 .unwrap();
894
895 assert_eq!(info.coverage().unwrap().get(GlyphId::new(9)), Some(0));
896 assert_eq!(info.correction(GlyphId::new(9)), Some(37));
897 assert_eq!(info.correction(GlyphId::new(10)), None);
899
900 assert_eq!(
903 info.correction_for_ppem(GlyphId::new(9), 11),
904 Some(adjusted(37, 5))
905 );
906 assert_eq!(
907 info.correction_for_ppem(GlyphId::new(9), 12),
908 Some(adjusted(37, -1))
909 );
910 assert_eq!(
911 info.correction_for_ppem(GlyphId::new(9), 20),
912 Some(plain(37))
913 );
914 }
915
916 #[test]
917 fn glyph_info_lookups() {
918 let buf = math_table();
919 let math = math(&buf);
920 let glyph_info = math.math_glyph_info().unwrap();
921
922 assert!(glyph_info.math_top_accent_attachment().is_none());
924 assert!(!glyph_info.is_extended_shape(GlyphId::new(9)));
925 }
926
927 #[test]
928 fn math_kern_has_one_more_value_than_height() {
929 let buf = math_table();
930 let math = math(&buf);
931 let kern_info = math
932 .math_glyph_info()
933 .unwrap()
934 .math_kern_info()
935 .unwrap()
936 .unwrap();
937
938 let kern = kern_info
939 .kern(GlyphId::new(9), MathKernCorner::TopRight)
940 .unwrap();
941
942 assert_eq!(kern.height_count(), 2);
943 assert_eq!(
944 kern.correction_height()
945 .iter()
946 .map(|v| v.value())
947 .collect::<Vec<_>>(),
948 vec![FWord::new(20), FWord::new(40)]
949 );
950 assert_eq!(
952 kern.kern_values()
953 .iter()
954 .map(|v| v.value())
955 .collect::<Vec<_>>(),
956 vec![FWord::new(-1), FWord::new(-2), FWord::new(-3)]
957 );
958
959 assert!(kern_info
961 .kern(GlyphId::new(9), MathKernCorner::TopLeft)
962 .is_none());
963 assert!(kern_info
965 .kern(GlyphId::new(10), MathKernCorner::TopRight)
966 .is_none());
967 }
968
969 #[test]
970 fn kerning_picks_the_band_the_height_falls_in() {
971 let buf = math_table();
972 let math = math(&buf);
973 let kern = math
974 .math_glyph_info()
975 .unwrap()
976 .math_kern_info()
977 .unwrap()
978 .unwrap()
979 .kern(GlyphId::new(9), MathKernCorner::TopRight)
980 .unwrap();
981
982 assert_eq!(kern.kerning(0), Some(-1));
984 assert_eq!(kern.kerning(19), Some(-1));
985 assert_eq!(kern.kerning(20), Some(-2));
987 assert_eq!(kern.kerning(39), Some(-2));
988 assert_eq!(kern.kerning(40), Some(-3));
989 assert_eq!(kern.kerning(1000), Some(-3));
990 }
991
992 #[test]
993 fn kern_entries_carry_their_own_adjustments() {
994 let buf = math_table();
995 let math = math(&buf);
996 let kern = math
997 .math_glyph_info()
998 .unwrap()
999 .math_kern_info()
1000 .unwrap()
1001 .unwrap()
1002 .kern(GlyphId::new(9), MathKernCorner::TopRight)
1003 .unwrap();
1004
1005 assert_eq!(
1009 kern.entries_for_ppem(10).collect::<Vec<_>>(),
1010 vec![
1011 entry(Some(plain(20)), plain(-1)),
1012 entry(Some(adjusted(40, 4)), adjusted(-2, 1)),
1013 entry(None, plain(-3)),
1014 ]
1015 );
1016 assert_eq!(
1017 kern.entries_for_ppem(11).collect::<Vec<_>>(),
1018 vec![
1019 entry(Some(plain(20)), plain(-1)),
1020 entry(Some(adjusted(40, -6)), adjusted(-2, 2)),
1021 entry(None, plain(-3)),
1022 ]
1023 );
1024 assert_eq!(
1026 kern.entries_for_ppem(12).collect::<Vec<_>>(),
1027 vec![
1028 entry(Some(plain(20)), plain(-1)),
1029 entry(Some(plain(40)), plain(-2)),
1030 entry(None, plain(-3)),
1031 ]
1032 );
1033
1034 assert_eq!(kern.kerning(40), Some(-3));
1036 }
1037
1038 #[test]
1039 fn variants_and_assembly() {
1040 let buf = math_table();
1041 let math = math(&buf);
1042 let variants = math.math_variants().unwrap();
1043
1044 assert_eq!(variants.min_connector_overlap(), UfWord::new(6));
1045
1046 let construction = variants
1047 .glyph_construction(GlyphId::new(9), StretchAxis::Vertical)
1048 .unwrap();
1049 assert_eq!(
1050 construction
1051 .math_glyph_variant_records()
1052 .iter()
1053 .map(|r| (r.variant_glyph(), r.advance_measurement()))
1054 .collect::<Vec<_>>(),
1055 vec![
1056 (GlyphId16::new(21), UfWord::new(300)),
1057 (GlyphId16::new(22), UfWord::new(400)),
1058 ]
1059 );
1060
1061 assert!(variants
1063 .glyph_construction(GlyphId::new(9), StretchAxis::Horizontal)
1064 .is_none());
1065
1066 let assembly = construction.glyph_assembly().unwrap().unwrap();
1067 assert_eq!(assembly.italics_correction().value(), FWord::new(17));
1068 assert_eq!(assembly.italics_correction_for_ppem(12), plain(17));
1070
1071 let part = &assembly.part_records()[0];
1072 assert_eq!(part.glyph_id(), GlyphId16::new(23));
1073 assert_eq!(part.start_connector_length(), UfWord::new(10));
1074 assert_eq!(part.end_connector_length(), UfWord::new(11));
1075 assert_eq!(part.full_advance(), UfWord::new(50));
1076 assert!(part.part_flags().contains(PartFlags::EXTENDER_FLAG));
1077 }
1078}