use std::sync::Arc;
use deckcraft_color::Rgba;
use kurbo::{Affine, BezPath};
use vello_cpu::{Pixmap, RenderContext, peniko};
pub const CELL: f64 = 0.75;
const TILES: &[(&str, [u8; 8])] = &[
("pct5", [0x80, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00]),
("pct10", [0x80, 0x00, 0x08, 0x00, 0x80, 0x00, 0x08, 0x00]),
("pct20", [0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00]),
("pct25", [0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22]),
("pct30", [0xAA, 0x44, 0xAA, 0x11, 0xAA, 0x44, 0xAA, 0x11]),
("pct40", [0xAA, 0x55, 0xAA, 0x51, 0xAA, 0x55, 0xAA, 0x15]),
("pct50", [0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55]),
("pct60", [0xEE, 0x55, 0xBB, 0x55, 0xEE, 0x55, 0xBB, 0x55]),
("pct70", [0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD]),
("pct75", [0x77, 0xFF, 0xDD, 0xFF, 0x77, 0xFF, 0xDD, 0xFF]),
("pct80", [0xEF, 0xFF, 0xFE, 0xFF, 0xEF, 0xFF, 0xFE, 0xFF]),
("pct90", [0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F]),
("horz", [0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
("vert", [0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80]),
("ltHorz", [0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00]),
("ltVert", [0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88]),
("dkHorz", [0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00]),
("dkVert", [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]),
("narHorz", [0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00]),
("narVert", [0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55]),
("dashHorz", [0xF0, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00]),
("dashVert", [0x80, 0x80, 0x80, 0x80, 0x08, 0x08, 0x08, 0x08]),
("cross", [0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80]),
("dnDiag", [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]),
("upDiag", [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]),
("ltDnDiag", [0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11]),
("ltUpDiag", [0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88]),
("dkDnDiag", [0xCC, 0x66, 0x33, 0x99, 0xCC, 0x66, 0x33, 0x99]),
("dkUpDiag", [0x33, 0x66, 0xCC, 0x99, 0x33, 0x66, 0xCC, 0x99]),
("wdDnDiag", [0xC1, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x83]),
("wdUpDiag", [0x83, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xC1]),
(
"dashDnDiag",
[0x00, 0x00, 0x88, 0x44, 0x22, 0x11, 0x00, 0x00],
),
(
"dashUpDiag",
[0x00, 0x00, 0x11, 0x22, 0x44, 0x88, 0x00, 0x00],
),
(
"diagCross",
[0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81],
),
("smCheck", [0x99, 0x66, 0x66, 0x99, 0x99, 0x66, 0x66, 0x99]),
("lgCheck", [0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F]),
("smGrid", [0xFF, 0x88, 0x88, 0x88, 0xFF, 0x88, 0x88, 0x88]),
("lgGrid", [0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80]),
("dotGrid", [0xAA, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00]),
(
"smConfetti",
[0x80, 0x08, 0x40, 0x02, 0x10, 0x01, 0x20, 0x04],
),
(
"lgConfetti",
[0xB1, 0x30, 0x03, 0x1B, 0xD8, 0xC0, 0x0C, 0x8D],
),
(
"horzBrick",
[0xFF, 0x80, 0x80, 0x80, 0xFF, 0x08, 0x08, 0x08],
),
(
"diagBrick",
[0x01, 0x02, 0x04, 0x08, 0x18, 0x24, 0x42, 0x81],
),
(
"solidDmnd",
[0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x10, 0x00],
),
("openDmnd", [0x82, 0x44, 0x28, 0x10, 0x28, 0x44, 0x82, 0x01]),
("dotDmnd", [0x80, 0x00, 0x22, 0x00, 0x08, 0x00, 0x22, 0x00]),
("plaid", [0xAA, 0x55, 0xAA, 0x55, 0xF0, 0xF0, 0xF0, 0xF0]),
("sphere", [0x77, 0x89, 0x8F, 0x8F, 0x77, 0x98, 0xF8, 0xF8]),
("weave", [0x88, 0x54, 0x22, 0x45, 0x88, 0x14, 0x22, 0x51]),
("divot", [0x00, 0x10, 0x08, 0x10, 0x00, 0x80, 0x01, 0x80]),
("shingle", [0x03, 0x84, 0x48, 0x30, 0x0C, 0x02, 0x01, 0x01]),
("wave", [0x00, 0x18, 0x25, 0xC0, 0x00, 0x18, 0x25, 0xC0]),
("trellis", [0xFF, 0x66, 0xFF, 0x99, 0xFF, 0x66, 0xFF, 0x99]),
("zigZag", [0x81, 0x42, 0x24, 0x18, 0x81, 0x42, 0x24, 0x18]),
];
pub fn tile(name: &str) -> [u8; 8] {
TILES
.iter()
.find(|(n, _)| *n == name)
.or_else(|| TILES.iter().find(|(n, _)| *n == "pct50"))
.map(|(_, t)| *t)
.unwrap_or([0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55])
}
pub fn is_fg(tile: &[u8; 8], x: usize, y: usize) -> bool {
tile.get(y % 8)
.is_some_and(|row| row & (0x80 >> (x % 8)) != 0)
}
fn premul(c: Rgba, alpha: f64) -> vello_cpu::color::PremulRgba8 {
let a = (c.a as f64 * alpha.clamp(0.0, 1.0))
.round()
.clamp(0.0, 255.0);
let m = |v: u8| (v as f64 * a / 255.0).round().clamp(0.0, 255.0) as u8;
vello_cpu::color::PremulRgba8 {
r: m(c.r),
g: m(c.g),
b: m(c.b),
a: a as u8,
}
}
pub fn fill(
ctx: &mut RenderContext,
name: &str,
fg: Rgba,
bg: Rgba,
alpha: f64,
path: &BezPath,
m: Affine,
) {
let det = m.determinant();
if !det.is_finite() || det.abs() <= 1e-12 {
return;
}
let t = tile(name);
let (f, b) = (premul(fg, alpha), premul(bg, alpha));
let data: Vec<vello_cpu::color::PremulRgba8> = (0..64)
.map(|i| if is_fg(&t, i % 8, i / 8) { f } else { b })
.collect();
let pm = Arc::new(Pixmap::from_parts(data, 8, 8));
ctx.set_transform(m);
ctx.set_paint(vello_cpu::Image {
image: vello_cpu::ImageSource::Pixmap(pm),
sampler: peniko::ImageSampler {
x_extend: peniko::Extend::Repeat,
y_extend: peniko::Extend::Repeat,
quality: peniko::ImageQuality::Low,
alpha: 1.0,
},
});
let k = crate::scale_of(m);
let k = if k.is_finite() && k > 0.0 { k } else { 1.0 };
ctx.set_paint_transform(m.inverse() * Affine::scale(CELL * k));
ctx.fill_path(path);
ctx.reset_paint_transform();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_drawingml_preset_has_a_tile() {
let all = [
"pct5",
"pct10",
"pct20",
"pct25",
"pct30",
"pct40",
"pct50",
"pct60",
"pct70",
"pct75",
"pct80",
"pct90",
"horz",
"vert",
"ltHorz",
"ltVert",
"dkHorz",
"dkVert",
"narHorz",
"narVert",
"dashHorz",
"dashVert",
"cross",
"dnDiag",
"upDiag",
"ltDnDiag",
"ltUpDiag",
"dkDnDiag",
"dkUpDiag",
"wdDnDiag",
"wdUpDiag",
"dashDnDiag",
"dashUpDiag",
"diagCross",
"smCheck",
"lgCheck",
"smGrid",
"lgGrid",
"dotGrid",
"smConfetti",
"lgConfetti",
"horzBrick",
"diagBrick",
"solidDmnd",
"openDmnd",
"dotDmnd",
"plaid",
"sphere",
"weave",
"divot",
"shingle",
"wave",
"trellis",
"zigZag",
];
assert_eq!(all.len(), 54);
for n in all {
assert!(TILES.iter().any(|(t, _)| *t == n), "{n}");
}
assert_eq!(TILES.len(), 54);
}
#[test]
fn percent_presets_get_denser() {
let share = |n: &str| {
let t = tile(n);
(0..64).filter(|i| is_fg(&t, i % 8, i / 8)).count()
};
let names = [
"pct5", "pct10", "pct20", "pct25", "pct30", "pct40", "pct50", "pct60", "pct70",
"pct75", "pct80", "pct90",
];
assert!(
names.windows(2).all(|w| share(w[0]) <= share(w[1])),
"{:?}",
names.map(share)
);
assert_eq!((share("pct5"), share("pct50"), share("pct90")), (2, 32, 62));
assert_eq!(tile("nonsense"), tile("pct50"));
}
}