1#![forbid(unsafe_code)]
3
4use std::fmt;
5use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
6
7#[derive(Clone, Debug, Default, PartialEq)]
9pub struct Fragment {
10 pub root: NodeId,
12 pub nodes: Vec<LayoutNode>,
14 pub source_map: SourceMap,
16 pub metadata: FragmentMetadata,
18 pub surface: Surface,
20}
21
22impl Fragment {
23 pub fn new(
25 root: NodeId,
26 nodes: Vec<LayoutNode>,
27 source_map: SourceMap,
28 metadata: FragmentMetadata,
29 ) -> Result<Self, FragmentError> {
30 let surface = nodes
31 .get(root.index())
32 .map_or_else(Surface::default, Surface::of_root);
33 let fragment = Self {
34 root,
35 nodes,
36 source_map,
37 metadata,
38 surface,
39 };
40 fragment.validate()?;
41 Ok(fragment)
42 }
43
44 pub fn validate(&self) -> Result<(), FragmentError> {
46 for (index, node) in self.nodes.iter().enumerate() {
47 if node.id.index() != index {
48 return Err(FragmentError::IdMismatch { index, id: node.id });
49 }
50 }
51 if self.nodes.is_empty() {
52 return Ok(());
53 }
54 let root = self
55 .nodes
56 .get(self.root.index())
57 .ok_or(FragmentError::RootOutOfRange(self.root))?;
58 if root.origin != Point::ORIGIN {
59 return Err(FragmentError::RootNotAtOrigin);
60 }
61 if self.surface != Surface::of_root(root) {
62 return Err(FragmentError::SurfaceMismatch);
63 }
64 let mut has_parent = vec![false; self.nodes.len()];
65 for node in &self.nodes {
66 for &child in node.children() {
67 let slot =
68 has_parent
69 .get_mut(child.index())
70 .ok_or(FragmentError::ChildOutOfRange {
71 parent: node.id,
72 child,
73 })?;
74 if *slot || child == self.root {
75 return Err(FragmentError::MultipleParents(child));
76 }
77 *slot = true;
78 }
79 }
80 let mut reached = vec![false; self.nodes.len()];
81 let mut stack = vec![self.root];
82 while let Some(id) = stack.pop() {
83 if std::mem::replace(&mut reached[id.index()], true) {
84 return Err(FragmentError::MultipleParents(id));
85 }
86 stack.extend_from_slice(self.nodes[id.index()].children());
87 }
88 if let Some(index) = reached.iter().position(|reached| !reached) {
89 return Err(FragmentError::Unreachable(self.nodes[index].id));
90 }
91 for (index, file) in self.source_map.sources.iter().enumerate() {
92 if file.id.index() != index {
93 return Err(FragmentError::SourceIdMismatch { index, id: file.id });
94 }
95 }
96 let known = |range: &SourceRange| self.source_map.source(range.source).is_some();
97 for node in &self.nodes {
98 if let Some(range) = &node.primary_source {
99 if !known(range) {
100 return Err(FragmentError::UnknownSource {
101 node: node.id,
102 source: range.source,
103 });
104 }
105 }
106 }
107 for entry in &self.source_map.entries {
108 if entry.node.index() >= self.nodes.len() {
109 return Err(FragmentError::Unreachable(entry.node));
110 }
111 if !known(&entry.range) {
112 return Err(FragmentError::UnknownSource {
113 node: entry.node,
114 source: entry.range.source,
115 });
116 }
117 }
118 Ok(())
119 }
120
121 #[must_use]
123 pub fn node(&self, id: NodeId) -> Option<&LayoutNode> {
124 self.nodes.get(id.index()).filter(|node| node.id == id)
125 }
126
127 #[must_use]
129 pub fn root_node(&self) -> Option<&LayoutNode> {
130 self.node(self.root)
131 }
132
133 #[must_use]
135 pub fn children(&self, id: NodeId) -> &[NodeId] {
136 self.node(id).map_or(&[], LayoutNode::children)
137 }
138
139 pub fn source_entries_for_node(&self, node: NodeId) -> impl Iterator<Item = &SourceMapEntry> {
141 self.source_map.entries_for_node(node)
142 }
143
144 pub fn source_origins_for_node(&self, node: NodeId) -> impl Iterator<Item = SourceOrigin<'_>> {
146 let primary = self
147 .primary_source_for_node(node)
148 .and_then(|range| self.origin(node, range, SourceRole::Primary));
149 let enclosing = self.source_entries_for_node(node).filter_map(move |entry| {
150 self.origin(node, entry.range, SourceRole::EnclosingConstruct)
151 });
152 primary.into_iter().chain(enclosing)
153 }
154
155 #[must_use]
157 pub fn primary_source_for_node(&self, node: NodeId) -> Option<SourceRange> {
158 self.node(node)?.primary_source
159 }
160
161 #[must_use]
163 pub fn glyph_source_range(&self, node: NodeId, glyph_index: usize) -> Option<SourceRange> {
164 let node = self.node(node)?;
165 let LayoutNodeKind::GlyphRun(run) = &node.kind else {
166 return None;
167 };
168 let span = run.glyphs.get(glyph_index)?.cluster?;
169 Some(SourceRange {
170 source: node.primary_source?.source,
171 span,
172 })
173 }
174
175 #[must_use]
177 pub fn glyph_source_origin(
178 &self,
179 node: NodeId,
180 glyph_index: usize,
181 ) -> Option<SourceOrigin<'_>> {
182 let range = self.glyph_source_range(node, glyph_index)?;
183 self.origin(node, range, SourceRole::Primary)
184 }
185
186 #[must_use]
188 pub fn flatten(&self) -> Vec<Placed<'_>> {
189 let mut out = Vec::new();
190 if self.root_node().is_none() {
191 return out;
192 }
193 let mut visited = vec![false; self.nodes.len()];
194 let mut stack = vec![(self.root, Point::new(Length::ZERO, self.surface.baseline))];
195 while let Some((id, parent)) = stack.pop() {
196 let Some(node) = self.node(id) else {
197 continue;
198 };
199 if std::mem::replace(&mut visited[id.index()], true) {
200 continue;
201 }
202 let at = parent + node.origin;
203 match &node.kind {
204 LayoutNodeKind::Box(layout_box) => {
205 stack.extend(layout_box.children.iter().rev().map(|&child| (child, at)));
206 }
207 LayoutNodeKind::GlyphRun(run) => {
208 for (index, glyph) in run.glyphs.iter().enumerate() {
209 let source = self.glyph_source_range(id, index).or(node.primary_source);
210 out.push(Placed::Glyph {
211 node: id,
212 font: &run.font,
213 glyph_id: glyph.glyph_id,
214 x: at.x + glyph.offset.x,
215 y: at.y + glyph.offset.y,
216 source,
217 });
218 }
219 }
220 LayoutNodeKind::Rule => {
221 let height = node.height + node.depth;
222 if node.width > Length::ZERO && height > Length::ZERO {
224 out.push(Placed::Rule {
225 node: id,
226 x: at.x,
227 y: at.y - node.height,
228 width: node.width,
229 height,
230 source: node.primary_source,
231 });
232 }
233 }
234 LayoutNodeKind::Glue(_) | LayoutNodeKind::Kern(_) => {}
235 }
236 }
237 out
238 }
239
240 fn origin(
241 &self,
242 node: NodeId,
243 range: SourceRange,
244 role: SourceRole,
245 ) -> Option<SourceOrigin<'_>> {
246 Some(SourceOrigin {
247 node,
248 source: self.source_map.source(range.source)?,
249 span: range.span,
250 role,
251 })
252 }
253}
254
255#[derive(Clone, Copy, Debug, PartialEq)]
257pub enum Placed<'a> {
258 Glyph {
260 node: NodeId,
262 font: &'a FontRef,
264 glyph_id: GlyphId,
266 x: Length,
268 y: Length,
270 source: Option<SourceRange>,
272 },
273 Rule {
275 node: NodeId,
277 x: Length,
279 y: Length,
281 width: Length,
283 height: Length,
285 source: Option<SourceRange>,
287 },
288}
289
290#[derive(Clone, Copy, Debug, PartialEq, Eq)]
292#[non_exhaustive]
293pub enum FragmentError {
294 IdMismatch {
296 index: usize,
298 id: NodeId,
300 },
301 RootOutOfRange(NodeId),
303 RootNotAtOrigin,
305 SurfaceMismatch,
307 ChildOutOfRange {
309 parent: NodeId,
311 child: NodeId,
313 },
314 MultipleParents(NodeId),
316 Unreachable(NodeId),
318 SourceIdMismatch {
320 index: usize,
322 id: SourceId,
324 },
325 UnknownSource {
327 node: NodeId,
329 source: SourceId,
331 },
332}
333
334impl fmt::Display for FragmentError {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 match self {
337 Self::IdMismatch { index, id } => {
338 write!(f, "node at index {index} has id {}", id.0)
339 }
340 Self::RootOutOfRange(id) => write!(f, "root id {} indexes no node", id.0),
341 Self::RootNotAtOrigin => f.write_str("root origin is not (0, 0)"),
342 Self::SurfaceMismatch => f.write_str("surface does not match the root box"),
343 Self::ChildOutOfRange { parent, child } => {
344 write!(f, "node {} lists missing child {}", parent.0, child.0)
345 }
346 Self::MultipleParents(id) => write!(f, "node {} has more than one parent", id.0),
347 Self::Unreachable(id) => write!(f, "node {} is not reachable from the root", id.0),
348 Self::SourceIdMismatch { index, id } => {
349 write!(f, "source at index {index} has id {}", id.0)
350 }
351 Self::UnknownSource { node, source } => {
352 write!(f, "node {} refers to unknown source {}", node.0, source.0)
353 }
354 }
355 }
356}
357
358impl std::error::Error for FragmentError {}
359
360#[derive(Clone, Copy, Debug, PartialEq, Eq)]
362pub struct SourceOrigin<'a> {
363 pub node: NodeId,
365 pub source: &'a SourceFile,
367 pub span: ByteSpan,
369 pub role: SourceRole,
371}
372
373#[derive(Clone, Debug, Default, PartialEq, Eq)]
375pub struct FragmentMetadata {
376 pub format_id: String,
378 pub fragment_kind: FragmentKind,
380}
381
382#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
384#[non_exhaustive]
385pub enum FragmentKind {
386 #[default]
388 MathInline,
389 MathDisplay,
391 Text,
393}
394
395#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
397pub struct Surface {
398 pub width: Length,
400 pub height: Length,
402 pub baseline: Length,
404}
405
406impl Surface {
407 #[must_use]
409 pub fn of_root(root: &LayoutNode) -> Self {
410 Self {
411 width: root.width,
412 height: root.height + root.depth,
413 baseline: root.height,
414 }
415 }
416}
417
418#[derive(Clone, Debug, PartialEq)]
420pub struct LayoutNode {
421 pub id: NodeId,
423 pub origin: Point,
425 pub width: Length,
427 pub height: Length,
429 pub depth: Length,
431 pub primary_source: Option<SourceRange>,
433 pub kind: LayoutNodeKind,
435}
436
437impl LayoutNode {
438 #[must_use]
440 pub fn children(&self) -> &[NodeId] {
441 match &self.kind {
442 LayoutNodeKind::Box(layout_box) => &layout_box.children,
443 _ => &[],
444 }
445 }
446}
447
448#[derive(Clone, Debug, PartialEq)]
450#[non_exhaustive]
451pub enum LayoutNodeKind {
452 Box(LayoutBox),
454 GlyphRun(GlyphRun),
456 Rule,
458 Glue(Glue),
460 Kern(Kern),
462}
463
464#[derive(Clone, Debug, PartialEq, Eq)]
466pub struct LayoutBox {
467 pub kind: BoxKind,
469 pub children: Vec<NodeId>,
471}
472
473#[derive(Clone, Copy, Debug, PartialEq, Eq)]
475#[non_exhaustive]
476pub enum BoxKind {
477 Horizontal,
479 Vertical,
481}
482
483#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
485pub enum Axis {
486 #[default]
488 Horizontal,
489 Vertical,
491}
492
493#[derive(Clone, Debug, PartialEq, Eq)]
495pub struct GlyphRun {
496 pub font: FontRef,
498 pub glyphs: Vec<PositionedGlyph>,
500}
501
502#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
504pub struct PositionedGlyph {
505 pub glyph_id: GlyphId,
507 pub offset: Point,
509 pub cluster: Option<ByteSpan>,
511}
512
513#[derive(Clone, Debug, PartialEq, Eq, Hash)]
515pub struct FontRef {
516 pub key: Option<FontKey>,
518 pub spec: String,
520 pub size: Length,
522}
523
524#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
526pub struct FontKey(pub u64);
527
528#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
530pub struct Glue {
531 pub amount: Length,
533 pub axis: Axis,
535}
536
537#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
539pub struct Kern {
540 pub amount: Length,
542 pub axis: Axis,
544}
545
546#[derive(Clone, Debug, Default, PartialEq, Eq)]
548pub struct SourceMap {
549 pub sources: Vec<SourceFile>,
551 pub entries: Vec<SourceMapEntry>,
553}
554
555impl SourceMap {
556 pub fn add_source(&mut self, name: impl Into<String>) -> SourceId {
558 let id = SourceId(u32::try_from(self.sources.len()).unwrap_or(u32::MAX));
559 self.sources.push(SourceFile {
560 id,
561 name: name.into(),
562 });
563 id
564 }
565
566 pub fn intern_source(&mut self, name: impl Into<String>) -> SourceId {
568 let name = name.into();
569 if let Some(source) = self.sources.iter().find(|source| source.name == name) {
570 return source.id;
571 }
572 self.add_source(name)
573 }
574
575 pub fn add_entry(&mut self, node: NodeId, range: SourceRange) {
577 self.entries.push(SourceMapEntry { node, range });
578 }
579
580 #[must_use]
582 pub fn source(&self, id: SourceId) -> Option<&SourceFile> {
583 self.sources
584 .get(id.index())
585 .filter(|source| source.id == id)
586 }
587
588 pub fn entries_for_node(&self, node: NodeId) -> impl Iterator<Item = &SourceMapEntry> {
590 self.entries.iter().filter(move |entry| entry.node == node)
591 }
592}
593
594#[derive(Clone, Debug, PartialEq, Eq)]
596pub struct SourceFile {
597 pub id: SourceId,
599 pub name: String,
601}
602
603#[derive(Clone, Copy, Debug, PartialEq, Eq)]
605pub struct SourceMapEntry {
606 pub node: NodeId,
608 pub range: SourceRange,
610}
611
612#[derive(Clone, Copy, Debug, PartialEq, Eq)]
614pub struct SourceRange {
615 pub source: SourceId,
617 pub span: ByteSpan,
619}
620
621#[derive(Clone, Copy, Debug, PartialEq, Eq)]
623#[non_exhaustive]
624pub enum SourceRole {
625 Primary,
627 EnclosingConstruct,
629}
630
631#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
633pub struct ByteSpan {
634 pub start: u32,
636 pub end: u32,
638}
639
640#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
642pub struct Point {
643 pub x: Length,
645 pub y: Length,
647}
648
649impl Point {
650 pub const ORIGIN: Self = Self {
652 x: Length::ZERO,
653 y: Length::ZERO,
654 };
655
656 #[must_use]
658 pub const fn new(x: Length, y: Length) -> Self {
659 Self { x, y }
660 }
661}
662
663impl Add for Point {
664 type Output = Self;
665
666 fn add(self, rhs: Self) -> Self {
667 Self::new(self.x + rhs.x, self.y + rhs.y)
668 }
669}
670
671#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
673pub struct Length(pub i32);
674
675impl Length {
676 pub const ZERO: Self = Self(0);
678 pub const SP_PER_PT: i32 = 65_536;
680
681 #[must_use]
683 pub const fn from_scaled_points(value: i32) -> Self {
684 Self(value)
685 }
686
687 #[must_use]
689 pub fn from_pt(points: f64) -> Self {
690 Self((points * f64::from(Self::SP_PER_PT)).round() as i32)
691 }
692
693 #[must_use]
695 pub fn to_pt(self) -> f64 {
696 f64::from(self.0) / f64::from(Self::SP_PER_PT)
697 }
698}
699
700impl Add for Length {
701 type Output = Self;
702
703 fn add(self, rhs: Self) -> Self {
704 Self(self.0.saturating_add(rhs.0))
705 }
706}
707
708impl AddAssign for Length {
709 fn add_assign(&mut self, rhs: Self) {
710 *self = *self + rhs;
711 }
712}
713
714impl Sub for Length {
715 type Output = Self;
716
717 fn sub(self, rhs: Self) -> Self {
718 Self(self.0.saturating_sub(rhs.0))
719 }
720}
721
722impl SubAssign for Length {
723 fn sub_assign(&mut self, rhs: Self) {
724 *self = *self - rhs;
725 }
726}
727
728impl Neg for Length {
729 type Output = Self;
730
731 fn neg(self) -> Self {
732 Self(self.0.saturating_neg())
733 }
734}
735
736impl Mul<i32> for Length {
737 type Output = Self;
738
739 fn mul(self, rhs: i32) -> Self {
740 Self(self.0.saturating_mul(rhs))
741 }
742}
743
744impl Mul<f64> for Length {
745 type Output = Self;
746
747 fn mul(self, rhs: f64) -> Self {
748 Self((f64::from(self.0) * rhs).round() as i32)
749 }
750}
751
752#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
754pub struct NodeId(pub u32);
755
756impl NodeId {
757 #[must_use]
759 pub const fn index(self) -> usize {
760 self.0 as usize
761 }
762}
763
764#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
766pub struct SourceId(pub u32);
767
768impl SourceId {
769 #[must_use]
771 pub const fn index(self) -> usize {
772 self.0 as usize
773 }
774}
775
776#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
778pub struct GlyphId(pub u32);
779
780#[derive(Clone, Copy, Debug, PartialEq)]
782pub enum OutlineCommand {
783 MoveTo {
785 x: f32,
787 y: f32,
789 },
790 LineTo {
792 x: f32,
794 y: f32,
796 },
797 QuadTo {
799 cx: f32,
801 cy: f32,
803 x: f32,
805 y: f32,
807 },
808 CurveTo {
810 c1x: f32,
812 c1y: f32,
814 c2x: f32,
816 c2y: f32,
818 x: f32,
820 y: f32,
822 },
823 Close,
825}
826
827#[derive(Clone, Debug, Default, PartialEq)]
829pub struct GlyphOutline {
830 pub units_per_em: u16,
832 pub commands: Vec<OutlineCommand>,
834}
835
836#[cfg(test)]
837mod tests {
838 use super::*;
839
840 const PT: i32 = Length::SP_PER_PT;
841
842 fn node(
843 id: u32,
844 origin: (i32, i32),
845 size: (i32, i32, i32),
846 kind: LayoutNodeKind,
847 ) -> LayoutNode {
848 LayoutNode {
849 id: NodeId(id),
850 origin: Point::new(Length(origin.0 * PT), Length(origin.1 * PT)),
851 width: Length(size.0 * PT),
852 height: Length(size.1 * PT),
853 depth: Length(size.2 * PT),
854 primary_source: None,
855 kind,
856 }
857 }
858
859 fn hbox(children: &[u32]) -> LayoutNodeKind {
860 LayoutNodeKind::Box(LayoutBox {
861 kind: BoxKind::Horizontal,
862 children: children.iter().copied().map(NodeId).collect(),
863 })
864 }
865
866 fn run(glyphs: &[(u32, i32, i32)]) -> LayoutNodeKind {
867 LayoutNodeKind::GlyphRun(GlyphRun {
868 font: FontRef {
869 key: Some(FontKey(3)),
870 spec: "[test.otf]".into(),
871 size: Length(10 * PT),
872 },
873 glyphs: glyphs
874 .iter()
875 .map(|&(glyph, x, y)| PositionedGlyph {
876 glyph_id: GlyphId(glyph),
877 offset: Point::new(Length(x * PT), Length(y * PT)),
878 cluster: None,
879 })
880 .collect(),
881 })
882 }
883
884 fn sample() -> Fragment {
885 let nodes = vec![
886 node(0, (0, 0), (20, 8, 2), hbox(&[1, 3])),
887 node(1, (3, -2), (5, 4, 0), hbox(&[2])),
888 node(2, (1, 0), (4, 4, 0), run(&[(7, 0, 0), (8, 2, 1)])),
889 node(3, (10, 0), (6, 3, 1), LayoutNodeKind::Rule),
890 ];
891 Fragment::new(
892 NodeId(0),
893 nodes,
894 SourceMap::default(),
895 FragmentMetadata::default(),
896 )
897 .expect("valid fragment")
898 }
899
900 #[test]
901 fn surface_derives_from_the_root() {
902 let fragment = sample();
903 assert_eq!(fragment.surface.width, Length(20 * PT));
904 assert_eq!(fragment.surface.height, Length(10 * PT));
905 assert_eq!(fragment.surface.baseline, Length(8 * PT));
906 }
907
908 #[test]
909 fn flatten_accumulates_parent_relative_origins_from_the_baseline() {
910 let fragment = sample();
911 let placed = fragment.flatten();
912 let pt = |value: i32| Length(value * PT);
913 assert_eq!(placed.len(), 3);
914 assert!(
916 matches!(placed[0], Placed::Glyph { glyph_id: GlyphId(7), x, y, .. } if x == pt(4) && y == pt(6))
917 );
918 assert!(
919 matches!(placed[1], Placed::Glyph { glyph_id: GlyphId(8), x, y, .. } if x == pt(6) && y == pt(7))
920 );
921 assert_eq!(
923 placed[2],
924 Placed::Rule {
925 node: NodeId(3),
926 x: pt(10),
927 y: pt(5),
928 width: pt(6),
929 height: pt(4),
930 source: None,
931 }
932 );
933 }
934
935 #[test]
936 fn validate_rejects_broken_trees() {
937 let mut fragment = sample();
938 fragment.nodes.swap(1, 2);
939 assert!(matches!(
940 fragment.validate(),
941 Err(FragmentError::IdMismatch { index: 1, .. })
942 ));
943
944 let mut fragment = sample();
945 if let LayoutNodeKind::Box(root) = &mut fragment.nodes[0].kind {
946 root.children.push(NodeId(2));
947 }
948 assert_eq!(
949 fragment.validate(),
950 Err(FragmentError::MultipleParents(NodeId(2)))
951 );
952
953 let mut fragment = sample();
954 if let LayoutNodeKind::Box(root) = &mut fragment.nodes[0].kind {
955 root.children.pop();
956 }
957 assert_eq!(
958 fragment.validate(),
959 Err(FragmentError::Unreachable(NodeId(3)))
960 );
961
962 let mut fragment = sample();
963 fragment.nodes[0].origin.x = Length(1);
964 assert_eq!(fragment.validate(), Err(FragmentError::RootNotAtOrigin));
965
966 let mut fragment = sample();
967 fragment.surface.baseline = Length::ZERO;
968 assert_eq!(fragment.validate(), Err(FragmentError::SurfaceMismatch));
969 }
970
971 #[test]
972 fn source_origins_list_the_primary_span_then_enclosing_spans() {
973 let mut fragment = sample();
974 let input = fragment.source_map.add_source("input");
975 let package = fragment.source_map.add_source("amsmath.sty");
976 assert_eq!(fragment.source_map.intern_source("input"), input);
977 fragment.nodes[2].primary_source = Some(SourceRange {
978 source: input,
979 span: ByteSpan { start: 1, end: 5 },
980 });
981 fragment.source_map.add_entry(
982 NodeId(2),
983 SourceRange {
984 source: package,
985 span: ByteSpan { start: 10, end: 20 },
986 },
987 );
988 if let LayoutNodeKind::GlyphRun(run) = &mut fragment.nodes[2].kind {
989 run.glyphs[1].cluster = Some(ByteSpan { start: 2, end: 4 });
990 }
991 fragment.validate().expect("valid");
992
993 let origins = fragment
994 .source_origins_for_node(NodeId(2))
995 .collect::<Vec<_>>();
996 assert_eq!(origins.len(), 2);
997 assert_eq!(origins[0].role, SourceRole::Primary);
998 assert_eq!(origins[0].span, ByteSpan { start: 1, end: 5 });
999 assert_eq!(origins[1].source.name, "amsmath.sty");
1000 assert_eq!(origins[1].role, SourceRole::EnclosingConstruct);
1001
1002 assert_eq!(fragment.glyph_source_range(NodeId(2), 0), None);
1003 let cluster = SourceRange {
1004 source: input,
1005 span: ByteSpan { start: 2, end: 4 },
1006 };
1007 assert_eq!(fragment.glyph_source_range(NodeId(2), 1), Some(cluster));
1008 let placed = fragment.flatten();
1009 assert!(
1010 matches!(placed[0], Placed::Glyph { source: Some(range), .. } if range.span == ByteSpan { start: 1, end: 5 })
1011 );
1012 assert!(matches!(placed[1], Placed::Glyph { source: Some(range), .. } if range == cluster));
1013
1014 fragment.nodes[3].primary_source = Some(SourceRange {
1015 source: SourceId(9),
1016 span: ByteSpan::default(),
1017 });
1018 assert!(matches!(
1019 fragment.validate(),
1020 Err(FragmentError::UnknownSource { .. })
1021 ));
1022 }
1023
1024 #[test]
1025 fn length_arithmetic_saturates_and_converts_points() {
1026 assert_eq!(Length::from_pt(1.5), Length(98_304));
1027 assert_eq!(Length(98_304).to_pt(), 1.5);
1028 assert_eq!(Length(3) + Length(4) - Length(10), Length(-3));
1029 assert_eq!(-Length(5), Length(-5));
1030 assert_eq!(Length(5) * 3, Length(15));
1031 assert_eq!(Length(10) * 0.25, Length(3));
1032 assert_eq!(Length(i32::MAX) + Length(1), Length(i32::MAX));
1033 assert_eq!(-Length(i32::MIN), Length(i32::MAX));
1034 }
1035}