paint.rs
23.8 kB · rust · 769 lines
1use super::cell::{moore, Cell};2use super::colors::{gradient, Color};3use super::colors::{4 BLACK, BLUE, BROWN, CYAN, GRAY, GREEN, INDIGO, MINT, ORANGE, PINK, PURPLE, RED, TEAL, WHITE,5 YELLOW,6};7use super::enums::Mode;8use super::errors::{value_error, MrlyError, Result};9use super::rng::Rng;10use super::state::{choice, randint, sample, shuffle};11use super::tensor::{Dtype, Tensor};12use serde::{Deserialize, Serialize};13use std::collections::HashMap;1415/// The seven ways a paint distributes its colors over a cell.16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]17pub enum Edition {18 /// One color per cell type.19 Simple,20 /// Color by cell index.21 Index,22 /// Color by concentric layer.23 Layers,24 /// Color by neighbor count.25 Neighbors,26 /// Color by row.27 Rows,28 /// Color by column.29 Columns,30 /// A random color per cell.31 Random,32}3334/// The fifteen named inks a paint draws from.35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]36pub enum Ink {37 /// Black (0, 0, 0).38 Black,39 /// White (255, 255, 255).40 White,41 /// Red (255, 61, 64).42 Red,43 /// Orange (255, 143, 44).44 Orange,45 /// Yellow (255, 209, 0).46 Yellow,47 /// Green (50, 204, 88).48 Green,49 /// Mint (0, 209, 187).50 Mint,51 /// Teal (0, 202, 216).52 Teal,53 /// Cyan (30, 201, 243).54 Cyan,55 /// Blue (0, 140, 255).56 Blue,57 /// Indigo (103, 104, 250).58 Indigo,59 /// Purple (211, 50, 233).60 Purple,61 /// The palette pink.62 Pink,63 /// Brown (177, 132, 98).64 Brown,65 /// Gray (142, 142, 147).66 Gray,67}6869/// The two ways secondary colors are drawn.70#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]71pub enum Scheme {72 /// Distinct secondary inks.73 Multicolor,74 /// One secondary ink stepped through shades.75 Multitone,76}7778/// The side of the figure the primary ink lands on.79#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]80pub enum Target {81 /// The primary on the filled cells, secondaries on the empty.82 Fill,83 /// The primary on the empty cells, secondaries on the filled.84 Void,85}8687impl Edition {88 /// Returns every edition in canonical order.89 pub fn all() -> [Edition; 7] {90 [91 Edition::Simple,92 Edition::Index,93 Edition::Layers,94 Edition::Neighbors,95 Edition::Rows,96 Edition::Columns,97 Edition::Random,98 ]99 }100 /// Returns the cell-painting mode this edition renders with.101 pub fn mode(self) -> Mode {102 match self {103 Edition::Simple => Mode::Type,104 Edition::Index => Mode::Index,105 Edition::Layers => Mode::Tag,106 Edition::Neighbors => Mode::Tag,107 Edition::Rows => Mode::Row,108 Edition::Columns => Mode::Column,109 Edition::Random => Mode::Random,110 }111 }112 /// Returns the edition's display name.113 pub fn name(self) -> &'static str {114 match self {115 Edition::Simple => "Simple",116 Edition::Index => "Index",117 Edition::Layers => "Layers",118 Edition::Neighbors => "Neighbors",119 Edition::Rows => "Rows",120 Edition::Columns => "Columns",121 Edition::Random => "Random",122 }123 }124 /// Parses a display name back into its edition, or an error for an unknown name.125 pub fn parse(name: &str) -> Result<Edition> {126 match name {127 "Simple" => Ok(Edition::Simple),128 "Index" => Ok(Edition::Index),129 "Layers" => Ok(Edition::Layers),130 "Neighbors" => Ok(Edition::Neighbors),131 "Rows" => Ok(Edition::Rows),132 "Columns" => Ok(Edition::Columns),133 "Random" => Ok(Edition::Random),134 other => value_error(format!("unknown edition {other:?}.")),135 }136 }137}138139impl Ink {140 /// Returns the ink's color.141 pub fn color(self) -> Color {142 match self {143 Ink::Black => BLACK,144 Ink::White => WHITE,145 Ink::Red => RED,146 Ink::Orange => ORANGE,147 Ink::Yellow => YELLOW,148 Ink::Green => GREEN,149 Ink::Mint => MINT,150 Ink::Teal => TEAL,151 Ink::Cyan => CYAN,152 Ink::Blue => BLUE,153 Ink::Indigo => INDIGO,154 Ink::Purple => PURPLE,155 Ink::Pink => PINK,156 Ink::Brown => BROWN,157 Ink::Gray => GRAY,158 }159 }160 /// Returns every ink in canonical order.161 pub fn all() -> [Ink; 15] {162 [163 Ink::Black,164 Ink::White,165 Ink::Red,166 Ink::Orange,167 Ink::Yellow,168 Ink::Green,169 Ink::Mint,170 Ink::Teal,171 Ink::Cyan,172 Ink::Blue,173 Ink::Indigo,174 Ink::Purple,175 Ink::Pink,176 Ink::Brown,177 Ink::Gray,178 ]179 }180 /// Returns the ink's display name.181 pub fn name(self) -> &'static str {182 match self {183 Ink::Black => "Black",184 Ink::White => "White",185 Ink::Red => "Red",186 Ink::Orange => "Orange",187 Ink::Yellow => "Yellow",188 Ink::Green => "Green",189 Ink::Mint => "Mint",190 Ink::Teal => "Teal",191 Ink::Cyan => "Cyan",192 Ink::Blue => "Blue",193 Ink::Indigo => "Indigo",194 Ink::Purple => "Purple",195 Ink::Pink => "Pink",196 Ink::Brown => "Brown",197 Ink::Gray => "Gray",198 }199 }200 /// Parses a display name back into its ink, or an error for an unknown name.201 pub fn parse(name: &str) -> Result<Ink> {202 Ink::all()203 .into_iter()204 .find(|ink| ink.name() == name)205 .ok_or_else(|| MrlyError::Value(format!("unknown ink {name:?}.")))206 }207}208209impl Scheme {210 /// Returns both schemes.211 pub fn all() -> [Scheme; 2] {212 [Scheme::Multicolor, Scheme::Multitone]213 }214 /// Returns the scheme's display name.215 pub fn name(self) -> &'static str {216 match self {217 Scheme::Multicolor => "Multicolor",218 Scheme::Multitone => "Multitone",219 }220 }221 /// Parses a display name back into its scheme, or an error for an unknown name.222 pub fn parse(name: &str) -> Result<Scheme> {223 match name {224 "Multicolor" => Ok(Scheme::Multicolor),225 "Multitone" => Ok(Scheme::Multitone),226 other => value_error(format!("unknown scheme {other:?}.")),227 }228 }229}230231impl Target {232 /// Returns both targets.233 pub fn all() -> [Target; 2] {234 [Target::Fill, Target::Void]235 }236 /// Returns the target's display name.237 pub fn name(self) -> &'static str {238 match self {239 Target::Fill => "Fill",240 Target::Void => "Void",241 }242 }243 /// Parses a display name back into its target, or an error for an unknown name.244 pub fn parse(name: &str) -> Result<Target> {245 match name {246 "Fill" => Ok(Target::Fill),247 "Void" => Ok(Target::Void),248 other => value_error(format!("unknown target {other:?}.")),249 }250 }251}252253const LEVELS: [u8; 2] = [33, 66];254255/// The constraints a caller may put on a random paint.256#[derive(Clone, Debug, Default)]257pub struct Config {258 /// The editions allowed, or None for all seven.259 pub editions: Option<Vec<Edition>>,260 /// The primary inks allowed, or None for black and white.261 pub primaries: Option<Vec<Ink>>,262 /// The forced target, or None for a coin flip.263 pub target: Option<Target>,264}265266/// A complete coloring recipe for one cell.267#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]268pub struct Paint {269 /// The coloring edition.270 pub edition: Edition,271 /// The secondary color scheme.272 pub scheme: Scheme,273 /// The side the primary ink lands on.274 pub target: Target,275 /// The primary ink.276 pub primary: Ink,277 /// The secondary inks.278 pub secondary: Vec<Ink>,279 /// The shade indices of a multitone ramp.280 pub shades: Vec<usize>,281}282283impl Paint {284 /// Builds a black-primary, fill-target, multicolor paint for an edition.285 pub fn new(edition: Edition) -> Paint {286 Paint {287 edition,288 scheme: Scheme::Multicolor,289 target: Target::Fill,290 primary: Ink::Black,291 secondary: Vec::new(),292 shades: Vec::new(),293 }294 }295 /// Returns true for the Simple edition.296 pub fn is_simple(&self) -> bool {297 self.edition == Edition::Simple298 }299 fn wipe(&mut self) {300 self.secondary.clear();301 self.shades.clear();302 }303}304305/// Draws a random edition from the allowed list, or from all seven.306pub fn random_edition(editions: Option<&[Edition]>) -> Edition {307 match editions {308 Some(list) if !list.is_empty() => choice(list),309 _ => choice(&Edition::all()),310 }311}312313fn random_primary(primaries: Option<&[Ink]>) -> Ink {314 if let Some(list) = primaries {315 if list.len() == 1 {316 return list[0];317 }318 }319 let mut choices = vec![Ink::Black, Ink::White];320 if let Some(list) = primaries {321 choices.retain(|ink| list.contains(ink));322 }323 if choices.is_empty() {324 choices = vec![Ink::Black, Ink::White];325 }326 choice(&choices)327}328329fn random_secondary(count: Option<usize>, primary: Option<Ink>) -> Vec<Ink> {330 let mut inks = Ink::all().to_vec();331 match primary {332 Some(p) => inks.retain(|&ink| ink != p),333 None => inks.retain(|&ink| ink != Ink::Black && ink != Ink::White),334 }335 let count = count.unwrap_or_else(|| randint(2, 9) as usize);336 let count = count.min(inks.len());337 sample(&inks, count)338}339340fn random_shades(count: Option<usize>, primary: Option<Ink>) -> Vec<usize> {341 if count == Some(1) {342 return if primary == Some(Ink::Black) {343 vec![0]344 } else {345 vec![1]346 };347 }348 let count = count.unwrap_or_else(|| randint(2, 9) as usize);349 let mut shades: Vec<usize> = (0..count).collect();350 shuffle(&mut shades);351 shades352}353354/// Redraws the paint's secondary inks and shades under its scheme.355pub fn reroll(mut paint: Paint) -> Paint {356 let (colors, shades) = if paint.is_simple() {357 (Some(1), Some(1))358 } else if paint.edition == Edition::Index {359 (Some(2), Some(2))360 } else {361 (None, None)362 };363 match paint.scheme {364 Scheme::Multicolor => {365 paint.wipe();366 paint.secondary = random_secondary(colors, Some(paint.primary));367 }368 Scheme::Multitone => {369 paint.wipe();370 paint.secondary = random_secondary(Some(1), None);371 paint.shades = random_shades(shades, Some(paint.primary));372 }373 }374 paint375}376377/// Draws the paint's scheme, target and primary under the config, then rerolls the rest.378pub fn setup(mut paint: Paint, config: &Config) -> Paint {379 paint.scheme = choice(&[Scheme::Multicolor, Scheme::Multitone]);380 paint.target = config381 .target382 .unwrap_or_else(|| choice(&[Target::Fill, Target::Void]));383 paint.primary = random_primary(config.primaries.as_deref());384 reroll(paint)385}386387fn remap_tags(target: Target, cell: &mut Cell) -> usize {388 let target_value = match target {389 Target::Fill => 0,390 Target::Void => 1,391 };392 let tags = match &cell.tags {393 Some(tags) => tags.clone(),394 None => return 0,395 };396 let relevant: Vec<u8> = cell397 .types398 .bytes()399 .iter()400 .zip(tags.bytes().iter())401 .filter(|(&t, _)| t == target_value)402 .map(|(_, &tag)| tag)403 .collect();404 if relevant.is_empty() {405 return 0;406 }407 let mut unique: Vec<u8> = relevant.clone();408 unique.sort_unstable();409 unique.dedup();410 let lookup: HashMap<u8, u8> = unique411 .iter()412 .enumerate()413 .map(|(i, &tag)| (tag, i as u8))414 .collect();415 let data: Vec<u8> = tags416 .bytes()417 .iter()418 .map(|tag| *lookup.get(tag).unwrap_or(&0))419 .collect();420 cell.tags = Some(Tensor::of(data, tags.shape.clone()));421 unique.len()422}423424fn apply_colors(mut paint: Paint, max_val: usize) -> Paint {425 match paint.scheme {426 Scheme::Multicolor => {427 paint.wipe();428 paint.secondary = random_secondary(Some(max_val), Some(paint.primary));429 }430 Scheme::Multitone => {431 paint.wipe();432 paint.secondary = random_secondary(Some(1), None);433 paint.shades = random_shades(Some(max_val), Some(paint.primary));434 }435 }436 paint437}438439/// Tags the cell for the Layers and Neighbors editions and returns the distinct tag count on the secondary side.440pub fn tag(441 cell: &mut Cell,442 edition: Edition,443 target: Target,444 mask: Option<&Tensor>,445) -> Result<usize> {446 match edition {447 Edition::Layers => {448 *cell = cell.clone().layers(Dtype::U8);449 Ok(remap_tags(target, cell))450 }451 Edition::Neighbors => {452 let owned;453 let neighbor_mask = match mask {454 Some(m) => m,455 None => {456 owned = moore(cell.types.shape.len());457 &owned458 }459 };460 *cell = cell.clone().neighbors(neighbor_mask, 1, false, Dtype::U8)?;461 Ok(remap_tags(target, cell))462 }463 _ => Ok(0),464 }465}466467/// Tags the cell for Layers and Neighbors paints and sizes the palette to the tag count.468pub fn prime(mut paint: Paint, cell: &mut Cell, mask: Option<&Tensor>) -> Result<Paint> {469 if matches!(paint.edition, Edition::Layers | Edition::Neighbors) {470 let max_val = tag(cell, paint.edition, paint.target, mask)?;471 paint = apply_colors(paint, max_val.max(1));472 }473 Ok(paint)474}475476fn primary_colors(paint: &Paint) -> Vec<Color> {477 vec![paint.primary.color()]478}479480fn secondary_colors(paint: &Paint) -> Result<Vec<Color>> {481 let mut colors: Vec<Color> = paint.secondary.iter().map(|ink| ink.color()).collect();482 if paint.scheme == Scheme::Multitone {483 if colors.is_empty() {484 return value_error("multitone paint needs a base color.");485 }486 let c1 = colors[0].lightness(LEVELS[0])?;487 let c2 = colors[0].lightness(LEVELS[1])?;488 let mut ramp = vec![c1, c2];489 let steps = paint.shades.len();490 if steps > 2 {491 ramp = gradient(&ramp, steps)?;492 }493 colors = paint494 .shades495 .iter()496 .map(|&i| ramp[i.min(ramp.len() - 1)])497 .collect();498 }499 Ok(colors)500}501502/// Colors the cell from the paint's inks under its edition mode.503pub fn apply(paint: &Paint, cell: &mut Cell) -> Result<()> {504 let primary = primary_colors(paint);505 let secondary = secondary_colors(paint)?;506 let mapping: HashMap<u8, Vec<Color>> = match paint.target {507 Target::Fill => HashMap::from([(0, secondary), (1, primary)]),508 Target::Void => HashMap::from([(0, primary), (1, secondary)]),509 };510 *cell = cell.clone().paint(&mapping, paint.edition.mode());511 Ok(())512}513514fn scatter(paint: &Paint, cell: &mut Cell) -> Result<()> {515 let rgba = |colors: Vec<Color>| -> Vec<[u8; 4]> {516 colors.iter().map(|c| [c.r, c.g, c.b, c.a]).collect()517 };518 let primary = rgba(primary_colors(paint));519 let secondary = rgba(secondary_colors(paint)?);520 let (void_inks, fill_inks) = match paint.target {521 Target::Fill => (secondary, primary),522 Target::Void => (primary, secondary),523 };524 let mut rng = Rng::new(0);525 let size = cell.size();526 let mut colors = cell527 .colors528 .take()529 .unwrap_or_else(|| vec![[0, 0, 0, 0]; size]);530 for (flat, &t) in cell.types.bytes().iter().enumerate() {531 let palette = match t {532 0 => &void_inks,533 1 => &fill_inks,534 _ => continue,535 };536 if !palette.is_empty() {537 colors[flat] = palette[rng.below(palette.len())];538 }539 }540 cell.colors = Some(colors);541 Ok(())542}543544/// Replays a stored paint onto a cell, tagging first and rendering deterministically.545pub fn coat(cell: &mut Cell, paint: &Paint, mask: Option<&Tensor>) -> Result<()> {546 tag(cell, paint.edition, paint.target, mask)?;547 if paint.edition.mode() == Mode::Random {548 scatter(paint, cell)549 } else {550 apply(paint, cell)551 }552}553554/// Draws a random paint under the config, applies it to the cell, and returns the recipe.555pub fn paint(cell: &mut Cell, config: &Config, mask: Option<&Tensor>) -> Result<Paint> {556 let edition = random_edition(config.editions.as_deref());557 let mut p = Paint::new(edition);558 p = setup(p, config);559 p = prime(p, cell, mask)?;560 apply(&p, cell)?;561 Ok(p)562}563564#[cfg(test)]565mod tests {566 use super::*;567 use crate::atoms;568 use crate::json;569 use crate::state::{guard, seed};570 fn round_trip(paint: &Paint) -> Paint {571 serde_json::from_value(serde_json::to_value(paint).unwrap()).unwrap()572 }573 #[test]574 fn simple_paint_colors_every_cell() {575 let _g = guard();576 seed(1);577 let mut cell = Cell::new(atoms::carpet_2d(9));578 let config = Config::default();579 let _ = paint(&mut cell, &config, None).unwrap();580 assert!(cell.colors.is_some());581 let colors = cell.colors.as_ref().unwrap();582 assert_eq!(colors.len(), cell.size());583 assert!(colors.iter().all(|rgba| rgba[3] == 255));584 assert_eq!(cell.size(), 81);585 }586 #[test]587 fn every_edition_paints_2d() {588 let _g = guard();589 for (i, edition) in Edition::all().into_iter().enumerate() {590 seed(i as u64);591 let mut cell = Cell::new(atoms::carpet_2d(9));592 let mut p = Paint::new(edition);593 p = setup(p, &Config::default());594 p = prime(p, &mut cell, None).unwrap();595 apply(&p, &mut cell).unwrap();596 let colors = cell.colors.as_ref().unwrap();597 assert_eq!(colors.len(), cell.size(), "edition {:?}", edition);598 }599 }600 #[test]601 fn every_edition_paints_3d() {602 let _g = guard();603 for (i, edition) in Edition::all().into_iter().enumerate() {604 seed(100 + i as u64);605 let mut cell = Cell::new(atoms::carpet_3d(3));606 let mut p = Paint::new(edition);607 p = setup(p, &Config::default());608 p = prime(p, &mut cell, None).unwrap();609 apply(&p, &mut cell).unwrap();610 let colors = cell.colors.as_ref().unwrap();611 assert_eq!(colors.len(), cell.size(), "edition {:?} 3d", edition);612 }613 }614 #[test]615 fn paint_is_seeded() {616 let _g = guard();617 seed(7);618 let mut a = Cell::new(atoms::carpet_2d(5));619 let pa = paint(&mut a, &Config::default(), None).unwrap();620 seed(7);621 let mut b = Cell::new(atoms::carpet_2d(5));622 let pb = paint(&mut b, &Config::default(), None).unwrap();623 assert_eq!(pa, pb);624 assert_eq!(a, b);625 }626 #[test]627 fn multitone_builds_shade_ramp() {628 let _g = guard();629 seed(3);630 let mut p = Paint::new(Edition::Layers);631 p.scheme = Scheme::Multitone;632 p.primary = Ink::Black;633 p.secondary = vec![Ink::Blue];634 p.shades = vec![0, 1, 2, 1, 0];635 let colors = secondary_colors(&p).unwrap();636 assert_eq!(colors.len(), p.shades.len());637 }638 #[test]639 fn paint_json_round_trips() {640 let mut p = Paint::new(Edition::Layers);641 p.scheme = Scheme::Multitone;642 p.target = Target::Void;643 p.primary = Ink::White;644 p.secondary = vec![Ink::Blue];645 p.shades = vec![2, 0, 1];646 assert_eq!(p, round_trip(&p));647 let json = serde_json::to_value(&p).unwrap();648 assert_eq!(json["edition"], "Layers");649 assert_eq!(json["secondary"], json!(["Blue"]));650 let mut q = Paint::new(Edition::Simple);651 q.secondary = vec![Ink::Teal];652 assert_eq!(q, round_trip(&q));653 }654 #[test]655 fn paint_json_rejects_garbage() {656 let read = |value| serde_json::from_value::<Paint>(value);657 assert!(read(json!({})).is_err());658 assert!(read(json!({659 "edition": "Sparkle", "scheme": "Multicolor", "target": "Fill",660 "primary": "Black", "secondary": [], "shades": [],661 }))662 .is_err());663 assert!(read(json!({664 "edition": "Simple", "scheme": "Multicolor", "target": "Fill",665 "primary": "Black", "secondary": ["Beige"], "shades": [],666 }))667 .is_err());668 assert!(read(json!({669 "edition": "Simple", "scheme": "Multicolor", "target": "Fill",670 "primary": "Black", "secondary": [], "shades": ["soup"],671 }))672 .is_err());673 }674 #[test]675 fn names_parse_back() {676 for edition in Edition::all() {677 assert_eq!(edition, Edition::parse(edition.name()).unwrap());678 }679 for ink in Ink::all() {680 assert_eq!(ink, Ink::parse(ink.name()).unwrap());681 }682 for scheme in Scheme::all() {683 assert_eq!(scheme, Scheme::parse(scheme.name()).unwrap());684 }685 for target in Target::all() {686 assert_eq!(target, Target::parse(target.name()).unwrap());687 }688 }689 #[test]690 fn coat_renders_a_stored_paint_exactly() {691 let _g = guard();692 for edition in Edition::all() {693 seed(11);694 let mut primed = Cell::new(atoms::carpet_2d(9));695 let mut p = Paint::new(edition);696 p = setup(p, &Config::default());697 p = prime(p, &mut primed, None).unwrap();698 let stored = round_trip(&p);699 seed(1);700 let mut a = Cell::new(atoms::carpet_2d(9));701 coat(&mut a, &stored, None).unwrap();702 seed(2);703 let mut b = Cell::new(atoms::carpet_2d(9));704 coat(&mut b, &stored, None).unwrap();705 assert_eq!(a, b, "edition {:?}", edition);706 assert_eq!(a.colors.as_ref().unwrap().len(), a.size());707 }708 }709 #[test]710 fn coat_matches_the_generative_render() {711 let _g = guard();712 for edition in [713 Edition::Simple,714 Edition::Index,715 Edition::Layers,716 Edition::Rows,717 ] {718 seed(21);719 let mut lived = Cell::new(atoms::carpet_2d(9));720 let p = paint(721 &mut lived,722 &Config {723 editions: Some(vec![edition]),724 ..Config::default()725 },726 None,727 )728 .unwrap();729 let mut coated = Cell::new(atoms::carpet_2d(9));730 coat(&mut coated, &p, None).unwrap();731 assert_eq!(lived.colors, coated.colors, "edition {:?}", edition);732 }733 }734 #[test]735 fn default_neighbors_mask_is_the_moore_ring() {736 let offsets: [(usize, usize); 8] = [737 (0, 0),738 (0, 1),739 (0, 2),740 (1, 0),741 (1, 2),742 (2, 0),743 (2, 1),744 (2, 2),745 ];746 let mut types = Tensor::new(vec![45, 5]);747 for count in 0..9 {748 for &(dy, dx) in offsets.iter().take(count) {749 types.set(&[5 * count + 1 + dy, 1 + dx], 1);750 }751 }752 let mut cell = Cell::new(types);753 let classes = tag(&mut cell, Edition::Neighbors, Target::Fill, None).unwrap();754 assert_eq!(classes, 9);755 }756 #[test]757 fn tag_is_deterministic() {758 let mut a = Cell::new(atoms::carpet_2d(9));759 let mut b = Cell::new(atoms::carpet_2d(9));760 let ka = tag(&mut a, Edition::Layers, Target::Fill, None).unwrap();761 let kb = tag(&mut b, Edition::Layers, Target::Fill, None).unwrap();762 assert_eq!(a, b);763 assert_eq!(ka, kb);764 assert!(ka >= 1);765 let mut c = Cell::new(atoms::carpet_2d(9));766 assert_eq!(tag(&mut c, Edition::Simple, Target::Fill, None).unwrap(), 0);767 assert!(c.tags.is_none());768 }769}