mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-03 07:01:09 -06:00
Format: choose the native version in one place; share v4 with #1671
skylartaylor's review: #1647 and #1671 each introduced native format 4 for a different capability, so a reader that understood only one could accept a file carrying the other and silently drop its settings. No release has shipped v4 (main and v0.12.0 still write at most 3), so v4 is defined as one capability covering both retained placed-graphic layer options and explicit all-on PDF layer choices; #1671 is stacked on this branch. Move the version choice into required_version so every new capability is decided in one place, document the shared v4 contract on VERSION, and test that plain, EPS-only and layer-option documents keep 2, 3 and 4 respectively. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b7e36bf616
commit
2633fe8b5d
+37
-12
@@ -22,7 +22,10 @@ use serde_json::{Value, json};
|
||||
|
||||
pub const MIME: &str = "application/vnd.designcraft+zip";
|
||||
/// Latest reader capability. Version 2 pinned the Keep Lines Together and drop cap defaults;
|
||||
/// version 3 adds rendering of retained EPS source assets; version 4 retains placed graphic layer options.
|
||||
/// version 3 adds rendering of retained EPS source assets; version 4 retains placed graphic layer
|
||||
/// options. Version 4 is one capability shared with explicit all-on placed-PDF layer choices
|
||||
/// (storytold/designcraft#1671): it must not be released until a reader understands both, and any
|
||||
/// later capability needs version 5. [`required_version`] is the only place that picks a version.
|
||||
pub const VERSION: u32 = 4;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -67,6 +70,19 @@ pub fn save_with_preview(doc: &Document, preview: Option<&[u8]>) -> Result<Vec<u
|
||||
/// 4 GiB written without them; the margin covers deflate output that is larger than its input.
|
||||
const LARGE_ENTRY: usize = (u32::MAX as usize).saturating_sub(64 << 20);
|
||||
|
||||
/// The oldest reader capability that keeps everything in `doc`. Documents that need nothing newer
|
||||
/// keep their older requirement so existing readers still open them.
|
||||
fn required_version(doc: &Document) -> u32 {
|
||||
// Older readers ignore unknown Graphic fields and would silently drop layer choices.
|
||||
if doc.has_graphic_layer_options() {
|
||||
return 4;
|
||||
}
|
||||
// Older readers would silently omit EPS artwork. Preview-only legacy assets and documents
|
||||
// without EPS stay at 2 so their existing readers keep working; writing 1 would make a
|
||||
// reader re-run the version 2 paragraph upgrade over current defaults.
|
||||
if doc.assets.values().any(|a| designcraft_images::is_eps(&a.data)) { 3 } else { 2 }
|
||||
}
|
||||
|
||||
fn save_with_large_entries_from(doc: &Document, preview: Option<&[u8]>, large_entry: usize) -> Result<Vec<u8>, FormatError> {
|
||||
// JSON writes infinity and NaN as null, which no load accepts: the file would not open.
|
||||
if let Some(id) = doc.non_finite_item() {
|
||||
@@ -84,17 +100,7 @@ fn save_with_large_entries_from(doc: &Document, preview: Option<&[u8]>, large_en
|
||||
z.start_file("mimetype", stored).map_err(io)?;
|
||||
z.write_all(MIME.as_bytes()).map_err(|e| FormatError::Io(e.to_string()))?;
|
||||
z.start_file("meta.json", deflate).map_err(io)?;
|
||||
// Older readers would silently omit EPS artwork. Preview-only legacy assets and
|
||||
// documents without EPS stay at 2 so their existing readers keep working; writing 1
|
||||
// would make a reader re-run the version 2 paragraph upgrade over current defaults.
|
||||
// Older readers ignore unknown Graphic fields and would silently drop layer choices.
|
||||
let version = if doc.has_graphic_layer_options() {
|
||||
4
|
||||
} else if doc.assets.values().any(|a| designcraft_images::is_eps(&a.data)) {
|
||||
3
|
||||
} else {
|
||||
2
|
||||
};
|
||||
let version = required_version(doc);
|
||||
let meta = json!({"format": "designcraft", "version": version, "generator": concat!("DesignCraft ", env!("CARGO_PKG_VERSION"))});
|
||||
z.write_all(meta.to_string().as_bytes()).map_err(|e| FormatError::Io(e.to_string()))?;
|
||||
if let Some(png) = preview {
|
||||
@@ -481,6 +487,25 @@ mod tests {
|
||||
assert_eq!(version(&d), 2, "legacy EPS previews stay compatible");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_place_picks_the_oldest_reader_that_keeps_the_document() {
|
||||
use designcraft_doc::{Content, GraphicLayerOptions};
|
||||
let mut d = Document::new(&NewDocument::default());
|
||||
assert_eq!(required_version(&d), 2, "plain documents keep the version 2 reader");
|
||||
let aid = AssetId(d.alloc());
|
||||
let eps = b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 20 10\n%%EOF\n".to_vec();
|
||||
d.assets.insert(
|
||||
aid,
|
||||
Arc::new(Asset { id: aid, name: "logo.eps".into(), mime: "application/postscript".into(), data: Arc::new(eps), ..Default::default() }),
|
||||
);
|
||||
let id = place(&mut d, aid, 0.0);
|
||||
assert_eq!(required_version(&d), 3, "EPS sources alone keep the version 3 reader");
|
||||
let Some(Content::Graphic(g)) = d.item_mut(id).map(|it| &mut it.content) else { panic!("placed graphic") };
|
||||
g.layer_options = Some(GraphicLayerOptions { layers: vec![[("Name".to_string(), "Art".to_string())].into()], ..Default::default() });
|
||||
assert_eq!(required_version(&d), 4, "retained graphic layer options need version 4 even with EPS");
|
||||
assert_eq!(required_version(&load(&save(&d).unwrap()).unwrap()), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_with_assets() {
|
||||
let mut d = Document::new(&NewDocument { pages: 3, ..Default::default() });
|
||||
|
||||
@@ -27,6 +27,11 @@ field. Documents without layer settings retain their existing version 2/3
|
||||
requirements. Older graphics without `layerOptions` still deserialize with
|
||||
`None`, and their JSON representation is unchanged.
|
||||
|
||||
Version 4 is a single reader capability shared with explicit all-on placed-PDF
|
||||
layer choices (#1671): a version 4 reader must understand both, so the two
|
||||
changes ship in the same release. One function in the format crate,
|
||||
`required_version`, chooses the version for every save.
|
||||
|
||||
Synthetic regressions cover independent overrides on two placements sharing
|
||||
one asset, metadata round trips, native reader requirements, nested cells,
|
||||
nested layer hierarchy, preflight/PDF warnings after rasterization and flattening,
|
||||
|
||||
Reference in New Issue
Block a user