1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct Geometry {
8 pub body: u64,
9 pub footing: Option<u64>,
12 pub top: u64,
13 pub bottom: u64,
14}
15
16impl Geometry {
17 pub fn new(body: i64, footing: Option<i64>, top: i64, bottom: i64) -> Result<Self, String> {
20 if body < 1 {
21 return Err(format!("LINAGE gives a page body of {body} lines, and it needs at least 1"));
22 }
23 if let Some(f) = footing.filter(|f| !(1..=body).contains(f)) {
24 return Err(format!("LINAGE puts the footing at line {f}, outside the page body of {body} lines"));
25 }
26 if top < 0 || bottom < 0 {
27 return Err(format!("LINAGE gives margins of {top} and {bottom} lines"));
28 }
29 Ok(Self { body: body as u64, footing: footing.map(|f| f as u64), top: top as u64, bottom: bottom as u64 })
30 }
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub struct Page {
36 pub geometry: Geometry,
37 pub counter: u64,
39 owed: u64,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum Motion {
46 Lines(u64),
47 Page,
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct Step {
54 pub ahead: u64,
55 pub behind: u64,
56 pub end_of_page: bool,
57}
58
59impl Page {
60 pub fn opened(geometry: Geometry) -> Self {
61 Self { geometry, counter: 1, owed: geometry.top }
62 }
63
64 pub fn write<E>(&mut self, before: bool, motion: Motion, next: impl FnOnce() -> Result<Geometry, E>) -> Result<Step, E> {
68 let current = self.geometry;
69 let (moved, overflow) = match motion {
70 Motion::Lines(n) if self.counter.saturating_add(n) <= current.body => {
71 self.counter += n;
72 (n, false)
73 }
74 _ => {
75 let following = next()?;
76 let rest = current.body.saturating_sub(self.counter).saturating_add(current.bottom);
77 self.geometry = following;
78 self.counter = 1;
79 (rest.saturating_add(following.top).saturating_add(1), motion != Motion::Page)
80 }
81 };
82 let end_of_page = overflow || self.geometry.footing.is_some_and(|f| self.counter >= f);
83 let owed = std::mem::take(&mut self.owed);
84 Ok(if before { Step { ahead: owed, behind: moved, end_of_page } } else { Step { ahead: owed.saturating_add(moved), behind: 0, end_of_page } })
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 fn page(body: i64, footing: Option<i64>, top: i64, bottom: i64) -> Page {
93 Page::opened(Geometry::new(body, footing, top, bottom).unwrap())
94 }
95
96 fn step(p: &mut Page, before: bool, motion: Motion, next: Geometry) -> (u64, u64, bool, u64) {
97 let s = p.write(before, motion, || Ok::<_, ()>(next)).unwrap();
98 (s.ahead, s.behind, s.end_of_page, p.counter)
99 }
100
101 #[test]
102 fn the_first_write_moves_past_the_top_margin_and_counts_from_line_one() {
103 let g = Geometry::new(5, Some(4), 2, 3).unwrap();
104 let mut p = Page::opened(g);
105 assert_eq!(p.counter, 1);
106 assert_eq!(step(&mut p, false, Motion::Lines(1), g), (3, 0, false, 2));
107 assert_eq!(step(&mut p, false, Motion::Lines(1), g), (1, 0, false, 3));
108 assert_eq!(step(&mut p, false, Motion::Lines(1), g), (1, 0, true, 4));
109 assert_eq!(step(&mut p, false, Motion::Lines(0), g), (0, 0, true, 4));
110 assert_eq!(step(&mut p, false, Motion::Lines(2), g), (1 + 3 + 2 + 1, 0, true, 1));
111 }
112
113 #[test]
114 fn before_advancing_prints_then_moves_and_overflow_leaves_the_printer_on_the_next_page() {
115 let g = Geometry::new(3, Some(3), 1, 1).unwrap();
116 let mut p = Page::opened(g);
117 assert_eq!(step(&mut p, true, Motion::Lines(1), g), (1, 1, false, 2));
118 assert_eq!(step(&mut p, true, Motion::Lines(1), g), (0, 1, true, 3));
119 assert_eq!(step(&mut p, true, Motion::Lines(1), g), (0, 1 + 1 + 1, true, 1));
120 }
121
122 #[test]
123 fn without_footing_only_an_overflow_is_the_end_of_the_page() {
124 let g = Geometry::new(2, None, 0, 0).unwrap();
125 let mut p = Page::opened(g);
126 assert_eq!(step(&mut p, false, Motion::Lines(1), g), (1, 0, false, 2));
127 assert_eq!(step(&mut p, false, Motion::Lines(0), g), (0, 0, false, 2));
128 assert_eq!(step(&mut p, false, Motion::Lines(1), g), (1, 0, true, 1));
129 assert_eq!(step(&mut p, false, Motion::Page, g), (2, 0, false, 1));
130 }
131
132 #[test]
133 fn advancing_page_takes_the_next_geometry_and_raises_end_of_page_only_at_a_first_line_footing() {
134 let mut p = page(10, Some(8), 0, 0);
135 let next = Geometry::new(4, Some(2), 3, 0).unwrap();
136 assert_eq!(step(&mut p, false, Motion::Lines(5), next), (5, 0, false, 6));
137 assert_eq!(step(&mut p, false, Motion::Page, next), (4 + 3 + 1, 0, false, 1));
138 assert_eq!(p.geometry, next);
139 let at_one = Geometry::new(4, Some(1), 0, 0).unwrap();
140 assert_eq!(step(&mut p, true, Motion::Page, at_one), (0, 3 + 1, true, 1));
141 }
142
143 #[test]
144 fn a_page_the_values_cannot_make_is_refused() {
145 assert!(Geometry::new(0, None, 0, 0).is_err());
146 assert!(Geometry::new(5, Some(6), 0, 0).is_err());
147 assert!(Geometry::new(5, Some(0), 0, 0).is_err());
148 assert!(Geometry::new(5, None, -1, 0).is_err());
149 assert_eq!(Geometry::new(5, None, 0, 0).unwrap().footing, None);
150 }
151}