1use crate::{Cell, Footprint, Pos, ShapeId, StampMode, StampSink};
5
6pub struct FootprintSink<'a> {
14 sink: &'a mut dyn StampSink,
15 footprint: Option<Footprint>,
16}
17
18impl<'a> FootprintSink<'a> {
19 #[must_use]
21 pub fn new(sink: &'a mut dyn StampSink) -> Self {
22 Self {
23 sink,
24 footprint: None,
25 }
26 }
27
28 #[must_use]
33 pub fn footprint(&self) -> Option<Footprint> {
34 self.footprint
35 }
36}
37
38impl StampSink for FootprintSink<'_> {
39 fn stamp(&mut self, at: Pos, cell: Cell, mode: StampMode, owner: Option<ShapeId>) {
40 self.footprint = Some(match self.footprint {
41 None => Footprint { min: at, max: at },
42 Some(footprint) => footprint.including(at),
43 });
44 self.sink.stamp(at, cell, mode, owner);
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use std::num::NonZeroU16;
51
52 use super::FootprintSink;
53 use crate::{
54 Arm, Buffer, Cell, CellReader, Footprint, OwnerReader, Pos, ShapeId, Size, StampMode,
55 StampSink, Stroke, StrokeCell, Window,
56 };
57
58 #[derive(Default)]
61 struct Recording {
62 stamps: Vec<(Pos, StampMode, Option<ShapeId>)>,
63 }
64
65 impl StampSink for Recording {
66 fn stamp(&mut self, at: Pos, _cell: Cell, mode: StampMode, owner: Option<ShapeId>) {
67 self.stamps.push((at, mode, owner));
68 }
69 }
70
71 fn light() -> Stroke {
72 Stroke::from("light")
73 }
74
75 fn id(ordinal: u16) -> ShapeId {
76 ShapeId::new(NonZeroU16::new(ordinal).expect("no test asks for zero"))
77 }
78
79 fn a_buffer(origin: Pos, width: u16, height: u16) -> Buffer {
82 Buffer::new(Window {
83 origin,
84 size: Size { width, height },
85 })
86 }
87
88 fn decided() -> Cell {
90 Cell::from(StrokeCell {
91 base: light(),
92 top: Arm::Set(light()),
93 right: Arm::Set(light()),
94 bottom: Arm::Set(light()),
95 left: Arm::Set(light()),
96 })
97 }
98
99 fn horizontal_run() -> Cell {
101 Cell::from(StrokeCell {
102 base: light(),
103 top: Arm::Unset,
104 right: Arm::Set(light()),
105 bottom: Arm::Unset,
106 left: Arm::Set(light()),
107 })
108 }
109
110 fn deciding_nothing() -> Cell {
112 Cell::from(StrokeCell {
113 base: light(),
114 top: Arm::Unset,
115 right: Arm::Unset,
116 bottom: Arm::Unset,
117 left: Arm::Unset,
118 })
119 }
120
121 #[test]
127 fn an_empty_drawing_has_no_footprint_and_the_first_stamp_is_one_cell() {
128 let mut recording = Recording::default();
129 let mut sink = FootprintSink::new(&mut recording);
130 assert_eq!(
131 sink.footprint(),
132 None,
133 "a drawing that stamped nothing reached somewhere"
134 );
135
136 let at = Pos { x: 2, y: 1 };
137 sink.stamp(at, decided(), StampMode::Above, None);
138
139 assert_eq!(sink.footprint(), Some(Footprint { min: at, max: at }));
140 }
141
142 #[test]
145 fn the_footprint_is_the_union_of_every_stamp() {
146 let mut recording = Recording::default();
147 let mut sink = FootprintSink::new(&mut recording);
148
149 sink.stamp(Pos { x: 3, y: 4 }, decided(), StampMode::Above, None);
150 sink.stamp(Pos { x: -1, y: 2 }, decided(), StampMode::Above, None);
151
152 assert_eq!(
153 sink.footprint(),
154 Some(Footprint {
155 min: Pos { x: -1, y: 2 },
156 max: Pos { x: 3, y: 4 },
157 })
158 );
159 }
160
161 #[test]
167 fn a_stamp_outside_the_window_writes_no_cell_and_still_counts() {
168 let mut buffer = a_buffer(Pos { x: 0, y: 0 }, 1, 1);
169 let far = Pos { x: -5, y: -7 };
170 let footprint = {
171 let mut sink = FootprintSink::new(&mut buffer);
172 sink.stamp(far, decided(), StampMode::Above, Some(id(1)));
173 sink.footprint()
174 };
175
176 assert!(
177 buffer.cell(far).is_none(),
178 "the window wrote a cell it does not hold"
179 );
180 assert_eq!(footprint, Some(Footprint { min: far, max: far }));
181 }
182
183 #[test]
190 fn a_stamp_that_decides_no_side_still_counts() {
191 let mut buffer = a_buffer(Pos { x: 0, y: 0 }, 3, 1);
192 let at = Pos { x: 0, y: 0 };
193 let footprint = {
194 let mut sink = FootprintSink::new(&mut buffer);
195 sink.stamp(at, deciding_nothing(), StampMode::Above, Some(id(1)));
196 sink.stamp(
197 Pos { x: 2, y: 0 },
198 horizontal_run(),
199 StampMode::Above,
200 Some(id(2)),
201 );
202 sink.footprint()
203 };
204
205 assert_eq!(
206 buffer.owner(Pos { x: 0, y: 0 }),
207 None,
208 "a cell deciding no side owns nothing"
209 );
210 assert_eq!(
211 footprint,
212 Some(Footprint {
213 min: Pos { x: 0, y: 0 },
214 max: Pos { x: 2, y: 0 },
215 })
216 );
217 }
218
219 #[test]
222 fn a_stamp_below_a_decided_cell_changes_nothing_and_still_counts() {
223 let mut buffer = a_buffer(Pos { x: 0, y: 0 }, 1, 1);
224 let at = Pos { x: 0, y: 0 };
225 buffer.stamp(at, decided(), StampMode::Above, Some(id(1)));
226 let before = buffer.cell(at).cloned();
227
228 let footprint = {
229 let mut sink = FootprintSink::new(&mut buffer);
230 sink.stamp(at, deciding_nothing(), StampMode::Below, Some(id(2)));
231 sink.footprint()
232 };
233
234 assert_eq!(buffer.cell(at).cloned(), before, "the cell changed");
235 assert_eq!(footprint, Some(Footprint { min: at, max: at }));
236 }
237
238 #[test]
241 fn every_stamp_reaches_the_sink_it_wraps() {
242 let mut recording = Recording::default();
243 let mut sink = FootprintSink::new(&mut recording);
244
245 sink.stamp(Pos { x: 1, y: 2 }, decided(), StampMode::Above, Some(id(3)));
246 sink.stamp(Pos { x: 4, y: 5 }, decided(), StampMode::Below, None);
247
248 assert_eq!(
249 recording.stamps,
250 vec![
251 (Pos { x: 1, y: 2 }, StampMode::Above, Some(id(3))),
252 (Pos { x: 4, y: 5 }, StampMode::Below, None),
253 ]
254 );
255 }
256}