Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ pub mod contents {
pub use crate::onenote::math_inline_object::{MathInlineObject, MathObjectType};
pub use crate::onenote::note_tag::NoteTag;
pub use crate::onenote::outline::{Outline, OutlineElement, OutlineGroup, OutlineItem};
pub use crate::onenote::picture::Picture;
pub use crate::onenote::rich_text::{
EmbeddedInkContainer, EmbeddedInkSpace, EmbeddedObject, ParagraphStyling, RichText,
TextHyperlink,
Expand Down
74 changes: 74 additions & 0 deletions crates/parser/src/one/property_set/image_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::onestore::Object;
pub(crate) struct Data {
pub(crate) last_modified: Option<Time>,
pub(crate) picture_container: Option<ExGuid>,
pub(crate) web_picture_container: Option<ExGuid>,
pub(crate) layout_max_width: Option<f32>,
pub(crate) layout_max_height: Option<f32>,
pub(crate) is_layout_size_set_by_user: bool,
Expand All @@ -44,6 +45,8 @@ pub(crate) fn parse(object: &Object) -> Result<Data> {

let last_modified = Time::parse(PropertyType::LastModifiedTime, object)?;
let picture_container = ObjectReference::parse(PropertyType::PictureContainer, object)?;
let web_picture_container =
ObjectReference::parse(PropertyType::WebPictureContainer14, object)?;
let layout_max_width = simple::parse_f32(PropertyType::LayoutMaxWidth, object)?;
let layout_max_height = simple::parse_f32(PropertyType::LayoutMaxHeight, object)?;
let is_layout_size_set_by_user =
Expand Down Expand Up @@ -73,6 +76,7 @@ pub(crate) fn parse(object: &Object) -> Result<Data> {
let data = Data {
last_modified,
picture_container,
web_picture_container,
layout_max_width,
layout_max_height,
is_layout_size_set_by_user,
Expand All @@ -96,3 +100,73 @@ pub(crate) fn parse(object: &Object) -> Result<Data> {

Ok(data)
}

#[cfg(test)]
mod tests {
use super::parse;
use crate::fsshttpb::data::cell_id::CellId;
use crate::fsshttpb::data::exguid::ExGuid;
use crate::one::property::PropertyType;
use crate::one::property_set::PropertySetId;
use crate::onestore::shared::compact_id::CompactId;
use crate::onestore::shared::object_prop_set::ObjectPropSet;
use crate::onestore::shared::prop_set::PropertySet;
use crate::onestore::{MappingTable, Object};
use crate::reader::Reader;
use std::rc::Rc;

struct TestMapping;

impl MappingTable for TestMapping {
fn resolve_id(&self, index: usize, _cid: &CompactId) -> Option<ExGuid> {
Some(ExGuid {
value: index as u32 + 1,
..Default::default()
})
}

fn get_object_space(&self, _index: usize, _cid: &CompactId) -> Option<CellId> {
None
}
}

#[test]
fn parses_primary_and_web_picture_references() {
let property_ids = [
PropertyType::PictureContainer,
PropertyType::WebPictureContainer14,
];
let mut bytes = Vec::new();
bytes.extend_from_slice(&(property_ids.len() as u16).to_le_bytes());
for property_id in property_ids {
bytes.extend_from_slice(&(property_id as u32).to_le_bytes());
}

let mut reader = Reader::new(&bytes);
let object = Object {
context_id: Default::default(),
jc_id: PropertySetId::ImageNode.as_jcid(),
props: ObjectPropSet {
object_ids: vec![
CompactId {
n: 0,
guid_index: 0,
},
CompactId {
n: 1,
guid_index: 0,
},
],
properties: PropertySet::parse(&mut reader).unwrap(),
..Default::default()
},
file_data: None,
mapping: Rc::new(TestMapping),
};

let data = parse(&object).unwrap();

assert_eq!(data.picture_container.unwrap().value, 1);
assert_eq!(data.web_picture_container.unwrap().value, 2);
}
}
27 changes: 27 additions & 0 deletions crates/parser/src/onenote/embedded_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::one::property::file_type::FileType;
use crate::one::property_set::{embedded_file_container, embedded_file_node};
use crate::onenote::ParserContext;
use crate::onenote::note_tag::{NoteTag, parse_note_tags};
use crate::onenote::picture::Picture;
use crate::onestore::ObjectSpace;
use crate::onestore::shared::file_blob::{FileBlob, FileDataStatus};

Expand All @@ -17,6 +18,7 @@ pub struct EmbeddedFile {
pub(crate) filename: String,
pub(crate) file_type: FileType,
pub(crate) data: FileBlob,
pub(crate) icon: Option<Picture>,

pub(crate) layout_max_width: Option<f32>,
pub(crate) layout_max_height: Option<f32>,
Expand Down Expand Up @@ -86,6 +88,11 @@ impl EmbeddedFile {
self.data.status()
}

/// The icon stored by OneNote for this embedded file, when available.
pub fn icon(&self) -> Option<&Picture> {
self.icon.as_ref()
}

/// The max width of the embedded file's icon in half-inch increments.
///
/// See [\[MS-ONE\] 2.3.21].
Expand Down Expand Up @@ -138,12 +145,31 @@ pub(crate) fn parse_embedded_file(
.ok_or_else(|| ErrorKind::MalformedOneNoteData("embedded file is missing".into()))?;
let node = embedded_file_node::parse(node_object)?;

let icon = node.picture_container.and_then(|container_id| {
let result = space
.get_object(container_id)
.ok_or_else(|| {
ErrorKind::MalformedOneNoteData("embedded file icon container is missing".into())
.into()
})
.and_then(crate::one::property_set::picture_container::parse);

match result {
Ok(data) => Some(Picture::new(data.data, data.extension)),
Err(err) => {
warn!(ctx, "could not parse embedded file icon: {err}");
None
}
}
});

// Helper function to create a fallback for corrupted files
let create_fallback = |filename: String| -> Result<EmbeddedFile> {
Ok(EmbeddedFile {
filename,
file_type: node.file_type,
data: FileBlob::missing(),
icon: icon.clone(),
layout_max_width: node.layout_max_width,
layout_max_height: node.layout_max_height,
offset_horizontal: node.offset_from_parent_horiz,
Expand Down Expand Up @@ -184,6 +210,7 @@ pub(crate) fn parse_embedded_file(
filename: embedded_filename,
file_type: node.file_type,
data: container.into_value(),
icon,
layout_max_width: node.layout_max_width,
layout_max_height: node.layout_max_height,
offset_horizontal: node.offset_from_parent_horiz,
Expand Down
32 changes: 30 additions & 2 deletions crates/parser/src/onenote/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::one::property_set::{image_node, picture_container};
use crate::onenote::ParserContext;
use crate::onenote::iframe::{IFrame, parse_iframe};
use crate::onenote::note_tag::{NoteTag, parse_note_tags};
use crate::onenote::picture::Picture;
use crate::onestore::ObjectSpace;
use crate::onestore::shared::file_blob::{FileBlob, FileDataStatus};

Expand All @@ -17,6 +18,7 @@ use crate::onestore::shared::file_blob::{FileBlob, FileDataStatus};
pub struct Image {
pub(crate) data: Option<FileBlob>,
pub(crate) extension: Option<String>,
pub(crate) web_picture: Option<Picture>,

pub(crate) layout_max_width: Option<f32>,
pub(crate) layout_max_height: Option<f32>,
Expand Down Expand Up @@ -57,12 +59,12 @@ impl Image {
/// [`data_status`](Self::data_status) before reading to distinguish
/// invalid data from a valid zero-byte payload.
pub fn read(&self) -> Option<Box<dyn std::io::Read>> {
self.data.as_ref().map(|blob| blob.read())
self.data.as_ref().map(FileBlob::read)
}

/// The size of the image in bytes, or `None` if not yet uploaded.
pub fn size(&self) -> Option<u64> {
self.data.as_ref().map(|blob| blob.size())
self.data.as_ref().map(FileBlob::size)
}

/// Availability of the image's binary data.
Expand All @@ -81,6 +83,14 @@ impl Image {
self.extension.as_deref()
}

/// A browser-compatible picture stored as a fallback for the primary image.
///
/// OneNote may provide this when the primary representation uses a format
/// that browsers and other consumers cannot display directly.
pub fn web_picture(&self) -> Option<&Picture> {
self.web_picture.as_ref()
}

/// The maximum width to display the image in half-inch increments.
///
/// See [\[MS-ONE\] 2.3.21].
Expand Down Expand Up @@ -256,6 +266,23 @@ pub(crate) fn parse_image(
(None, None)
};

let web_picture = node.web_picture_container.and_then(|container_id| {
let result = space
.get_object(container_id)
.ok_or_else(|| {
ErrorKind::MalformedOneNoteData("web picture container is missing".into()).into()
})
.and_then(picture_container::parse);

match result {
Ok(data) => Some(Picture::new(data.data, data.extension)),
Err(err) => {
warn!(ctx, "could not parse web picture fallback: {err}");
None
}
}
});

let embed = node
.iframe
.into_iter()
Expand All @@ -265,6 +292,7 @@ pub(crate) fn parse_image(
let image = Image {
data,
extension,
web_picture,
layout_max_width: node.layout_max_width,
layout_max_height: node.layout_max_height,
alt_text: node.alt_text,
Expand Down
1 change: 1 addition & 0 deletions crates/parser/src/onenote/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub(crate) mod outline;
pub(crate) mod page;
pub(crate) mod page_content;
pub(crate) mod page_series;
pub(crate) mod picture;
pub(crate) mod rich_text;
pub(crate) mod section;
pub(crate) mod table;
Expand Down
62 changes: 62 additions & 0 deletions crates/parser/src/onenote/picture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use crate::onestore::shared::file_blob::{FileBlob, FileDataStatus};

/// A stored picture representation associated with a OneNote object.
///
/// Some objects carry supplemental pictures, such as a browser-compatible
/// fallback for an image or the icon for an embedded file. The bytes remain
/// backed by the parser's lazy file source and are read only on demand.
#[derive(Clone, PartialEq, Debug)]
pub struct Picture {
pub(crate) data: FileBlob,
pub(crate) extension: Option<String>,
}

impl Picture {
pub(crate) fn new(data: FileBlob, extension: Option<String>) -> Self {
Self { data, extension }
}

/// A [`std::io::Read`] over the picture's binary data.
pub fn read(&self) -> Box<dyn std::io::Read> {
self.data.read()
}

/// The size of the picture in bytes.
pub fn size(&self) -> u64 {
self.data.size()
}

/// Availability of the picture's binary data.
pub fn data_status(&self) -> FileDataStatus {
self.data.status()
}

/// The picture's file extension, when stored by OneNote.
pub fn extension(&self) -> Option<&str> {
self.extension.as_deref()
}
}

#[cfg(test)]
mod tests {
use super::Picture;
use crate::fs::file_source::BytesSource;
use crate::onestore::shared::file_blob::{FileBlob, FileDataStatus};
use bytes::Bytes;
use std::io::Read;
use std::sync::Arc;

#[test]
fn exposes_lazy_picture_data_and_metadata() {
let source = Arc::new(BytesSource::new(Bytes::from_static(b"picture")));
let picture = Picture::new(FileBlob::from_source(source, 0, 7), Some("png".to_owned()));

let mut bytes = Vec::new();
picture.read().read_to_end(&mut bytes).unwrap();

assert_eq!(bytes, b"picture");
assert_eq!(picture.size(), 7);
assert_eq!(picture.data_status(), FileDataStatus::Available);
assert_eq!(picture.extension(), Some("png"));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
source: crates/parser/tests/lib.rs
assertion_line: 70
assertion_line: 84
expression: parser.parse_section(path).unwrap()
---
Section {
Expand Down Expand Up @@ -639,6 +639,12 @@ Section {
filename: "testing.docx",
file_type: Unknown,
data: FileBlob [ 504b030414000600080000002100cd625ce778010000a5060000130008025b43 ... 637573746f6d2e786d6c504b0506000000000e000e0081030000ad3500000000; 14 KiB ],
icon: Some(
Picture {
data: FileBlob [ 89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7a ... 978e886a15139b10a9a10e00fadb902d8aa71aea0000000049454e44ae426082; 0 KiB ],
extension: None,
},
),
layout_max_width: None,
layout_max_height: None,
offset_horizontal: None,
Expand Down
1 change: 1 addition & 0 deletions crates/parser/tests/snapshots/lib__parse_notebook.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5933,6 +5933,7 @@ Notebook {
extension: Some(
".jpg",
),
web_picture: None,
layout_max_width: Some(
12.5,
),
Expand Down
1 change: 1 addition & 0 deletions crates/parser/tests/snapshots/lib__parse_notebook_new.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5933,6 +5933,7 @@ Notebook {
extension: Some(
".jpg",
),
web_picture: None,
layout_max_width: Some(
12.5,
),
Expand Down
1 change: 1 addition & 0 deletions crates/parser/tests/snapshots/lib__parse_section.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5930,6 +5930,7 @@ Section {
extension: Some(
".jpg",
),
web_picture: None,
layout_max_width: Some(
12.5,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5928,6 +5928,7 @@ Section {
FileBlob [ ffd8ffe000104a46494600010101004800480000ffdb00430003020203020203 ... f43c7f77dde2727c5dff0041dbc3cd9cfe99cdce2f4ce2f57c4feafb3e0fffd9; 88 KiB ],
),
extension: None,
web_picture: None,
layout_max_width: Some(
12.5,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8438,6 +8438,7 @@ Section {
extension: Some(
".png",
),
web_picture: None,
layout_max_width: Some(
10.0,
),
Expand Down
Loading
Loading