mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-11 04:52:13 +00:00
Merge pull request #562 from dotCooCoo/feat/vectorcraft-pdf-preference-read
vectorcraft-format: read PDF preference with document contents
This commit is contained in:
3 files changed
+95
-8
No files matched your search
@@ -34,6 +34,12 @@ reads one, and `Contents::read(json)` reads `format`, `version`, `document`, `im
|
||||
`profiles` and skips `generator`, `preview` and `pdf` as the JSON is read, so their base64 is not
|
||||
buffered or kept. Both read the files VectorCraft opens:
|
||||
|
||||
`Contents::read_with_pdf_preference(json)` returns `(contents, pdf_compatible)` in that same
|
||||
envelope pass. It reads an optional boolean `pdfCompatible`, falling back to a non-null `pdf`
|
||||
member when the preference is absent or null. It neither retains nor decodes the PDF payload.
|
||||
Repeated PDF members use their last value, and the array form returns false. A non-boolean
|
||||
preference is an error in this opt-in method; `Contents::read` keeps ignoring that field.
|
||||
|
||||
- They return `ReadError::Envelope(Error::Format)` for another format name and
|
||||
`ReadError::Envelope(Error::TooNew)` for a version newer than `VERSION`, after the JSON reads;
|
||||
`ReadError::Json` otherwise.
|
||||
|
||||
@@ -188,18 +188,30 @@ impl<D> File<D> {
|
||||
where
|
||||
D: Deserialize<'de>,
|
||||
{
|
||||
read(json, true)
|
||||
read(json, true, false).map(|(file, _)| file)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`File::read`] and [`Contents::read`]; `keep` is false for [`Contents`], which skips
|
||||
/// `generator`, `preview` and `pdf` without keeping them.
|
||||
fn read<'de, D: Deserialize<'de>>(json: &'de [u8], keep: bool) -> Result<File<D>, ReadError> {
|
||||
fn read<'de, D: Deserialize<'de>>(json: &'de [u8], keep: bool, read_pdf: bool) -> Result<(File<D>, bool), ReadError> {
|
||||
let mut de = serde_json::Deserializer::from_slice(json);
|
||||
let file = serde::Deserializer::deserialize_struct(&mut de, "File", MEMBERS, FileVisitor { keep, document: PhantomData })?;
|
||||
let mut pdf = PdfPreference::default();
|
||||
let file = serde::Deserializer::deserialize_struct(
|
||||
&mut de,
|
||||
"File",
|
||||
MEMBERS,
|
||||
FileVisitor { keep, pdf: read_pdf.then_some(&mut pdf), document: PhantomData },
|
||||
)?;
|
||||
de.end()?;
|
||||
file.check()?;
|
||||
Ok(file)
|
||||
Ok((file, pdf.preference.unwrap_or(pdf.present)))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PdfPreference {
|
||||
preference: Option<bool>,
|
||||
present: bool,
|
||||
}
|
||||
|
||||
/// The members of a file, in the order of the array form.
|
||||
@@ -219,12 +231,13 @@ struct Members<D> {
|
||||
}
|
||||
|
||||
/// Reads a [`File`] from a JSON object or array.
|
||||
struct FileVisitor<D> {
|
||||
struct FileVisitor<'a, D> {
|
||||
keep: bool,
|
||||
pdf: Option<&'a mut PdfPreference>,
|
||||
document: PhantomData<D>,
|
||||
}
|
||||
|
||||
impl<'de, D: Deserialize<'de>> Visitor<'de> for FileVisitor<D> {
|
||||
impl<'de, D: Deserialize<'de>> Visitor<'de> for FileVisitor<'_, D> {
|
||||
type Value = File<D>;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
@@ -236,7 +249,7 @@ impl<'de, D: Deserialize<'de>> Visitor<'de> for FileVisitor<D> {
|
||||
Ok(File { format, version, generator: None, preview: None, profiles, pdf: None, document, images })
|
||||
}
|
||||
|
||||
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<File<D>, A::Error> {
|
||||
fn visit_map<A: MapAccess<'de>>(mut self, mut map: A) -> Result<File<D>, A::Error> {
|
||||
let (mut format, mut version, mut document, mut images, mut profiles) = (None, None, None, None, None);
|
||||
let (mut generator, mut preview, mut pdf) = (None, None, None);
|
||||
while let Some(key) = map.next_key::<String>()? {
|
||||
@@ -249,6 +262,16 @@ impl<'de, D: Deserialize<'de>> Visitor<'de> for FileVisitor<D> {
|
||||
"generator" if self.keep => generator = map.next_value::<Lenient<String>>()?.0,
|
||||
"preview" if self.keep => preview = map.next_value::<Lenient<Image>>()?.0,
|
||||
"pdf" if self.keep => pdf = map.next_value::<Lenient<Image>>()?.0,
|
||||
"pdfCompatible" if self.pdf.is_some() => {
|
||||
if let Some(state) = self.pdf.as_mut() {
|
||||
state.preference = map.next_value::<Option<bool>>()?;
|
||||
}
|
||||
}
|
||||
"pdf" if self.pdf.is_some() => {
|
||||
if let Some(state) = self.pdf.as_mut() {
|
||||
state.present = map.next_value::<Option<IgnoredAny>>()?.is_some();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
map.next_value::<IgnoredAny>()?;
|
||||
}
|
||||
@@ -339,10 +362,25 @@ impl<D> Contents<D> {
|
||||
where
|
||||
D: Deserialize<'de>,
|
||||
{
|
||||
let File { format, version, document, images, profiles, .. } = read(json, false)?;
|
||||
let (file, _) = read(json, false, false)?;
|
||||
let File { format, version, document, images, profiles, .. } = file;
|
||||
Ok(Self { format, version, document, images, profiles })
|
||||
}
|
||||
|
||||
/// Reads the document and its saved PDF preference in one envelope pass, without
|
||||
/// keeping or decoding the PDF payload. A non-null `pdf` supplies the fallback when
|
||||
/// `pdfCompatible` is absent or null. Repeated members use their last value; arrays
|
||||
/// have no PDF preference and return false. A non-boolean preference is an error.
|
||||
/// The existing [`Self::read`] continues to ignore that optional preference.
|
||||
pub fn read_with_pdf_preference<'de>(json: &'de [u8]) -> Result<(Self, bool), ReadError>
|
||||
where
|
||||
D: Deserialize<'de>,
|
||||
{
|
||||
let (file, compatible) = read(json, false, true)?;
|
||||
let File { format, version, document, images, profiles, .. } = file;
|
||||
Ok((Self { format, version, document, images, profiles }, compatible))
|
||||
}
|
||||
|
||||
/// [`File::is_legacy`].
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
self.format == LEGACY_EXTENSION
|
||||
|
||||
@@ -252,6 +252,49 @@ fn contents_read_the_document_images_and_profiles_without_the_preview_and_pdf()
|
||||
assert!(Contents::<Doc>::read(no_document).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contents_read_pdf_preference_while_skipping_the_payload() {
|
||||
for (members, expected) in [
|
||||
("", false),
|
||||
(r#", "pdf":null"#, false),
|
||||
(r#", "pdf":{"data":"not base64"}"#, true),
|
||||
(r#", "pdf":[1,2,3]"#, true),
|
||||
(r#", "pdfCompatible":true"#, true),
|
||||
(r#", "pdf":{}, "pdfCompatible":false"#, false),
|
||||
(r#", "pdfCompatible":false, "pdf":{}"#, false),
|
||||
(r#", "pdfCompatible":true, "pdfCompatible":null, "pdf":{}"#, true),
|
||||
(r#", "pdf":{}, "pdf":null"#, false),
|
||||
(r#", "pdfCompatible":false, "pdfCompatible":true"#, true),
|
||||
(r#", "unknown":{"pdf":{}}"#, false),
|
||||
] {
|
||||
let json = format!(r#"{{"format":"vectorcraft","version":4,"document":{{"layers":[]}}{members}}}"#);
|
||||
let (contents, compatible) = Contents::<Doc>::read_with_pdf_preference(json.as_bytes()).unwrap();
|
||||
assert_eq!(compatible, expected, "{members}");
|
||||
assert_eq!(contents, Contents::<Doc>::read(json.as_bytes()).unwrap());
|
||||
}
|
||||
let array = br#"["drawcraft",1,{"layers":[]}]"#;
|
||||
assert!(!Contents::<Doc>::read_with_pdf_preference(array).unwrap().1);
|
||||
let mut file = File::new(doc());
|
||||
file.pdf = Some(Image::inline("application/pdf", &vec![7; 128 << 10]));
|
||||
assert!(Contents::<Doc>::read_with_pdf_preference(&serde_json::to_vec(&file).unwrap()).unwrap().1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pdf_preference_read_is_opt_in_and_preserves_envelope_validation() {
|
||||
let invalid = br#"{"format":"vectorcraft","version":4,"document":{"layers":[]},"pdfCompatible":"invalid"}"#;
|
||||
assert!(Contents::<Doc>::read(invalid).is_ok());
|
||||
assert!(File::<Doc>::read(invalid).is_ok());
|
||||
assert!(Contents::<Doc>::read_with_pdf_preference(invalid).is_err());
|
||||
for json in [
|
||||
br#"{"format":"other","version":4,"document":{"layers":[]}}"#.as_slice(),
|
||||
br#"{"format":"vectorcraft","version":999,"document":{"layers":[]}}"#.as_slice(),
|
||||
br#"{"format":"vectorcraft","version":4}"#.as_slice(),
|
||||
br#"{"format":"vectorcraft","version":4,"version":4,"document":{"layers":[]}}"#.as_slice(),
|
||||
] {
|
||||
assert_eq!(Contents::<Doc>::read(json).unwrap_err().to_string(), Contents::<Doc>::read_with_pdf_preference(json).unwrap_err().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reading_refuses_another_format_and_a_newer_version() {
|
||||
let newer = br#"{"format":"vectorcraft","version":5,"document":0}"#;
|
||||
|
||||
Reference in new issue
Block a user