mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-01 22:47:04 -06:00
Model: an exhausted id space refuses growth instead of duplicating ids (#407)
alloc_id bumped next_id with saturating_add: a presentation whose next_id reached u32::MAX (a hostile file can arrive that way; fix_next_id imports it) handed out that same id to every new slide, shape, layout and media - duplicate ids, ambiguous references, silent corruption on every later insert. alloc_id now returns Option<u32> via checked_add, and every caller refuses: the engine's insert/duplicate/media/layout commands answer with a clear error naming the command, paste and duplicate re-id helpers propagate it, media addition is fallible, and a fresh slide is Option. The pptx export's synthesized blank layout is guarded the same way. Loading keeps such a deck viewable: repair re-ids duplicate slides while it can, and a duplicate it cannot re-id stays as it is - growth is refused, and only that duplicate keeps its ambiguous id. Renumbering the loaded graph was rejected: it needs the cross-document reference remapping that issue #406 tracks. Tests: model exhaustion (alloc_id None, fresh media refused, a new slide refused), engine (slide.new, shape.insert, slide.duplicate and edit.duplicate all refuse; the document holds no duplicate ids), format repair (an unre-id-able duplicate stays visible).
This commit is contained in:
@@ -361,7 +361,7 @@ fn two_frame_gif() -> Vec<u8> {
|
||||
#[test]
|
||||
fn animated_gif_shows_the_frame_for_its_time() {
|
||||
let mut p = Presentation::default();
|
||||
let media = p.add_media("a.gif", "image/gif", two_frame_gif());
|
||||
let media = p.add_media("a.gif", "image/gif", two_frame_gif()).expect("media");
|
||||
let fill = deckcraft_model::style::PictureFill { media, ..Default::default() };
|
||||
let sh = Shape { id: ShapeId(600), xfrm: Some(Xfrm::new(0.0, 0.0, 960.0, 540.0)), kind: ShapeKind::Picture { fill }, ..Default::default() };
|
||||
std::sync::Arc::make_mut(&mut p.slides[0]).shapes = vec![sh];
|
||||
@@ -653,7 +653,7 @@ fn a_background_is_laid_over_white() {
|
||||
let mut png = std::io::Cursor::new(Vec::new());
|
||||
image::DynamicImage::ImageRgb8(image::RgbImage::from_pixel(4, 4, image::Rgb([0, 0, 200]))).write_to(&mut png, image::ImageFormat::Png).unwrap();
|
||||
let mut p = deck_with(vec![]);
|
||||
let media = p.add_media("bg.png", "image/png", png.into_inner());
|
||||
let media = p.add_media("bg.png", "image/png", png.into_inner()).expect("media");
|
||||
let pf = PictureFill { media, mode: PictureMode::Stretch { fill_rect: [0.5, 0.5, 0.0, 0.0] }, ..Default::default() };
|
||||
std::sync::Arc::make_mut(&mut p.slides[0]).background = Some(deckcraft_model::Background::Fill { fill: Fill::Picture(pf) });
|
||||
let img = render_slide(&p, 0, &RenderOpts { scale: 0.5, ..Default::default() });
|
||||
@@ -1149,7 +1149,7 @@ fn half_red_wmf() -> Vec<u8> {
|
||||
#[test]
|
||||
fn metafile_pictures_draw_their_vectors() {
|
||||
let mut p = Presentation::default();
|
||||
let media = p.add_media("image1.wmf", "image/x-wmf", half_red_wmf());
|
||||
let media = p.add_media("image1.wmf", "image/x-wmf", half_red_wmf()).expect("media");
|
||||
let fill = deckcraft_model::style::PictureFill { media, ..Default::default() };
|
||||
let sh = Shape { id: ShapeId(600), xfrm: Some(Xfrm::new(100.0, 100.0, 400.0, 200.0)), kind: ShapeKind::Picture { fill }, ..Default::default() };
|
||||
std::sync::Arc::make_mut(&mut p.slides[0]).shapes = vec![sh];
|
||||
@@ -1174,7 +1174,7 @@ fn broken_metafiles_draw_nothing_and_never_panic() {
|
||||
cases.push(huge);
|
||||
for bytes in cases {
|
||||
let mut p = Presentation::default();
|
||||
let media = p.add_media("image1.emf", "image/x-emf", bytes);
|
||||
let media = p.add_media("image1.emf", "image/x-emf", bytes).expect("media");
|
||||
let fill = deckcraft_model::style::PictureFill { media, ..Default::default() };
|
||||
let sh = Shape { id: ShapeId(600), xfrm: Some(Xfrm::new(0.0, 0.0, 400.0, 200.0)), kind: ShapeKind::Picture { fill }, ..Default::default() };
|
||||
std::sync::Arc::make_mut(&mut p.slides[0]).shapes = vec![sh];
|
||||
@@ -1195,7 +1195,7 @@ fn an_object_deckcraft_doesnt_model_shows_its_preview_picture() {
|
||||
};
|
||||
for with_preview in [true, false] {
|
||||
let mut p = Presentation::default();
|
||||
let media = p.add_media("image1.png", "image/png", png.clone());
|
||||
let media = p.add_media("image1.png", "image/png", png.clone()).expect("media");
|
||||
let sh = Shape {
|
||||
id: ShapeId(600),
|
||||
xfrm: Some(Xfrm::new(100.0, 100.0, 400.0, 200.0)),
|
||||
@@ -1567,7 +1567,7 @@ fn a_picture_casts_its_shadow_from_its_pixels() {
|
||||
};
|
||||
for as_fill in [false, true] {
|
||||
let mut p = Presentation::default();
|
||||
let media = p.add_media("logo.png", "image/png", png.clone());
|
||||
let media = p.add_media("logo.png", "image/png", png.clone()).expect("media");
|
||||
let pf = deckcraft_model::style::PictureFill { media, ..Default::default() };
|
||||
let mut sh = Shape { id: ShapeId(600), xfrm: Some(Xfrm::new(100.0, 100.0, 400.0, 200.0)), ..Default::default() };
|
||||
if as_fill {
|
||||
|
||||
@@ -265,7 +265,7 @@ fn png(w: u32, h: u32, rgb: [u8; 3]) -> Vec<u8> {
|
||||
|
||||
fn picture_deck(line: Option<Line>) -> Presentation {
|
||||
let mut p = Presentation::default();
|
||||
let media = p.add_media("image1.png", "image/png", png(8, 8, [0, 0, 255]));
|
||||
let media = p.add_media("image1.png", "image/png", png(8, 8, [0, 0, 255])).expect("media");
|
||||
let sh = Shape {
|
||||
id: ShapeId(15),
|
||||
xfrm: Some(Xfrm::new(100.0, 100.0, 200.0, 100.0)),
|
||||
@@ -399,7 +399,7 @@ fn half_red_half_blue() -> Vec<u8> {
|
||||
fn tiled(sx: f64, flip: &str, align: &str) -> Image {
|
||||
use deckcraft_model::style::{PictureFill, PictureMode};
|
||||
let mut p = Presentation::default();
|
||||
let media = p.add_media("t.png", "image/png", half_red_half_blue());
|
||||
let media = p.add_media("t.png", "image/png", half_red_half_blue()).expect("media");
|
||||
let mode = PictureMode::Tile { tx: 0.0, ty: 0.0, sx, sy: 10.0, flip: flip.into(), align: align.into() };
|
||||
let sh = Shape {
|
||||
id: ShapeId(21),
|
||||
@@ -606,7 +606,7 @@ fn a_large_pictures_shadow_is_whole() {
|
||||
let mut counts = vec![];
|
||||
for side in [8u32, 400] {
|
||||
let mut p = Presentation::default();
|
||||
let media = p.add_media("image1.png", "image/png", png(side, side * 3 / 4, [0, 0, 255]));
|
||||
let media = p.add_media("image1.png", "image/png", png(side, side * 3 / 4, [0, 0, 255])).expect("media");
|
||||
let mut fx = offset_shadow();
|
||||
if let Some(o) = &mut fx.outer_shadow {
|
||||
o.blur = 8.0;
|
||||
@@ -690,7 +690,7 @@ fn an_upright_picture_fill_stays_upright_in_a_turned_shape() {
|
||||
let blue = Rgba::rgb(0, 0, 255);
|
||||
for upright in [false, true] {
|
||||
let mut p = Presentation::default();
|
||||
let media = p.add_media("t.png", "image/png", half_red_half_blue());
|
||||
let media = p.add_media("t.png", "image/png", half_red_half_blue()).expect("media");
|
||||
// Half red (left) half blue picture in a 200 × 200 square turned a quarter (and so
|
||||
// covering the same square).
|
||||
let sh = Shape {
|
||||
|
||||
Reference in New Issue
Block a user