137 lines
5.6 KiB
Rust
137 lines
5.6 KiB
Rust
//! Placed documents on screen: drawn from bitmaps made in the background (a placeholder until
|
||
//! then), the same as drawn exactly; exports draw them exactly at once.
|
||
|
||
use vectorcraft_color::{Color, Paint};
|
||
use vectorcraft_doc::{Appearance, ImageBlob, LinkInfo, Node, NodeId, NodeKind, PlacedDocument};
|
||
use vectorcraft_geom::shapes;
|
||
|
||
use super::*;
|
||
|
||
/// Every file reads as a 40×20 artboard filled blue; a file whose bytes start with `loop` also
|
||
/// places itself (the same key) in its middle.
|
||
fn blue_page(p: &PlacedDocument, bytes: &[u8]) -> Option<(Document, Node, Rect)> {
|
||
let mut d = Document::new(40.0, 20.0);
|
||
d.layers.clear();
|
||
let blue = Appearance::basic(Paint::solid(Color::rgb(0.0, 0.0, 1.0)), Paint::None, 0.0);
|
||
let mut children = vec![Arc::new(Node::path(NodeId(1), shapes::rectangle(Rect::new(0.0, 0.0, 40.0, 20.0)), blue))];
|
||
if bytes.starts_with(b"loop") {
|
||
d.images.insert(p.key.clone(), ImageBlob::new("application/json", bytes.to_vec()));
|
||
let me = PlacedDocument { xf: Affine::translate((10.0, 5.0)) * Affine::scale(0.5), ..p.clone() };
|
||
children.push(Arc::new(Node::new(NodeId(2), NodeKind::PlacedDocument(Box::new(me)))));
|
||
}
|
||
Some((d, Node::new(NodeId(0), NodeKind::Group { children, clip: false }), Rect::new(0.0, 0.0, 40.0, 20.0)))
|
||
}
|
||
|
||
/// A 100×100 document showing one placed file (`bytes` under `key`) in the box (10, 10)–(90, 50).
|
||
fn doc_with(key: &str, bytes: &[u8]) -> Document {
|
||
vectorcraft_doc::placed_document::set_loader(blue_page);
|
||
let mut d = Document::new(100.0, 100.0);
|
||
d.images.insert(key.into(), ImageBlob::new("application/json", bytes.to_vec()));
|
||
let p = PlacedDocument {
|
||
link: LinkInfo::new("/art/card.vectorcraft"),
|
||
key: key.into(),
|
||
bounding: false,
|
||
width: 40.0,
|
||
height: 20.0,
|
||
xf: Affine::translate((10.0, 10.0)) * Affine::scale(2.0),
|
||
placement: Default::default(),
|
||
};
|
||
let l = d.layers[0].id;
|
||
d.insert(Some(l), 0, Node::new(NodeId(50), NodeKind::PlacedDocument(Box::new(p)))).unwrap();
|
||
d
|
||
}
|
||
|
||
fn doc(key: &str) -> Document {
|
||
doc_with(key, format!("{{\"doc\": \"{key}\"}}").as_bytes())
|
||
}
|
||
|
||
fn render(d: &Document, progressive: bool) -> Rendered {
|
||
let opts = RenderOptions { background: Some([255, 255, 255, 255]), progressive_placed: progressive, ..Default::default() };
|
||
Renderer::new().render(d, 100, 100, Affine::IDENTITY, &opts)
|
||
}
|
||
|
||
/// Wait for the background bitmaps (at most 10 s).
|
||
fn settle() {
|
||
let t = std::time::Instant::now();
|
||
while placed_document::busy() && t.elapsed().as_secs() < 10 {
|
||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||
}
|
||
assert!(!placed_document::busy(), "bitmaps still coming");
|
||
}
|
||
|
||
#[test]
|
||
fn a_placed_document_draws_exactly_with_its_own_art() {
|
||
let d = doc("exact-1");
|
||
let r = render(&d, false);
|
||
assert_eq!(&r.pixel(50, 30)[..3], &[0, 0, 255], "blue inside the box");
|
||
assert_eq!(&r.pixel(95, 95)[..3], &[255, 255, 255], "nothing outside");
|
||
}
|
||
|
||
#[test]
|
||
fn on_screen_a_placed_document_shows_a_placeholder_then_its_bitmap() {
|
||
let d = doc("progressive-1");
|
||
let generation = placed_document::generation();
|
||
let first = render(&d, true);
|
||
let shown = &first.pixel(50, 30)[..3];
|
||
assert!(shown == [0, 0, 255] || shown != [255, 255, 255], "a placeholder or the art: {shown:?}");
|
||
settle();
|
||
assert!(placed_document::generation() > generation || shown == [0, 0, 255], "a bitmap was made");
|
||
let later = render(&d, true);
|
||
let exact = render(&d, false);
|
||
assert_eq!(&later.pixel(50, 30)[..3], &[0, 0, 255]);
|
||
for (x, y) in [(12, 12), (50, 30), (88, 48), (95, 95), (5, 5)] {
|
||
let (a, b) = (later.pixel(x, y), exact.pixel(x, y));
|
||
assert!(a.iter().zip(b).all(|(a, b)| a.abs_diff(b) <= 24), "at ({x}, {y}): {a:?} vs {b:?}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn copies_of_one_placed_document_share_its_bitmap() {
|
||
let mut d = doc("shared-1");
|
||
let n = d.node(NodeId(50)).unwrap().clone();
|
||
let l = d.layers[0].id;
|
||
let mut copy = n.clone();
|
||
copy.id = NodeId(51);
|
||
copy.transform(Affine::translate((0.0, 45.0)), false);
|
||
d.insert(Some(l), 1, copy).unwrap();
|
||
render(&d, true);
|
||
settle();
|
||
let r = render(&d, true);
|
||
assert!(!placed_document::pending_for("shared-1"), "nothing more to make");
|
||
assert_eq!(&r.pixel(50, 30)[..3], &[0, 0, 255]);
|
||
assert_eq!(&r.pixel(50, 75)[..3], &[0, 0, 255]);
|
||
}
|
||
|
||
#[test]
|
||
fn a_placed_document_kept_as_its_preview_draws_the_preview() {
|
||
let mut d = doc("preview-1");
|
||
// Only the preview (a 20×10 green PNG) is there, and the file can't be read here.
|
||
let img = image::RgbaImage::from_pixel(20, 10, image::Rgba([0, 200, 0, 255]));
|
||
let mut png = vec![];
|
||
img.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).unwrap();
|
||
let mut blob = ImageBlob::new("image/png", png);
|
||
blob.proxy = Some(blob.bytes.clone());
|
||
d.images.insert("preview-1".into(), blob);
|
||
let exact = render(&d, false);
|
||
assert_eq!(&exact.pixel(50, 30)[..3], &[0, 200, 0], "the preview, in the box");
|
||
assert_eq!(&exact.pixel(95, 95)[..3], &[255, 255, 255]);
|
||
// On screen past the preview's resolution (it has 0.5 px a point; this is 2): still the preview.
|
||
render(&d, true);
|
||
settle();
|
||
let shown = render(&d, true);
|
||
assert_eq!(&shown.pixel(50, 30)[..3], &[0, 200, 0]);
|
||
}
|
||
|
||
#[test]
|
||
fn a_document_that_places_itself_draws_a_few_levels_deep_then_stops() {
|
||
let d = doc_with("loop-1", b"loop");
|
||
let t = std::time::Instant::now();
|
||
let r = render(&d, false);
|
||
assert!(t.elapsed().as_secs() < 10);
|
||
assert_eq!(&r.pixel(50, 30)[..3], &[0, 0, 255]);
|
||
// On screen too.
|
||
render(&d, true);
|
||
settle();
|
||
assert_eq!(&render(&d, true).pixel(50, 30)[..3], &[0, 0, 255]);
|
||
}
|