monospace_core/buffer.rs
1//! The buffer: a window of cells. See _The buffer_ and _Stamping_ in
2//! [`docs/model.md`](../../../docs/model.md).
3
4use std::collections::HashMap;
5
6use crate::{Arm, Cell, Pos, ShapeId, StrokeCell, Window};
7
8/// Which side of an already-defined cell decides when a stamp lands on it. See _Stamping_ in
9/// [`docs/model.md`](../../../docs/model.md).
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum StampMode {
12 /// Overwrites the base stroke and every arm the stamp decides. An arm the stamp leaves
13 /// `Unset` keeps whatever the target already had.
14 Above,
15 /// Leaves the base stroke alone. Writes only the sides the target has left `Unset`, taking
16 /// them from the stamp; a side the target has already decided keeps its value.
17 Below,
18}
19
20/// What a stamp is handed to: one write operation and no reader.
21///
22/// **A sink is the whole of what writing needs**, which is what lets a figure layer hold one
23/// without holding a [`Buffer`], and lets an adapter stand between a drawing and the store that
24/// keeps it — measuring what passes, or sending it somewhere else — without the drawing knowing.
25/// [`Buffer`] is the sink this crate ships, and the one that keeps the cells and the owners.
26///
27/// The call is the one [`Surface::stamp`](crate::Surface::stamp) becomes, and it carries the two
28/// things a fragment is never told: which [`StampMode`] decides the composition, and which identity
29/// the stamp is made under. See _Stamping_ in [`docs/model.md`](../../../docs/model.md).
30pub trait StampSink {
31 /// Writes `cell` at the absolute position `at`, under `mode`, recorded under `owner`.
32 ///
33 /// **What a sink does with a stamp it cannot hold is its own answer**: a buffer writes nothing
34 /// outside its window.
35 fn stamp(&mut self, at: Pos, cell: Cell, mode: StampMode, owner: Option<ShapeId>);
36}
37
38/// What a caller reads a cell through: one position, answered from the store behind it.
39///
40/// **Reading a cell is the whole of what rendering needs**, which is what lets [`render`] ask a
41/// value for its cells without being handed a [`Buffer`] and without being able to ask who owns
42/// one. [`Buffer`] is the reader this crate ships.
43///
44/// [`render`]: crate::render
45pub trait CellReader {
46 /// The cell at the absolute position `at`, or `None` if it is undefined.
47 ///
48 /// An undefined cell and a position nothing was ever written at look the same: there is no
49 /// cell there either way, so there is nothing to distinguish them by.
50 #[must_use]
51 fn cell(&self, at: Pos) -> Option<&Cell>;
52}
53
54/// What a caller reads an owner through: one position, answered from the record behind it.
55///
56/// **Who wrote a cell is a question of its own**, asked by a front end deciding what a click
57/// landed on and by nothing that renders, so it rides on an interface separate from
58/// [`CellReader`]. [`Buffer`] answers both.
59pub trait OwnerReader {
60 /// The identity of whoever decided the cell at the position `at`, or `None` when nobody holds
61 /// that position.
62 ///
63 /// **The answer is a value rather than a borrow of one.** An identity is two bytes and `Copy`,
64 /// so a caller holds what it is given and may go on drawing into the buffer and ask again,
65 /// which is what a front end deciding what to do with a cell it was handed has to be able to
66 /// do. Nothing is owed by holding it and nothing is spent by copying it.
67 ///
68 /// **`None` covers two cases and distinguishes neither**, in the type or in the answer: a
69 /// position no shape wrote, and a position a shape wrote without deciding. Nothing was ever
70 /// recorded at either, so there is nothing to say which it was. A position outside the window is
71 /// the first of them: the record never holds one.
72 ///
73 /// **The position is one a caller already holds**, and reaching it from a click is
74 /// [`Window::position`]'s job rather than this method's: an offset counted from the window's
75 /// corner is the one position that offset reaches, and a caller that added the origin itself
76 /// would be asking about another cell.
77 #[must_use]
78 fn owner(&self, at: Pos) -> Option<ShapeId>;
79}
80
81/// A window of cells, and a record of which shape decided each of them.
82///
83/// Every position in a freshly created buffer is undefined: it holds no cell until [`stamp`] is
84/// called for it. The buffer is a temporary working surface, not the document; there is no erase.
85///
86/// **The record is kept beside the cells rather than inside one**, in a map of its own, for the
87/// reason two cells that render alike must stay one cell: an identity inside a [`Cell`] would be a
88/// part of the cell rather than a note about who wrote it, and every equality, every render and
89/// every catalog lookup would then depend on it. [`owner`] reads the record back.
90///
91/// [`owner`]: OwnerReader::owner
92/// [`cell`]: CellReader::cell
93/// [`stamp`]: StampSink::stamp
94pub struct Buffer {
95 window: Window,
96 cells: HashMap<(i16, i16), Cell>,
97 /// Who decided each position, for the positions somebody decided. A position absent here is
98 /// owned by nobody: either no shape wrote it, or the shape that did wrote nothing there.
99 owners: HashMap<(i16, i16), ShapeId>,
100}
101
102impl Buffer {
103 /// Creates a buffer with no positions defined, holding `window`.
104 ///
105 /// A width or height of zero is allowed and gives a buffer with no positions; construction
106 /// cannot fail.
107 #[must_use]
108 pub fn new(window: Window) -> Self {
109 Self {
110 window,
111 cells: HashMap::new(),
112 owners: HashMap::new(),
113 }
114 }
115
116 /// The window this buffer was created with.
117 ///
118 /// **A value rather than a borrow**: a window is two small `Copy` values, and a caller that
119 /// reads it while holding the buffer needs nothing back. What it is for is the translation an
120 /// offset into the window needs and the size a rendering asks for.
121 #[must_use]
122 pub fn window(&self) -> Window {
123 self.window
124 }
125
126 /// Whether `at` falls inside this buffer's window.
127 fn contains(&self, at: Pos) -> bool {
128 self.window.contains(at)
129 }
130}
131
132impl CellReader for Buffer {
133 fn cell(&self, at: Pos) -> Option<&Cell> {
134 self.cells.get(&(at.x, at.y))
135 }
136}
137
138impl OwnerReader for Buffer {
139 fn owner(&self, at: Pos) -> Option<ShapeId> {
140 self.owners.get(&(at.x, at.y)).copied()
141 }
142}
143
144impl StampSink for Buffer {
145 /// Writes `cell` at the absolute position `at`, under `mode`.
146 ///
147 /// A position outside the window is left unchanged. Writing an undefined position defines it
148 /// entirely, `Unset` arms included, whichever mode is given. Writing an already-defined
149 /// position consults `mode` for which side decides: [`StampMode::Above`] overwrites the base
150 /// stroke and reads the *stamp's* arms, leaving alone whichever ones `cell` itself leaves
151 /// `Unset`; [`StampMode::Below`] leaves the base stroke alone and reads the *target's* arms
152 /// instead, writing only the sides the cell already stored has left `Unset`. A cell holding a
153 /// literal glyph or a named symbol is decided on every side either way, so it behaves as the
154 /// fully decided case throughout — see _A cell can be a literal instead_ and _A cell can name
155 /// a symbol instead_ in [`docs/model.md`](../../../docs/model.md).
156 /// `owner` is the identity of whoever is stamping, or `None` for a stamp by nobody: a figure
157 /// this crate draws on its own gallery carries no identity, and neither does a caller with no
158 /// diagram to draw from. **`None` records nothing rather than recording an absence** — it is
159 /// not an owner the buffer holds, so a position keeps whoever holds it and answers `None` where
160 /// nobody does.
161 ///
162 /// What the stamp records follows the rules that decide the cell rather than sitting beside
163 /// them, which is what makes the record and the picture one thing instead of two: this stamp
164 /// owns the position when it decides at least one side of it, and under [`StampMode::Below`]
165 /// only when nobody holds it already. A shape behind therefore cannot take a cell from one in
166 /// front of it however much it decides there, and since every stamp in front takes, the record
167 /// comes out the same whichever order a diagram is drawn in.
168 fn stamp(&mut self, at: Pos, cell: Cell, mode: StampMode, owner: Option<ShapeId>) {
169 if !self.contains(at) {
170 return;
171 }
172
173 // Read before the match below, which moves `cell` in two of its arms. A stamp that decides
174 // no side at all is written like any other cell and owns nothing.
175 let decides_something = decides_a_side(&cell);
176
177 let takes = match self.cells.get_mut(&(at.x, at.y)) {
178 None => {
179 self.cells.insert((at.x, at.y), cell);
180 decides_something
181 }
182 Some(target) => {
183 *target = match mode {
184 // Above onto a decided stamp always reproduces the stamp itself: every arm
185 // it names wins outright, so the merge that would compute the same thing is
186 // skipped. Mirrors the Below branch below it: the rule that the topmost
187 // figure owns each side it decides names no stamp mode, so implementing it in
188 // one direction only would read as a deliberate asymmetry. A literal or a
189 // symbol is decided by definition, so this is also where it wins outright.
190 StampMode::Above if cell.is_decided() => cell,
191 StampMode::Above => merge(cell, target),
192 // Below never changes a decided target: merging would reproduce it exactly,
193 // so this returns instead of rebuilding and storing an identical cell. No
194 // test can fail for this arm either way — deleting the guard
195 // leaves every buffer byte-identical. An opaque target is decided too, so
196 // this is also where it is left alone.
197 //
198 // The early return leaves the record alone too, and that is the point of it
199 // rather than a side effect: a stamp that reproduces the cell it landed on
200 // decided nothing there, so it owns nothing there either.
201 StampMode::Below if target.is_decided() => return,
202 StampMode::Below => merge(target.clone(), &cell),
203 };
204 decides_something
205 && match mode {
206 StampMode::Above => true,
207 StampMode::Below => !self.owners.contains_key(&(at.x, at.y)),
208 }
209 }
210 };
211
212 if takes && let Some(owner) = owner {
213 self.owners.insert((at.x, at.y), owner);
214 }
215 }
216}
217
218/// Builds the cell that results from merging `top` onto `bottom`, per _Stamping_ in
219/// [`docs/model.md`](../../../docs/model.md). Which cell plays `top` is the caller's choice, not
220/// this function's: an `Above` stamp is `top` over the target, and a `Below` stamp puts the target
221/// itself in that role.
222///
223/// Three cases, matched explicitly rather than folded into one arm-by-arm loop — an opaque cell
224/// has no arms, so [`merge_arm`] never sees one:
225///
226/// - **An opaque cell on top** — a literal or a symbol — always wins outright, whatever `bottom`
227/// is. Unreachable through [`StampSink::stamp`] today, because its two `is_decided` shortcuts
228/// catch every decided cell first — written as a returning branch rather than `unreachable!()`
229/// so those shortcuts stay deletable optimizations instead of becoming load-bearing.
230/// - **A stroke cell over an opaque cell** wins as a stroke cell, with every side it left `Unset`
231/// closed: an opaque cell has no arms to fall through to, only a refusal on every side, per _A
232/// cell can be a literal instead_ and _A cell can name a symbol instead_ in
233/// [`docs/model.md`](../../../docs/model.md).
234/// - **Two stroke cells** merge arm by arm: `top`'s base stroke, and each arm `top` decides, with
235/// an arm `top` leaves `Unset` falling through to `bottom`'s side.
236fn merge(top: Cell, bottom: &Cell) -> Cell {
237 match (top, bottom) {
238 (opaque @ (Cell::Literal(_) | Cell::Symbol(_)), _) => opaque,
239 (Cell::Strokes(stroke), Cell::Literal(_) | Cell::Symbol(_)) => {
240 close_unset_sides(stroke).into()
241 }
242 (Cell::Strokes(top), Cell::Strokes(bottom)) => merge_strokes(top, bottom).into(),
243 }
244}
245
246/// Whether `cell` decides at least one side, which is the test a stamp passes before it can own
247/// the position it landed on.
248///
249/// **Not the same question as [`Cell::is_decided`]**, which asks whether _every_ side is decided
250/// and is about whether a cell can still change. This asks the opposite end: whether there is
251/// anything here at all. A cell whose four arms are all `Unset` renders as a space whatever stands
252/// beside it and is nobody's, so a stamp of one takes nothing however it composes — which is a
253/// question the merge cannot answer, because a stamp that decides no side still writes its base
254/// stroke over an undefined position and changes what is stored.
255///
256/// A literal or a symbol is decided on every side by definition, so it always passes.
257fn decides_a_side(cell: &Cell) -> bool {
258 match cell {
259 Cell::Literal(_) | Cell::Symbol(_) => true,
260 Cell::Strokes(cell) => {
261 !matches!(cell.top, Arm::Unset)
262 || !matches!(cell.right, Arm::Unset)
263 || !matches!(cell.bottom, Arm::Unset)
264 || !matches!(cell.left, Arm::Unset)
265 }
266 }
267}
268
269/// `cell`, with every `Unset` arm closed. An opaque cell refuses every side it meets rather than
270/// leaving any open, per _A cell can be a literal instead_ and _A cell can name a symbol instead_
271/// in [`docs/model.md`](../../../docs/model.md) — there is no arm of its own to fall through to.
272fn close_unset_sides(cell: StrokeCell) -> StrokeCell {
273 let close = |arm| {
274 if matches!(arm, Arm::Unset) {
275 Arm::Closed
276 } else {
277 arm
278 }
279 };
280
281 StrokeCell {
282 base: cell.base,
283 top: close(cell.top),
284 right: close(cell.right),
285 bottom: close(cell.bottom),
286 left: close(cell.left),
287 }
288}
289
290/// `top`'s base stroke, and each arm `top` decides, with an arm `top` leaves `Unset` falling
291/// through to `bottom`'s side — per _Stamping_ in [`docs/model.md`](../../../docs/model.md): "the
292/// base stroke ends up owned by the topmost figure, and each arm ends up owned by the topmost
293/// figure that decided it, with abstentions falling through to the ones behind."
294fn merge_strokes(top: StrokeCell, bottom: &StrokeCell) -> StrokeCell {
295 StrokeCell {
296 base: top.base,
297 top: merge_arm(top.top, bottom.top.clone()),
298 right: merge_arm(top.right, bottom.right.clone()),
299 bottom: merge_arm(top.bottom, bottom.bottom.clone()),
300 left: merge_arm(top.left, bottom.left.clone()),
301 }
302}
303
304/// `top`, unless it is `Unset` — an abstaining arm never writes anything, so the side falls
305/// through to whatever `bottom` has. Only [`merge_strokes`] calls this: a literal has no arm to
306/// offer either side of the comparison.
307fn merge_arm(top: Arm, bottom: Arm) -> Arm {
308 if matches!(top, Arm::Unset) {
309 bottom
310 } else {
311 top
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use std::num::NonZeroU16;
318
319 use super::{Buffer, CellReader, OwnerReader, StampMode, StampSink};
320 use crate::{
321 Arm, BoxShape, Cell, FigureLayer, Glyph, Offset, Pos, Shape as _, ShapeId, Size, Stroke,
322 StrokeCell, Symbol, SymbolCell, SymbolStyleName, Window,
323 };
324
325 fn light() -> Stroke {
326 Stroke::from("light")
327 }
328
329 fn double() -> Stroke {
330 Stroke::from("double")
331 }
332
333 fn heavy() -> Stroke {
334 Stroke::from("heavy")
335 }
336
337 fn glyph(text: &str) -> Glyph {
338 Glyph::new(text).unwrap_or_else(|| panic!("{text:?} is one glyph"))
339 }
340
341 /// The Solid `DirectionUp` symbol as a cell, which is what the composition tests below put
342 /// where a literal would otherwise sit.
343 fn a_symbol_cell() -> Cell {
344 Cell::Symbol(SymbolCell {
345 symbol: Symbol::DirectionUp,
346 style: SymbolStyleName::from("solid"),
347 })
348 }
349
350 /// An identity of the ordinal `ordinal`, which is how every test below states who stamped.
351 fn id(ordinal: u16) -> ShapeId {
352 ShapeId::new(NonZeroU16::new(ordinal).expect("no test asks for zero"))
353 }
354
355 /// A buffer `width` by `height` at `origin`, which is how every test below states where the
356 /// positions it asks about are.
357 fn a_buffer(origin: Pos, width: u16, height: u16) -> Buffer {
358 Buffer::new(Window {
359 origin,
360 size: Size { width, height },
361 })
362 }
363
364 /// A cell deciding all four sides, which is what makes it immutable under `Below`.
365 fn decided() -> Cell {
366 Cell::from(StrokeCell {
367 base: light(),
368 top: Arm::Set(light()),
369 right: Arm::Set(light()),
370 bottom: Arm::Set(light()),
371 left: Arm::Set(light()),
372 })
373 }
374
375 /// A cell deciding its top and bottom and abstaining on the other two — a vertical run, and the
376 /// shape of a box's left or right border.
377 fn vertical_run() -> Cell {
378 Cell::from(StrokeCell {
379 base: light(),
380 top: Arm::Set(light()),
381 right: Arm::Unset,
382 bottom: Arm::Set(light()),
383 left: Arm::Unset,
384 })
385 }
386
387 /// A cell deciding its left and right and abstaining on the other two — a horizontal run.
388 fn horizontal_run() -> Cell {
389 Cell::from(StrokeCell {
390 base: light(),
391 top: Arm::Unset,
392 right: Arm::Set(light()),
393 bottom: Arm::Unset,
394 left: Arm::Set(light()),
395 })
396 }
397
398 /// A cell deciding no side at all: four `Unset` arms, which renders as a space whatever stands
399 /// beside it. Written rather than derived, because this is the cell the rule about deciding
400 /// nothing is *about*, and a rule whose counterexample is built from the thing it rules on
401 /// cannot fail.
402 fn deciding_nothing() -> Cell {
403 Cell::from(StrokeCell {
404 base: light(),
405 top: Arm::Unset,
406 right: Arm::Unset,
407 bottom: Arm::Unset,
408 left: Arm::Unset,
409 })
410 }
411
412 /// A four-by-three box at `at`: the size the spec's measured windows use throughout, and small
413 /// enough that its border and its interior are each more than one cell.
414 fn a_box(at: Pos, stroke: Stroke, fill: Option<&str>) -> BoxShape {
415 BoxShape {
416 at,
417 size: Size {
418 width: 4,
419 height: 3,
420 },
421 stroke,
422 fill: fill.map(glyph),
423 }
424 }
425
426 /// Every position of a window `width` by `height` at `origin`, written out once so that a test
427 /// can walk a whole window rather than one position somebody chose.
428 fn every_position(origin: Pos, width: u16, height: u16) -> Vec<Pos> {
429 (0..height)
430 .flat_map(|y| {
431 (0..width).map(move |x| Pos {
432 x: origin.x + i16::try_from(x).expect("a window this narrow"),
433 y: origin.y + i16::try_from(y).expect("a window this short"),
434 })
435 })
436 .collect()
437 }
438
439 #[test]
440 fn a_new_buffer_has_no_cell_anywhere() {
441 let buffer = Buffer::new(Window {
442 origin: Pos { x: -1, y: -1 },
443 size: Size {
444 width: 3,
445 height: 3,
446 },
447 });
448
449 for x in -2..3 {
450 for y in -2..3 {
451 assert!(buffer.cell(Pos { x, y }).is_none());
452 }
453 }
454 }
455
456 #[test]
457 fn stamping_outside_the_window_changes_nothing() {
458 let mut buffer = Buffer::new(Window {
459 origin: Pos { x: 0, y: 0 },
460 size: Size {
461 width: 2,
462 height: 1,
463 },
464 });
465
466 buffer.stamp(
467 Pos { x: 2, y: 0 },
468 StrokeCell {
469 base: light(),
470 top: Arm::Set(light()),
471 right: Arm::Set(light()),
472 bottom: Arm::Set(light()),
473 left: Arm::Set(light()),
474 }
475 .into(),
476 StampMode::Above,
477 None,
478 );
479
480 assert!(buffer.cell(Pos { x: 2, y: 0 }).is_none());
481 }
482
483 #[test]
484 fn stamping_an_undefined_position_defines_it_entirely() {
485 let mut buffer = Buffer::new(Window {
486 origin: Pos { x: 0, y: 0 },
487 size: Size {
488 width: 1,
489 height: 1,
490 },
491 });
492 let cell: Cell = StrokeCell {
493 base: light(),
494 top: Arm::Unset,
495 right: Arm::Set(light()),
496 bottom: Arm::Closed,
497 left: Arm::Unset,
498 }
499 .into();
500
501 buffer.stamp(Pos { x: 0, y: 0 }, cell.clone(), StampMode::Above, None);
502
503 assert_eq!(buffer.cell(Pos { x: 0, y: 0 }), Some(&cell));
504 }
505
506 /// The example named "An abstaining stamp": a second stamp decides three sides and abstains
507 /// on the top, so the cell's top arm survives even though everything else was overwritten.
508 #[test]
509 fn an_unset_arm_on_a_stamp_leaves_the_target_arm_alone() {
510 let mut buffer = Buffer::new(Window {
511 origin: Pos { x: 0, y: 0 },
512 size: Size {
513 width: 1,
514 height: 1,
515 },
516 });
517 buffer.stamp(
518 Pos { x: 0, y: 0 },
519 StrokeCell {
520 base: light(),
521 top: Arm::Set(light()),
522 right: Arm::Closed,
523 bottom: Arm::Closed,
524 left: Arm::Closed,
525 }
526 .into(),
527 StampMode::Above,
528 None,
529 );
530
531 buffer.stamp(
532 Pos { x: 0, y: 0 },
533 StrokeCell {
534 base: light(),
535 top: Arm::Unset,
536 right: Arm::Set(light()),
537 bottom: Arm::Closed,
538 left: Arm::Set(light()),
539 }
540 .into(),
541 StampMode::Above,
542 None,
543 );
544
545 assert_eq!(
546 buffer.cell(Pos { x: 0, y: 0 }),
547 Some(&Cell::from(StrokeCell {
548 base: light(),
549 top: Arm::Set(light()),
550 right: Arm::Set(light()),
551 bottom: Arm::Closed,
552 left: Arm::Set(light()),
553 }))
554 );
555 }
556
557 #[test]
558 fn stamping_outside_the_window_changes_nothing_under_below() {
559 let mut buffer = Buffer::new(Window {
560 origin: Pos { x: 0, y: 0 },
561 size: Size {
562 width: 2,
563 height: 1,
564 },
565 });
566
567 buffer.stamp(
568 Pos { x: 2, y: 0 },
569 StrokeCell {
570 base: light(),
571 top: Arm::Set(light()),
572 right: Arm::Set(light()),
573 bottom: Arm::Set(light()),
574 left: Arm::Set(light()),
575 }
576 .into(),
577 StampMode::Below,
578 None,
579 );
580
581 assert!(buffer.cell(Pos { x: 2, y: 0 }).is_none());
582 }
583
584 #[test]
585 fn stamping_an_undefined_position_with_below_defines_it_entirely() {
586 let mut buffer = Buffer::new(Window {
587 origin: Pos { x: 0, y: 0 },
588 size: Size {
589 width: 1,
590 height: 1,
591 },
592 });
593 let cell: Cell = StrokeCell {
594 base: light(),
595 top: Arm::Unset,
596 right: Arm::Set(light()),
597 bottom: Arm::Closed,
598 left: Arm::Unset,
599 }
600 .into();
601
602 buffer.stamp(Pos { x: 0, y: 0 }, cell.clone(), StampMode::Below, None);
603
604 assert_eq!(buffer.cell(Pos { x: 0, y: 0 }), Some(&cell));
605 }
606
607 /// The example named "The two modes on identical input": with `Below`, the target's own base
608 /// stroke and already-decided arms win, and only the side it left `Unset` is written.
609 #[test]
610 fn below_writes_only_the_targets_unset_sides_and_leaves_the_base_stroke_alone() {
611 let mut buffer = Buffer::new(Window {
612 origin: Pos { x: 0, y: 0 },
613 size: Size {
614 width: 1,
615 height: 1,
616 },
617 });
618 buffer.stamp(
619 Pos { x: 0, y: 0 },
620 StrokeCell {
621 base: light(),
622 top: Arm::Unset,
623 right: Arm::Set(light()),
624 bottom: Arm::Closed,
625 left: Arm::Unset,
626 }
627 .into(),
628 StampMode::Above,
629 None,
630 );
631
632 buffer.stamp(
633 Pos { x: 0, y: 0 },
634 StrokeCell {
635 base: Stroke::from("double"),
636 top: Arm::Set(Stroke::from("double")),
637 right: Arm::Closed,
638 bottom: Arm::Set(Stroke::from("double")),
639 left: Arm::Unset,
640 }
641 .into(),
642 StampMode::Below,
643 None,
644 );
645
646 assert_eq!(
647 buffer.cell(Pos { x: 0, y: 0 }),
648 Some(&Cell::from(StrokeCell {
649 base: light(),
650 top: Arm::Set(Stroke::from("double")),
651 right: Arm::Set(light()),
652 bottom: Arm::Closed,
653 left: Arm::Unset,
654 }))
655 );
656 }
657
658 /// The example named "A decided cell ignores a Below stamp": the merge that would produce
659 /// this cell is skipped, and the cell is the same as if it had run.
660 #[test]
661 fn below_onto_a_decided_cell_changes_nothing() {
662 let mut buffer = Buffer::new(Window {
663 origin: Pos { x: 0, y: 0 },
664 size: Size {
665 width: 1,
666 height: 1,
667 },
668 });
669 let decided: Cell = StrokeCell {
670 base: light(),
671 top: Arm::Set(light()),
672 right: Arm::Closed,
673 bottom: Arm::Set(light()),
674 left: Arm::Closed,
675 }
676 .into();
677 buffer.stamp(Pos { x: 0, y: 0 }, decided.clone(), StampMode::Above, None);
678
679 buffer.stamp(
680 Pos { x: 0, y: 0 },
681 StrokeCell {
682 base: Stroke::from("double"),
683 top: Arm::Set(Stroke::from("double")),
684 right: Arm::Set(Stroke::from("double")),
685 bottom: Arm::Set(Stroke::from("double")),
686 left: Arm::Set(Stroke::from("double")),
687 }
688 .into(),
689 StampMode::Below,
690 None,
691 );
692
693 assert_eq!(buffer.cell(Pos { x: 0, y: 0 }), Some(&decided));
694 }
695
696 /// The mirror of the example above: a fully decided `Above` stamp wins
697 /// outright, whatever the target already had — the merge that would produce this cell is
698 /// skipped, and the cell is the same as if it had run.
699 #[test]
700 fn above_with_a_decided_stamp_wins_outright() {
701 let mut buffer = Buffer::new(Window {
702 origin: Pos { x: 0, y: 0 },
703 size: Size {
704 width: 1,
705 height: 1,
706 },
707 });
708 buffer.stamp(
709 Pos { x: 0, y: 0 },
710 StrokeCell {
711 base: Stroke::from("double"),
712 top: Arm::Unset,
713 right: Arm::Set(Stroke::from("double")),
714 bottom: Arm::Unset,
715 left: Arm::Closed,
716 }
717 .into(),
718 StampMode::Above,
719 None,
720 );
721
722 let decided: Cell = StrokeCell {
723 base: light(),
724 top: Arm::Set(light()),
725 right: Arm::Closed,
726 bottom: Arm::Set(light()),
727 left: Arm::Closed,
728 }
729 .into();
730 buffer.stamp(Pos { x: 0, y: 0 }, decided.clone(), StampMode::Above, None);
731
732 assert_eq!(buffer.cell(Pos { x: 0, y: 0 }), Some(&decided));
733 }
734
735 /// The example named "The two orders agree": three figures overlap at one position, front to
736 /// back with `Below` and back to front with `Above`, and the two drawing orders are
737 /// equivalent, so the resulting cell must be the same either way.
738 #[test]
739 fn front_to_back_with_below_equals_back_to_front_with_above() {
740 let pos = Pos { x: 0, y: 0 };
741 let a = || {
742 Cell::from(StrokeCell {
743 base: Stroke::from("double"),
744 top: Arm::Unset,
745 right: Arm::Set(Stroke::from("double")),
746 bottom: Arm::Unset,
747 left: Arm::Closed,
748 })
749 };
750 let b = || {
751 Cell::from(StrokeCell {
752 base: Stroke::from("light"),
753 top: Arm::Set(Stroke::from("light")),
754 right: Arm::Closed,
755 bottom: Arm::Unset,
756 left: Arm::Set(Stroke::from("light")),
757 })
758 };
759 let c = || {
760 Cell::from(StrokeCell {
761 base: Stroke::from("heavy"),
762 top: Arm::Closed,
763 right: Arm::Set(Stroke::from("heavy")),
764 bottom: Arm::Set(Stroke::from("heavy")),
765 left: Arm::Set(Stroke::from("heavy")),
766 })
767 };
768
769 let mut front_to_back = Buffer::new(Window {
770 origin: Pos { x: 0, y: 0 },
771 size: Size {
772 width: 1,
773 height: 1,
774 },
775 });
776 front_to_back.stamp(pos, a(), StampMode::Below, None);
777 front_to_back.stamp(pos, b(), StampMode::Below, None);
778 front_to_back.stamp(pos, c(), StampMode::Below, None);
779
780 let mut back_to_front = Buffer::new(Window {
781 origin: Pos { x: 0, y: 0 },
782 size: Size {
783 width: 1,
784 height: 1,
785 },
786 });
787 back_to_front.stamp(pos, c(), StampMode::Above, None);
788 back_to_front.stamp(pos, b(), StampMode::Above, None);
789 back_to_front.stamp(pos, a(), StampMode::Above, None);
790
791 let expected = Some(&Cell::from(StrokeCell {
792 base: Stroke::from("double"),
793 top: Arm::Set(Stroke::from("light")),
794 right: Arm::Set(Stroke::from("double")),
795 bottom: Arm::Set(Stroke::from("heavy")),
796 left: Arm::Closed,
797 }));
798 assert_eq!(front_to_back.cell(pos), back_to_front.cell(pos));
799 assert_eq!(front_to_back.cell(pos), expected);
800 }
801
802 /// _Stamping_'s row "Arms, stamping a literal" under `Above`: the literal wins outright.
803 #[test]
804 fn a_literal_stamped_above_a_stroke_cell_replaces_it() {
805 let mut buffer = Buffer::new(Window {
806 origin: Pos { x: 0, y: 0 },
807 size: Size {
808 width: 1,
809 height: 1,
810 },
811 });
812 buffer.stamp(
813 Pos { x: 0, y: 0 },
814 StrokeCell {
815 base: light(),
816 top: Arm::Unset,
817 right: Arm::Set(light()),
818 bottom: Arm::Set(light()),
819 left: Arm::Set(light()),
820 }
821 .into(),
822 StampMode::Above,
823 None,
824 );
825
826 let literal = Cell::Literal(glyph("A"));
827 buffer.stamp(Pos { x: 0, y: 0 }, literal.clone(), StampMode::Above, None);
828
829 assert_eq!(buffer.cell(Pos { x: 0, y: 0 }), Some(&literal));
830 }
831
832 /// _Stamping_'s row "Arms, stamping a literal" under `Below`: the target stays a stroke cell,
833 /// and the sides it left `Unset` come out `Closed`, inherited from the literal.
834 #[test]
835 fn a_literal_stamped_below_a_stroke_cell_closes_its_unset_sides() {
836 let mut buffer = Buffer::new(Window {
837 origin: Pos { x: 0, y: 0 },
838 size: Size {
839 width: 1,
840 height: 1,
841 },
842 });
843 buffer.stamp(
844 Pos { x: 0, y: 0 },
845 StrokeCell {
846 base: light(),
847 top: Arm::Unset,
848 right: Arm::Set(light()),
849 bottom: Arm::Closed,
850 left: Arm::Set(light()),
851 }
852 .into(),
853 StampMode::Above,
854 None,
855 );
856
857 buffer.stamp(
858 Pos { x: 0, y: 0 },
859 Cell::Literal(glyph("A")),
860 StampMode::Below,
861 None,
862 );
863
864 assert_eq!(
865 buffer.cell(Pos { x: 0, y: 0 }),
866 Some(&Cell::from(StrokeCell {
867 base: light(),
868 top: Arm::Closed,
869 right: Arm::Set(light()),
870 bottom: Arm::Closed,
871 left: Arm::Set(light()),
872 }))
873 );
874 }
875
876 /// _Stamping_'s row "A literal, stamping arms" under `Above`: the target becomes a stroke
877 /// cell, and the sides the stamp leaves `Unset` come out `Closed`, inherited from the literal
878 /// it replaced.
879 #[test]
880 fn a_stroke_cell_stamped_above_a_literal_inherits_its_closed_sides_where_it_abstains() {
881 let mut buffer = Buffer::new(Window {
882 origin: Pos { x: 0, y: 0 },
883 size: Size {
884 width: 1,
885 height: 1,
886 },
887 });
888 buffer.stamp(
889 Pos { x: 0, y: 0 },
890 Cell::Literal(glyph("A")),
891 StampMode::Above,
892 None,
893 );
894
895 buffer.stamp(
896 Pos { x: 0, y: 0 },
897 StrokeCell {
898 base: light(),
899 top: Arm::Unset,
900 right: Arm::Set(light()),
901 bottom: Arm::Closed,
902 left: Arm::Set(light()),
903 }
904 .into(),
905 StampMode::Above,
906 None,
907 );
908
909 assert_eq!(
910 buffer.cell(Pos { x: 0, y: 0 }),
911 Some(&Cell::from(StrokeCell {
912 base: light(),
913 top: Arm::Closed,
914 right: Arm::Set(light()),
915 bottom: Arm::Closed,
916 left: Arm::Set(light()),
917 }))
918 );
919 }
920
921 /// _Stamping_'s row "A literal, stamping arms" under `Below`: the literal target is decided,
922 /// so it is left alone.
923 #[test]
924 fn a_stroke_cell_stamped_below_a_literal_changes_nothing() {
925 let mut buffer = Buffer::new(Window {
926 origin: Pos { x: 0, y: 0 },
927 size: Size {
928 width: 1,
929 height: 1,
930 },
931 });
932 let literal = Cell::Literal(glyph("A"));
933 buffer.stamp(Pos { x: 0, y: 0 }, literal.clone(), StampMode::Above, None);
934
935 buffer.stamp(
936 Pos { x: 0, y: 0 },
937 StrokeCell {
938 base: Stroke::from("double"),
939 top: Arm::Set(Stroke::from("double")),
940 right: Arm::Set(Stroke::from("double")),
941 bottom: Arm::Set(Stroke::from("double")),
942 left: Arm::Set(Stroke::from("double")),
943 }
944 .into(),
945 StampMode::Below,
946 None,
947 );
948
949 assert_eq!(buffer.cell(Pos { x: 0, y: 0 }), Some(&literal));
950 }
951
952 /// _Stamping_'s row "A literal, stamping a literal" under `Above`: the incoming literal wins
953 /// outright.
954 #[test]
955 fn a_literal_stamped_above_a_literal_replaces_it() {
956 let mut buffer = Buffer::new(Window {
957 origin: Pos { x: 0, y: 0 },
958 size: Size {
959 width: 1,
960 height: 1,
961 },
962 });
963 buffer.stamp(
964 Pos { x: 0, y: 0 },
965 Cell::Literal(glyph("A")),
966 StampMode::Above,
967 None,
968 );
969
970 let incoming = Cell::Literal(glyph("B"));
971 buffer.stamp(Pos { x: 0, y: 0 }, incoming.clone(), StampMode::Above, None);
972
973 assert_eq!(buffer.cell(Pos { x: 0, y: 0 }), Some(&incoming));
974 }
975
976 /// _Stamping_'s row "A literal, stamping a literal" under `Below`: the target is decided, so
977 /// it is left alone.
978 #[test]
979 fn a_literal_stamped_below_a_literal_changes_nothing() {
980 let mut buffer = Buffer::new(Window {
981 origin: Pos { x: 0, y: 0 },
982 size: Size {
983 width: 1,
984 height: 1,
985 },
986 });
987 let original = Cell::Literal(glyph("A"));
988 buffer.stamp(Pos { x: 0, y: 0 }, original.clone(), StampMode::Above, None);
989
990 buffer.stamp(
991 Pos { x: 0, y: 0 },
992 Cell::Literal(glyph("B")),
993 StampMode::Below,
994 None,
995 );
996
997 assert_eq!(buffer.cell(Pos { x: 0, y: 0 }), Some(&original));
998 }
999
1000 /// Rule 3 — a symbol cell composes exactly as a literal does. Above, it replaces what it
1001 /// meets; below, a stroke stamp leaves it alone; two of them compose as two literals, with the
1002 /// top one winning.
1003 #[test]
1004 fn a_symbol_cell_composes_as_a_literal_does() {
1005 let pos = Pos { x: 0, y: 0 };
1006
1007 let mut named = a_buffer(pos, 1, 1);
1008 named.stamp(pos, vertical_run(), StampMode::Above, None);
1009 named.stamp(pos, a_symbol_cell(), StampMode::Above, None);
1010
1011 let mut spelled = a_buffer(pos, 1, 1);
1012 spelled.stamp(pos, vertical_run(), StampMode::Above, None);
1013 spelled.stamp(pos, Cell::Literal(glyph("▲")), StampMode::Above, None);
1014
1015 assert_eq!(
1016 named.cell(pos),
1017 Some(&a_symbol_cell()),
1018 "the symbol replaces the stroke cell it was stamped over"
1019 );
1020 assert_eq!(spelled.cell(pos), Some(&Cell::Literal(glyph("▲"))));
1021
1022 named.stamp(pos, horizontal_run(), StampMode::Below, None);
1023 spelled.stamp(pos, horizontal_run(), StampMode::Below, None);
1024
1025 assert_eq!(
1026 named.cell(pos),
1027 Some(&a_symbol_cell()),
1028 "a stamp behind changes neither opaque kind"
1029 );
1030 assert_eq!(spelled.cell(pos), Some(&Cell::Literal(glyph("▲"))));
1031
1032 let incoming = Cell::Symbol(SymbolCell {
1033 symbol: Symbol::Circle,
1034 style: SymbolStyleName::from("solid"),
1035 });
1036 named.stamp(pos, incoming.clone(), StampMode::Above, None);
1037
1038 assert_eq!(
1039 named.cell(pos),
1040 Some(&incoming),
1041 "two symbol cells compose as two literals do, the top one winning"
1042 );
1043 }
1044
1045 /// The example named "Why the closed sides matter": a literal between two stroke figures in a
1046 /// stack. Without the literal's four `Closed` sides the two orders would disagree — the front
1047 /// figure would reach through it and fill a side the other order had already sealed.
1048 #[test]
1049 fn front_to_back_with_below_equals_back_to_front_with_above_with_a_literal_in_the_middle() {
1050 let pos = Pos { x: 0, y: 0 };
1051 let s1 = || {
1052 Cell::from(StrokeCell {
1053 base: light(),
1054 top: Arm::Unset,
1055 right: Arm::Set(light()),
1056 bottom: Arm::Set(light()),
1057 left: Arm::Set(light()),
1058 })
1059 };
1060 let literal = || Cell::Literal(glyph("A"));
1061 let s2 = || {
1062 Cell::from(StrokeCell {
1063 base: light(),
1064 top: Arm::Set(light()),
1065 right: Arm::Set(light()),
1066 bottom: Arm::Set(light()),
1067 left: Arm::Set(light()),
1068 })
1069 };
1070
1071 let mut front_to_back = Buffer::new(Window {
1072 origin: Pos { x: 0, y: 0 },
1073 size: Size {
1074 width: 1,
1075 height: 1,
1076 },
1077 });
1078 front_to_back.stamp(pos, s1(), StampMode::Below, None);
1079 front_to_back.stamp(pos, literal(), StampMode::Below, None);
1080 front_to_back.stamp(pos, s2(), StampMode::Below, None);
1081
1082 let mut back_to_front = Buffer::new(Window {
1083 origin: Pos { x: 0, y: 0 },
1084 size: Size {
1085 width: 1,
1086 height: 1,
1087 },
1088 });
1089 back_to_front.stamp(pos, s2(), StampMode::Above, None);
1090 back_to_front.stamp(pos, literal(), StampMode::Above, None);
1091 back_to_front.stamp(pos, s1(), StampMode::Above, None);
1092
1093 let expected = Some(&Cell::from(StrokeCell {
1094 base: light(),
1095 top: Arm::Closed,
1096 right: Arm::Set(light()),
1097 bottom: Arm::Set(light()),
1098 left: Arm::Set(light()),
1099 }));
1100 assert_eq!(front_to_back.cell(pos), back_to_front.cell(pos));
1101 assert_eq!(front_to_back.cell(pos), expected);
1102 }
1103
1104 /// The record is kept beside the cell and never inside it, so two cells that render alike stay
1105 /// one cell value whoever wrote them.
1106 ///
1107 /// **The two positions below are given the same literal by two different identities**, which is
1108 /// the shipped demonstration's own arrangement — its first two entries are two filled boxes
1109 /// whose fills print the same character at cells belonging to different shapes. The claim is
1110 /// the equality *and* the difference: the cells compare equal as cells, and the record says
1111 /// `1` wrote one and `2` wrote the other. An identity carried inside the cell would have made
1112 /// that equality false, which is the whole reason the record is a second map.
1113 #[test]
1114 fn two_cells_that_render_the_same_are_the_same_cell_and_the_record_does_not_change_that() {
1115 let mut buffer = a_buffer(Pos { x: 0, y: 0 }, 2, 1);
1116 let first = id(1);
1117 let second = id(2);
1118 let fill = Cell::Literal(glyph("░"));
1119
1120 buffer.stamp(
1121 Pos { x: 0, y: 0 },
1122 fill.clone(),
1123 StampMode::Above,
1124 Some(first),
1125 );
1126 buffer.stamp(
1127 Pos { x: 1, y: 0 },
1128 fill.clone(),
1129 StampMode::Below,
1130 Some(second),
1131 );
1132
1133 assert_eq!(
1134 buffer.cell(Pos { x: 0, y: 0 }),
1135 buffer.cell(Pos { x: 1, y: 0 }),
1136 "the same cell value written by two identities is one cell value"
1137 );
1138 assert_eq!(buffer.cell(Pos { x: 0, y: 0 }), Some(&fill));
1139 assert_eq!(buffer.owner(Pos { x: 0, y: 0 }), Some(first));
1140 assert_eq!(
1141 buffer.owner(Pos { x: 1, y: 0 }),
1142 Some(second),
1143 "the record tells the two apart where the cell cannot"
1144 );
1145 }
1146
1147 /// Every position of an overlap resolves to the front-most shape that decided it.
1148 ///
1149 /// **The arrangement is the spec's first measured window**, a seven-by-five window at `(-3, -2)`
1150 /// holding a light box at `(0, 0)`, a filled double box at `(-2, -1)` written last and therefore
1151 /// in front, and a heavy box at `(6, 3)` lying wholly outside. `3` is in the arrangement on
1152 /// purpose: it stamps nothing, so a buffer that recorded a stamp it did not perform would give
1153 /// it every position in the window.
1154 ///
1155 /// The expectation is **read off the two rectangles rather than sampled from the output**, and
1156 /// every position of the window is asked — 35 of them, not the seven the spec's table happens
1157 /// to name. An unfilled box writes its border alone and a filled one its whole rectangle, so
1158 /// what decides each position follows from where the two stand, with no reference to what the
1159 /// record says.
1160 #[test]
1161 fn in_an_overlap_every_position_resolves_to_the_front_most_shape_that_decided_it() {
1162 let origin = Pos { x: -3, y: -2 };
1163 let mut buffer = a_buffer(origin, 7, 5);
1164 let back = id(1);
1165 let front = id(2);
1166 let outside = id(3);
1167
1168 // Front to back, which is the order `Diagram::draw` uses: the front-most shape stamps
1169 // first and decides a shared cell before anything behind it. `3` is drawn last of the three
1170 // because it is the back-most of the three, and it writes nothing either way.
1171 a_box(Pos { x: -2, y: -1 }, double(), Some("░")).draw(&mut FigureLayer::stamped_by(
1172 &mut buffer,
1173 StampMode::Below,
1174 front,
1175 ));
1176 a_box(Pos { x: 0, y: 0 }, light(), None).draw(&mut FigureLayer::stamped_by(
1177 &mut buffer,
1178 StampMode::Below,
1179 back,
1180 ));
1181 a_box(Pos { x: 6, y: 3 }, heavy(), None).draw(&mut FigureLayer::stamped_by(
1182 &mut buffer,
1183 StampMode::Below,
1184 outside,
1185 ));
1186
1187 // `2` is filled, so it decides every position of its own rectangle; `1` is not, so it
1188 // decides only its border; `3` is outside the window and decides nothing at all.
1189 let decided_by_front = |at: Pos| (-2..=1).contains(&at.x) && (-1..=1).contains(&at.y);
1190 let decided_by_back = |at: Pos| {
1191 (0..=3).contains(&at.x)
1192 && (0..=2).contains(&at.y)
1193 && (at.x == 0 || at.x == 3 || at.y == 0 || at.y == 2)
1194 };
1195
1196 for at in every_position(origin, 7, 5) {
1197 let expected = if decided_by_front(at) {
1198 Some(front)
1199 } else if decided_by_back(at) {
1200 Some(back)
1201 } else {
1202 None
1203 };
1204 assert_eq!(
1205 buffer.owner(at),
1206 expected,
1207 "the record names {expected:?} at {at:?}"
1208 );
1209 }
1210 }
1211
1212 /// A stamp that changes nothing at a position takes nothing.
1213 ///
1214 /// `2` is stamped `Below` onto a cell `1` already decided, which is the one case where the
1215 /// merge provably reproduces the target and the buffer returns before writing. The cell is
1216 /// asserted unchanged as well as the record, because "changed nothing" and "changed the cell
1217 /// but not the record" are different defects and only one of them is this rule.
1218 ///
1219 /// **`Below` is the mode this rule is about**, and the reason is the one the test beside it
1220 /// pins: an `Above` stamp that reproduces the cell it lands on still takes, because under
1221 /// `Above` taking is how a shape in front of everything claims a cell the shapes behind it
1222 /// also wrote. See `the_record_is_the_same_whether_a_diagram_is_drawn_front_to_back_or_back_to_front`.
1223 #[test]
1224 fn a_shape_that_stamps_a_position_without_changing_anything_there_does_not_take_it() {
1225 let mut buffer = a_buffer(Pos { x: 0, y: 0 }, 1, 1);
1226 let first = id(1);
1227 let second = id(2);
1228
1229 buffer.stamp(Pos { x: 0, y: 0 }, decided(), StampMode::Above, Some(first));
1230 let before = buffer.cell(Pos { x: 0, y: 0 }).cloned();
1231 assert!(
1232 before.is_some(),
1233 "the first stamp wrote the cell this test reads back"
1234 );
1235
1236 buffer.stamp(
1237 Pos { x: 0, y: 0 },
1238 Cell::from(StrokeCell {
1239 base: heavy(),
1240 top: Arm::Set(heavy()),
1241 right: Arm::Set(heavy()),
1242 bottom: Arm::Set(heavy()),
1243 left: Arm::Set(heavy()),
1244 }),
1245 StampMode::Below,
1246 Some(second),
1247 );
1248
1249 assert_eq!(
1250 buffer.cell(Pos { x: 0, y: 0 }).cloned(),
1251 before,
1252 "a stamp below a decided cell reproduces it exactly, so the cell cannot have moved"
1253 );
1254 assert_eq!(
1255 buffer.owner(Pos { x: 0, y: 0 }),
1256 Some(first),
1257 "the stamp behind changed nothing there, so it owns nothing there"
1258 );
1259 }
1260
1261 /// A stamp that decides no side takes nothing, and the position keeps whoever holds it.
1262 ///
1263 /// **Both halves, and the order matters**: the ownerless first stamp puts a cell there that
1264 /// decides nothing, and the second stamp — which does decide two sides, and so *does* change
1265 /// the cell — is not allowed to take it. That second half is what separates this rule from the
1266 /// one beside it, and it is the only way to catch an implementation that records "whoever
1267 /// stamped last" rather than "whoever decided".
1268 #[test]
1269 fn a_stamp_that_decides_no_side_takes_nothing_and_leaves_the_owner_it_found() {
1270 let mut buffer = a_buffer(Pos { x: 0, y: 0 }, 1, 1);
1271 let nobody = id(1);
1272 let second = id(2);
1273
1274 // A stamp by nobody at an undefined position writes a cell and records nothing, which is
1275 // the starting state this rule needs: a position holding a cell and owned by nobody.
1276 buffer.stamp(
1277 Pos { x: 0, y: 0 },
1278 deciding_nothing(),
1279 StampMode::Above,
1280 Some(nobody),
1281 );
1282 assert_eq!(
1283 buffer.owner(Pos { x: 0, y: 0 }),
1284 None,
1285 "a cell deciding no side is written like any other and owns nothing"
1286 );
1287
1288 buffer.stamp(
1289 Pos { x: 0, y: 0 },
1290 horizontal_run(),
1291 StampMode::Above,
1292 Some(second),
1293 );
1294
1295 // And the same stamp onto a position somebody *does* hold leaves that owner standing.
1296 let mut held = a_buffer(Pos { x: 0, y: 0 }, 1, 1);
1297 let holder = id(3);
1298 held.stamp(
1299 Pos { x: 0, y: 0 },
1300 horizontal_run(),
1301 StampMode::Above,
1302 Some(holder),
1303 );
1304 held.stamp(
1305 Pos { x: 0, y: 0 },
1306 deciding_nothing(),
1307 StampMode::Above,
1308 Some(second),
1309 );
1310
1311 assert_eq!(
1312 held.cell(Pos { x: 0, y: 0 }),
1313 Some(&horizontal_run()),
1314 "a stamp deciding no side changes nothing whatever it lands on"
1315 );
1316 assert_eq!(
1317 held.owner(Pos { x: 0, y: 0 }),
1318 Some(holder),
1319 "the owner it found is the owner it leaves"
1320 );
1321 }
1322
1323 /// A shape behind cannot take a cell from one in front of it, however much it decides there.
1324 ///
1325 /// **The cell behind really does decide more than the cell in front** — it fills the two sides
1326 /// `1` abstained on — and the record does not move. That is the whole claim, and it is the one
1327 /// a reader cannot check by looking at the picture: the glyph at `(0, 0)` reads `┼`, four sides
1328 /// joined, and nothing about it says that one shape wrote two of them and the other two.
1329 #[test]
1330 fn a_shape_behind_cannot_take_a_cell_from_one_in_front_of_it_however_much_it_decides() {
1331 let mut buffer = a_buffer(Pos { x: 0, y: 0 }, 1, 1);
1332 let front = id(1);
1333 let behind = id(2);
1334
1335 buffer.stamp(
1336 Pos { x: 0, y: 0 },
1337 vertical_run(),
1338 StampMode::Above,
1339 Some(front),
1340 );
1341 buffer.stamp(
1342 Pos { x: 0, y: 0 },
1343 horizontal_run(),
1344 StampMode::Below,
1345 Some(behind),
1346 );
1347
1348 assert_eq!(
1349 buffer.cell(Pos { x: 0, y: 0 }),
1350 Some(&decided()),
1351 "the shape behind decided the two sides the one in front left open"
1352 );
1353 assert_eq!(
1354 buffer.owner(Pos { x: 0, y: 0 }),
1355 Some(front),
1356 "and the record names the one in front, because it reached the cell first"
1357 );
1358 }
1359
1360 /// The record is the same whether a diagram is drawn front to back or back to front.
1361 ///
1362 /// **The same two figures in the same two orders, compared on the record as well as on the
1363 /// cells**, and asked about at every offset of the window rather than at one: a record that
1364 /// agreed everywhere except one position would be a different record, and the picture would not
1365 /// show it.
1366 ///
1367 /// The arrangement is the one where the two orders disagree in the cells, not only in the
1368 /// record — a filled double box over a light box, where the front one's fill and the one behind
1369 /// it both write the two cells they share.
1370 #[test]
1371 fn the_record_is_the_same_whether_a_diagram_is_drawn_front_to_back_or_back_to_front() {
1372 let origin = Pos { x: 0, y: 0 };
1373 let back = id(1);
1374 let front = id(2);
1375
1376 let mut front_to_back = a_buffer(origin, 6, 4);
1377 a_box(Pos { x: 2, y: 1 }, double(), Some("▓")).draw(&mut FigureLayer::stamped_by(
1378 &mut front_to_back,
1379 StampMode::Below,
1380 front,
1381 ));
1382 a_box(Pos { x: 0, y: 0 }, light(), Some("░")).draw(&mut FigureLayer::stamped_by(
1383 &mut front_to_back,
1384 StampMode::Below,
1385 back,
1386 ));
1387
1388 let mut back_to_front = a_buffer(origin, 6, 4);
1389 a_box(Pos { x: 0, y: 0 }, light(), Some("░")).draw(&mut FigureLayer::stamped_by(
1390 &mut back_to_front,
1391 StampMode::Above,
1392 back,
1393 ));
1394 a_box(Pos { x: 2, y: 1 }, double(), Some("▓")).draw(&mut FigureLayer::stamped_by(
1395 &mut back_to_front,
1396 StampMode::Above,
1397 front,
1398 ));
1399
1400 for at in every_position(origin, 6, 4) {
1401 assert_eq!(
1402 front_to_back.cell(at),
1403 back_to_front.cell(at),
1404 "the two orders disagree about the cell at {at:?}"
1405 );
1406 assert_eq!(
1407 front_to_back.owner(at),
1408 back_to_front.owner(at),
1409 "the two orders disagree about who owns the position {at:?}"
1410 );
1411 }
1412
1413 // And the two cells the boxes share are `2`'s in both, which is what "the same record"
1414 // has to mean rather than merely agreeing.
1415 for at in [Pos { x: 2, y: 1 }, Pos { x: 3, y: 2 }] {
1416 assert_eq!(
1417 front_to_back.owner(at),
1418 Some(front),
1419 "the shape in front owns the cell it decided"
1420 );
1421 }
1422 }
1423
1424 /// `owner` answers for a position, and `None` where nobody holds one.
1425 ///
1426 /// **Every position of a window and the run past each of its two edges**, which is what makes
1427 /// the `None` a claim about the record rather than about one position somebody happened to
1428 /// leave blank. The window's origin is `(-1, -1)`, so a position and the offset that reaches it
1429 /// are visibly different numbers — and this asks by position, as the interface does.
1430 #[test]
1431 fn owner_answers_for_a_position_and_none_for_one_nobody_holds() {
1432 let origin = Pos { x: -1, y: -1 };
1433 let mut buffer = a_buffer(origin, 2, 2);
1434 let writer = id(1);
1435
1436 buffer.stamp(
1437 Pos { x: 0, y: 0 },
1438 decided(),
1439 StampMode::Above,
1440 Some(writer),
1441 );
1442
1443 for at in every_position(origin, 2, 2) {
1444 let written = at == (Pos { x: 0, y: 0 });
1445 assert_eq!(
1446 buffer.owner(at),
1447 written.then_some(writer),
1448 "{at:?} is the one position written, and only it is owned"
1449 );
1450 }
1451
1452 // And the run past each of the two edges, which is what makes the `None` above a claim
1453 // about the record rather than about one position somebody left blank.
1454 for at in [
1455 Pos { x: 1, y: 0 },
1456 Pos { x: -1, y: 1 },
1457 Pos { x: 1, y: 1 },
1458 Pos {
1459 x: i16::MAX,
1460 y: i16::MAX,
1461 },
1462 ] {
1463 assert_eq!(
1464 buffer.owner(at),
1465 None,
1466 "{at:?} is outside a two-by-two window"
1467 );
1468 }
1469 }
1470
1471 /// A caller that adds the window's origin to the position it already has asks about another
1472 /// cell.
1473 ///
1474 /// **The window's origin is `(-2, -1)` and exactly one position in it is owned**, so the sum
1475 /// and the position land on different cells and the wrong one is asked about. The position
1476 /// [`Window::position`](crate::Window::position) hands back already carries the origin; adding
1477 /// it a second time reaches a cell nobody clicked, and `owner` answers truthfully about that
1478 /// one.
1479 #[test]
1480 fn a_caller_that_adds_the_windows_origin_to_a_click_asks_about_another_cell() {
1481 let origin = Pos { x: -2, y: -1 };
1482 let mut buffer = a_buffer(origin, 4, 4);
1483 let writer = id(1);
1484 let clicked = Offset { x: 2, y: 3 };
1485
1486 buffer.stamp(
1487 Pos { x: 0, y: 2 },
1488 decided(),
1489 StampMode::Above,
1490 Some(writer),
1491 );
1492
1493 let at = buffer
1494 .window()
1495 .position(clicked)
1496 .expect("the click reaches a position");
1497 assert_eq!(
1498 buffer.owner(at),
1499 Some(writer),
1500 "the click reaches the cell as written"
1501 );
1502 assert_eq!(
1503 buffer.owner(Pos {
1504 x: at.x + origin.x,
1505 y: at.y + origin.y,
1506 }),
1507 None,
1508 "adding the origin a second time asks about a cell nobody clicked"
1509 );
1510 }
1511
1512 /// A window whose origin is `i16::MIN` carries every offset, and none of the positions they
1513 /// reach is owned when the drawing never landed inside it.
1514 ///
1515 /// **`i16::MIN` is a real origin rather than a theoretical one**, and it is the ordinary shape
1516 /// of the question: a window far enough to the left that the whole plane to its right is a run
1517 /// of offsets. `checked_add_unsigned` is what answers, and adding an offset that far left never
1518 /// overflows — the largest offset there is a position too.
1519 #[test]
1520 fn a_window_at_i16_min_carries_every_offset_and_owns_nothing_outside_it() {
1521 let mut buffer = a_buffer(Pos { x: i16::MIN, y: 0 }, 4, 3);
1522
1523 a_box(Pos { x: 0, y: 0 }, light(), Some("░")).draw(&mut FigureLayer::stamped_by(
1524 &mut buffer,
1525 StampMode::Below,
1526 id(1),
1527 ));
1528
1529 let window = buffer.window();
1530 for y in 0..3 {
1531 for x in 0..5 {
1532 let at = window
1533 .position(Offset { x, y })
1534 .expect("a window at i16::MIN carries every offset");
1535 assert_eq!(
1536 buffer.owner(at),
1537 None,
1538 "{at:?} is not a cell the box wrote, which was drawn far from this window"
1539 );
1540 }
1541 }
1542 // And the run past the window's width: the offset is representable and so is the position
1543 // it reaches, and the answer is still nobody.
1544 let past = window
1545 .position(Offset { x: u16::MAX, y: 0 })
1546 .expect("a window at i16::MIN carries every offset");
1547 assert_eq!(buffer.owner(past), None);
1548 assert_eq!(
1549 buffer.cell(Pos { x: 0, y: 0 }),
1550 None,
1551 "and no cell was written there either, so nothing is owned in the first place"
1552 );
1553 }
1554
1555 /// A shape drawn by nobody owns nothing, even where it writes.
1556 ///
1557 /// **A whole box rather than one cell**, so that the twenty cells of its border and interior
1558 /// are all asked and a record that appeared on any of them fails: `FigureLayer::new` is the door the
1559 /// core's own gallery and every caller's own drawing go through, and it records nothing at all.
1560 /// The second half draws the same box through a layer bound to an identity, which is what makes
1561 /// the `None` above about the door rather than about a box that writes nothing.
1562 #[test]
1563 fn a_shape_drawn_by_nobody_owns_nothing_even_where_it_writes() {
1564 let mut by_nobody = a_buffer(Pos { x: 0, y: 0 }, 4, 3);
1565 a_box(Pos { x: 0, y: 0 }, light(), Some("░"))
1566 .draw(&mut FigureLayer::new(&mut by_nobody, StampMode::Above));
1567
1568 let mut by_somebody = a_buffer(Pos { x: 0, y: 0 }, 4, 3);
1569 let writer = id(1);
1570 a_box(Pos { x: 0, y: 0 }, light(), Some("░")).draw(&mut FigureLayer::stamped_by(
1571 &mut by_somebody,
1572 StampMode::Above,
1573 writer,
1574 ));
1575
1576 let mut wrote_something = false;
1577 for at in every_position(Pos { x: 0, y: 0 }, 4, 3) {
1578 assert!(by_nobody.cell(at).is_some(), "the box wrote {at:?}");
1579 wrote_something = true;
1580 assert_eq!(
1581 by_nobody.owner(at),
1582 None,
1583 "a shape drawn by nobody owns nothing at {at:?}"
1584 );
1585 assert_eq!(
1586 by_somebody.owner(at),
1587 Some(writer),
1588 "the same box under an identity owns every cell it decided"
1589 );
1590 }
1591 assert!(
1592 wrote_something,
1593 "the box above wrote nothing, so this test proved nothing"
1594 );
1595 }
1596}