diff --git a/Cargo.lock b/Cargo.lock index 0117007..f80d3dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1796,7 +1796,7 @@ dependencies = [ [[package]] name = "onenote_parser" version = "2.0.0" -source = "git+https://github.com/msiemens/onenote.rs?tag=v2.0.0#fa4d7a044324af3bfe68727704a9789a08b36a3c" +source = "git+https://github.com/emsi/onenote.rs?rev=57694b1ca128d4a6c1fb222f31049be3e6830599#57694b1ca128d4a6c1fb222f31049be3e6830599" dependencies = [ "bytes", "encoding_rs", @@ -1817,7 +1817,7 @@ dependencies = [ [[package]] name = "onenote_parser-macros" version = "2.0.0" -source = "git+https://github.com/msiemens/onenote.rs?tag=v2.0.0#fa4d7a044324af3bfe68727704a9789a08b36a3c" +source = "git+https://github.com/emsi/onenote.rs?rev=57694b1ca128d4a6c1fb222f31049be3e6830599#57694b1ca128d4a6c1fb222f31049be3e6830599" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5617964..154608b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ onenote-core = { path = "crates/onenote-core" } onenote-index = { path = "crates/onenote-index" } onenote-render = { path = "crates/onenote-render" } onenote-render-gtk = { path = "crates/onenote-render-gtk" } -onenote_parser = { git = "https://github.com/msiemens/onenote.rs", tag = "v2.0.0" } +onenote_parser = { git = "https://github.com/emsi/onenote.rs", rev = "57694b1ca128d4a6c1fb222f31049be3e6830599" } rusqlite = { version = "0.32.1", features = ["bundled"] } sanitize-filename = "0.6.0" serde = { version = "1.0.219", features = ["derive", "rc"] } diff --git a/crates/onenote-core/src/lib.rs b/crates/onenote-core/src/lib.rs index 3b2f3a5..05087ff 100644 --- a/crates/onenote-core/src/lib.rs +++ b/crates/onenote-core/src/lib.rs @@ -27,7 +27,7 @@ pub use resource::{ }; /// The crate API version during the pre-1.0 implementation phase. -pub const API_VERSION: u32 = 6; +pub const API_VERSION: u32 = 7; /// Logical display pixels per `OneNote` half-inch layout unit at 96 DPI. pub const PIXELS_PER_HALF_INCH: f32 = 48.0; diff --git a/crates/onenote-core/src/model.rs b/crates/onenote-core/src/model.rs index edbdecd..0ca2c29 100644 --- a/crates/onenote-core/src/model.rs +++ b/crates/onenote-core/src/model.rs @@ -643,6 +643,9 @@ pub struct TableCell { pub struct Image { /// Lazy binary payload. pub resource: ResourceRef, + /// Optional browser-compatible representation stored by `OneNote`. + #[serde(default)] + pub web_fallback: Option, /// Display width in logical pixels. pub width: Option, /// Display height in logical pixels. @@ -662,6 +665,9 @@ pub struct Image { pub struct Attachment { /// Lazy binary payload. pub resource: ResourceRef, + /// Optional icon stored by `OneNote` for this embedded file. + #[serde(default)] + pub icon: Option, /// Display width in logical pixels. pub width: Option, /// Display height in logical pixels. diff --git a/crates/onenote-core/src/parser.rs b/crates/onenote-core/src/parser.rs index ff94d34..24bea69 100644 --- a/crates/onenote-core/src/parser.rs +++ b/crates/onenote-core/src/parser.rs @@ -11,7 +11,7 @@ use crate::{Error, ResourceStore, Result, PIXELS_PER_HALF_INCH}; use linkify::{LinkFinder, LinkKind}; use onenote_parser::contents::{ Content, EmbeddedFile, Image as ParserImage, Ink as ParserInk, List, Outline as ParserOutline, - OutlineElement as ParserOutlineElement, OutlineItem, ParagraphStyling, RichText, + OutlineElement as ParserOutlineElement, OutlineItem, ParagraphStyling, Picture, RichText, Table as ParserTable, }; use onenote_parser::notebook::Notebook as ParserNotebook; @@ -538,8 +538,9 @@ impl Projector { } fn image(&mut self, image: &ParserImage, key: &str) -> Result { + let required_resources = 1 + usize::from(image.web_picture().is_some()); self.enforce( - self.resources.len() < self.limits.max_resources, + self.resources.len().saturating_add(required_resources) <= self.limits.max_resources, "resource limit exceeded", )?; let id = ResourceId::new(self.id("resource", key)); @@ -556,8 +557,12 @@ impl Projector { }; self.resources .insert(id, ResourceLoader::Image(image.clone())); + let web_fallback = image.web_picture().map(|picture| { + self.picture_resource(picture, &format!("{key}/web-fallback"), "image-fallback") + }); Ok(Image { resource, + web_fallback, width: image .layout_max_width() .or_else(|| image.picture_width()) @@ -597,8 +602,9 @@ impl Projector { } fn attachment(&mut self, file: &EmbeddedFile, key: &str) -> Result { + let required_resources = 1 + usize::from(file.icon().is_some()); self.enforce( - self.resources.len() < self.limits.max_resources, + self.resources.len().saturating_add(required_resources) <= self.limits.max_resources, "resource limit exceeded", )?; let id = ResourceId::new(self.id("resource", key)); @@ -616,13 +622,32 @@ impl Projector { }; self.resources .insert(id, ResourceLoader::Attachment(file.clone())); + let icon = file + .icon() + .map(|picture| self.picture_resource(picture, &format!("{key}/icon"), "file-icon")); Ok(Attachment { resource, + icon, width: file.layout_max_width().map(half_inches), height: file.layout_max_height().map(half_inches), }) } + fn picture_resource(&mut self, picture: &Picture, key: &str, stem: &str) -> ResourceRef { + let id = ResourceId::new(self.id("resource", key)); + let extension = picture.extension().unwrap_or("bin").trim_start_matches('.'); + let resource = ResourceRef { + id: id.clone(), + name: format!("{stem}.{extension}"), + media_type: image_media_type(extension).to_owned(), + size: picture.size(), + status: resource_status(picture.data_status()), + }; + self.resources + .insert(id, ResourceLoader::Picture(picture.clone())); + resource + } + fn ink_object( &mut self, ink: &ParserInk, diff --git a/crates/onenote-core/src/resource.rs b/crates/onenote-core/src/resource.rs index 1f8014f..4c371ef 100644 --- a/crates/onenote-core/src/resource.rs +++ b/crates/onenote-core/src/resource.rs @@ -1,5 +1,5 @@ use crate::{Error, ResourceId, ResourceStatus, Result}; -use onenote_parser::contents::{EmbeddedFile, FileDataStatus, Image}; +use onenote_parser::contents::{EmbeddedFile, FileDataStatus, Image, Picture}; use std::collections::HashMap; use std::io::{Read, Write}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -65,6 +65,7 @@ impl ResourceCopyControl { #[derive(Clone, Debug)] pub(crate) enum ResourceLoader { Image(Image), + Picture(Picture), Attachment(EmbeddedFile), } @@ -72,6 +73,7 @@ impl ResourceLoader { fn size(&self) -> u64 { match self { Self::Image(image) => image.size().unwrap_or(0), + Self::Picture(picture) => picture.size(), Self::Attachment(file) => file.size(), } } @@ -79,6 +81,7 @@ impl ResourceLoader { fn status(&self) -> ResourceStatus { let status = match self { Self::Image(image) => image.data_status(), + Self::Picture(picture) => picture.data_status(), Self::Attachment(file) => file.data_status(), }; resource_status(status) @@ -87,6 +90,7 @@ impl ResourceLoader { fn reader(&self) -> Option> { match self { Self::Image(image) => image.read(), + Self::Picture(picture) => Some(picture.read()), Self::Attachment(file) => Some(file.read()), } } @@ -94,6 +98,7 @@ impl ResourceLoader { fn verified_size(&self) -> Option { match self { Self::Image(image) => image.size(), + Self::Picture(picture) => Some(picture.size()), Self::Attachment(file) => Some(file.size()), } } diff --git a/crates/onenote-core/tests/private_corpus.rs b/crates/onenote-core/tests/private_corpus.rs index 2579e62..b67a89e 100644 --- a/crates/onenote-core/tests/private_corpus.rs +++ b/crates/onenote-core/tests/private_corpus.rs @@ -495,9 +495,13 @@ fn resource_refs(entries: &[NotebookEntry]) -> Vec<&ResourceRef> { for page in §ion.pages { for object in &page.objects { match &object.kind { - ObjectKind::Image(image) => resources.push(&image.resource), + ObjectKind::Image(image) => { + resources.push(&image.resource); + resources.extend(image.web_fallback.iter()); + } ObjectKind::Attachment(attachment) => { resources.push(&attachment.resource); + resources.extend(attachment.icon.iter()); } ObjectKind::Outline(outline) => { for element in &outline.elements { @@ -521,8 +525,14 @@ fn element_resource_refs<'a>( ) { for content in &element.content { match content { - ElementContent::Image(image) => output.push(&image.resource), - ElementContent::Attachment(attachment) => output.push(&attachment.resource), + ElementContent::Image(image) => { + output.push(&image.resource); + output.extend(image.web_fallback.iter()); + } + ElementContent::Attachment(attachment) => { + output.push(&attachment.resource); + output.extend(attachment.icon.iter()); + } ElementContent::Table(table) => { for row in &table.rows { for cell in row { diff --git a/crates/onenote-render-gtk/src/canvas.rs b/crates/onenote-render-gtk/src/canvas.rs index ab2c366..a66b911 100644 --- a/crates/onenote-render-gtk/src/canvas.rs +++ b/crates/onenote-render-gtk/src/canvas.rs @@ -1,4 +1,4 @@ -use crate::image_cache::{self, DecodedImage, MAX_TEXTURE_CACHE_BYTES}; +use crate::image_cache::{self, DecodedImage, ImageDecodeFailure, MAX_TEXTURE_CACHE_BYTES}; use crate::math_cache::{self, MathKey, MathSize, TypstMathBackend}; use crate::resolved_layout::ResolvedLayout; use crate::text; @@ -30,9 +30,10 @@ type ActionHandler = Rc; mod imp { use super::{ - gdk, glib, ActionHandler, Arc, Cell, HashMap, HashSet, MathKey, MathLayoutBackend, - OnceLock, PageScene, Rc, RefCell, ResolvedLayout, ResourceId, ResourceStore, SceneNodeId, - TypstMathBackend, CANVAS_MARGIN, DEFAULT_ZOOM, MAX_ZOOM, MIN_ZOOM, + gdk, glib, ActionHandler, Arc, Cell, HashMap, HashSet, ImageDecodeFailure, MathKey, + MathLayoutBackend, OnceLock, PageScene, Rc, RefCell, ResolvedLayout, ResourceId, + ResourceStore, SceneNodeId, TypstMathBackend, CANVAS_MARGIN, DEFAULT_ZOOM, MAX_ZOOM, + MIN_ZOOM, }; use gtk::prelude::*; use gtk::subclass::prelude::ObjectSubclassIsExt; @@ -51,8 +52,9 @@ mod imp { pub(super) layout_generation: Cell, pub(super) textures: RefCell>, pub(super) pending: RefCell>, - pub(super) failed: RefCell>, + pub(super) failed: RefCell>, pub(super) texture_bytes: Cell, + pub(super) texture_generation: Cell, pub(super) math_textures: RefCell>, pub(super) math_pending: RefCell>, pub(super) math_errors: RefCell>, @@ -80,6 +82,7 @@ mod imp { pending: RefCell::default(), failed: RefCell::default(), texture_bytes: Cell::default(), + texture_generation: Cell::default(), math_textures: RefCell::default(), math_pending: RefCell::default(), math_errors: RefCell::default(), @@ -244,6 +247,8 @@ impl PageCanvas { imp.pending.borrow_mut().clear(); imp.failed.borrow_mut().clear(); imp.texture_bytes.set(0); + imp.texture_generation + .set(imp.texture_generation.get().wrapping_add(1)); imp.math_textures.borrow_mut().clear(); imp.math_pending.borrow_mut().clear(); imp.math_errors.borrow_mut().clear(); @@ -266,6 +271,9 @@ impl PageCanvas { self.imp().pending.borrow_mut().clear(); self.imp().failed.borrow_mut().clear(); self.imp().texture_bytes.set(0); + self.imp() + .texture_generation + .set(self.imp().texture_generation.get().wrapping_add(1)); self.queue_draw(); } @@ -682,28 +690,7 @@ impl PageCanvas { } snapshot.restore(); } - ScenePrimitive::Image(image) => { - if let Some(texture) = self.texture(&image.resource.id) { - snapshot.append_texture(&texture, &graphene_rect(node_bounds)); - } else if self.imp().failed.borrow().contains(&image.resource.id) { - snapshot.append_color( - &gdk::RGBA::new(1.0, 0.92, 0.92, 1.0), - &graphene_rect(node_bounds), - ); - self.snapshot_label( - snapshot, - node_bounds, - "Image unavailable", - gdk::RGBA::new(0.55, 0.08, 0.08, 1.0), - ); - } else { - snapshot.append_color( - &gdk::RGBA::new(0.94, 0.94, 0.94, 1.0), - &graphene_rect(node_bounds), - ); - self.request_texture(image.resource.id.clone()); - } - } + ScenePrimitive::Image(image) => self.snapshot_image(snapshot, node_bounds, image), ScenePrimitive::Attachment(attachment) => { let hovered = self.imp().hovered_attachment.borrow().as_ref() == Some(&attachment.resource.id); @@ -737,6 +724,38 @@ impl PageCanvas { } } + fn snapshot_image(&self, snapshot: >k::Snapshot, bounds: Rect, image: &onenote_core::Image) { + if let Some(texture) = self.texture(&image.resource.id) { + snapshot.append_texture(&texture, &graphene_rect(bounds)); + return; + } + let Some(primary_failure) = self.image_failure(&image.resource.id) else { + Self::snapshot_image_loading(snapshot, bounds); + self.request_texture(image.resource.id.clone()); + return; + }; + let Some(fallback) = &image.web_fallback else { + self.snapshot_image_failure( + snapshot, + bounds, + image_failure_label(primary_failure, None), + ); + return; + }; + if let Some(texture) = self.texture(&fallback.id) { + snapshot.append_texture(&texture, &graphene_rect(bounds)); + } else if let Some(fallback_failure) = self.image_failure(&fallback.id) { + self.snapshot_image_failure( + snapshot, + bounds, + image_failure_label(primary_failure, Some(fallback_failure)), + ); + } else { + Self::snapshot_image_loading(snapshot, bounds); + self.request_texture(fallback.id.clone()); + } + } + fn snapshot_attachment( &self, snapshot: >k::Snapshot, @@ -774,7 +793,24 @@ impl PageCanvas { }; snapshot.append_color(&background, &graphene_rect(bounds)); snapshot_attachment_border(snapshot, bounds, border, 1.0); - snapshot_file_icon(snapshot, bounds, accent); + let icon_bounds = Rect { + x: bounds.x + 4.0, + y: bounds.y + (bounds.height - 24.0).max(0.0) / 2.0, + width: bounds.width.clamp(1.0, 24.0), + height: bounds.height.clamp(1.0, 24.0), + }; + if let Some(icon) = &attachment.icon { + if let Some(texture) = self.texture(&icon.id) { + snapshot.append_texture(&texture, &graphene_rect(icon_bounds)); + } else { + snapshot_file_icon(snapshot, bounds, accent); + if self.image_failure(&icon.id).is_none() { + self.request_texture(icon.id.clone()); + } + } + } else { + snapshot_file_icon(snapshot, bounds, accent); + } let label_bounds = Rect { x: bounds.x + 32.0, @@ -829,9 +865,33 @@ impl PageCanvas { .map(|entry| entry.texture.clone()) } + fn image_failure(&self, id: &ResourceId) -> Option { + self.imp().failed.borrow().get(id).copied() + } + + fn snapshot_image_loading(snapshot: >k::Snapshot, bounds: Rect) { + snapshot.append_color( + &gdk::RGBA::new(0.94, 0.94, 0.94, 1.0), + &graphene_rect(bounds), + ); + } + + fn snapshot_image_failure(&self, snapshot: >k::Snapshot, bounds: Rect, label: &str) { + snapshot.append_color( + &gdk::RGBA::new(1.0, 0.92, 0.92, 1.0), + &graphene_rect(bounds), + ); + self.snapshot_label( + snapshot, + bounds, + label, + gdk::RGBA::new(0.55, 0.08, 0.08, 1.0), + ); + } + fn request_texture(&self, id: ResourceId) { let imp = self.imp(); - if imp.failed.borrow().contains(&id) + if imp.failed.borrow().contains_key(&id) || imp.pending.borrow().contains(&id) || imp.textures.borrow().contains_key(&id) { @@ -841,11 +901,12 @@ impl PageCanvas { return; }; imp.pending.borrow_mut().insert(id.clone()); + let generation = imp.texture_generation.get(); let weak: glib::SendWeakRef = self.downgrade().into(); image_cache::spawn_decode(resources, id, move |id, decoded| { glib::MainContext::default().invoke(move || { if let Some(canvas) = weak.upgrade() { - canvas.finish_texture(id, decoded); + canvas.finish_texture(generation, id, decoded); } }); }); @@ -965,17 +1026,30 @@ impl PageCanvas { self.queue_draw(); } - fn finish_texture(&self, id: ResourceId, decoded: Option) { + fn finish_texture( + &self, + generation: u64, + id: ResourceId, + decoded: Result, + ) { let imp = self.imp(); - imp.pending.borrow_mut().remove(&id); - let Some(decoded) = decoded else { - imp.failed.borrow_mut().insert(id); - self.queue_draw(); + if generation != imp.texture_generation.get() { return; + } + imp.pending.borrow_mut().remove(&id); + let decoded = match decoded { + Ok(decoded) => decoded, + Err(failure) => { + imp.failed.borrow_mut().insert(id, failure); + self.queue_draw(); + return; + } }; let (id, texture, bytes) = image_cache::texture(decoded); if bytes > MAX_TEXTURE_CACHE_BYTES { - imp.failed.borrow_mut().insert(id); + imp.failed + .borrow_mut() + .insert(id, ImageDecodeFailure::CannotDisplay); self.queue_draw(); return; } @@ -1137,6 +1211,19 @@ fn perceived_lightness(color: gdk::RGBA) -> f32 { color.red() * 0.2126 + color.green() * 0.7152 + color.blue() * 0.0722 } +fn image_failure_label( + primary: ImageDecodeFailure, + fallback: Option, +) -> &'static str { + if primary == ImageDecodeFailure::Unavailable + && fallback.is_none_or(|failure| failure == ImageDecodeFailure::Unavailable) + { + "Image unavailable" + } else { + "Image cannot be displayed" + } +} + fn ink_bounds(strokes: &[onenote_core::InkStroke]) -> Option { strokes .iter() @@ -1228,7 +1315,8 @@ fn f64_to_f32(value: f64) -> f32 { #[cfg(test)] mod tests { use super::{ - next_attachment_index, normalize_zoom, PageCanvas, DEFAULT_ZOOM, MAX_ZOOM, MIN_ZOOM, + image_failure_label, next_attachment_index, normalize_zoom, ImageDecodeFailure, PageCanvas, + DEFAULT_ZOOM, MAX_ZOOM, MIN_ZOOM, }; use gtk::prelude::*; use gtk::subclass::prelude::ObjectSubclassIsExt; @@ -1263,6 +1351,28 @@ mod tests { assert_eq!(next_attachment_index(None, 0, false), None); } + #[test] + fn image_failure_labels_distinguish_missing_and_undecodable_data() { + assert_eq!( + image_failure_label(ImageDecodeFailure::Unavailable, None), + "Image unavailable" + ); + assert_eq!( + image_failure_label( + ImageDecodeFailure::Unavailable, + Some(ImageDecodeFailure::Unavailable) + ), + "Image unavailable" + ); + assert_eq!( + image_failure_label( + ImageDecodeFailure::CannotDisplay, + Some(ImageDecodeFailure::Unavailable) + ), + "Image cannot be displayed" + ); + } + #[test] fn attachment_pointer_and_click_dispatch_the_resource_action() { if std::env::var_os("DISPLAY").is_none() && std::env::var_os("WAYLAND_DISPLAY").is_none() { @@ -1297,6 +1407,7 @@ mod tests { size: 12, status: ResourceStatus::Available, }, + icon: None, width: None, height: None, }), diff --git a/crates/onenote-render-gtk/src/image_cache.rs b/crates/onenote-render-gtk/src/image_cache.rs index cc260c4..33710c0 100644 --- a/crates/onenote-render-gtk/src/image_cache.rs +++ b/crates/onenote-render-gtk/src/image_cache.rs @@ -2,7 +2,7 @@ use gtk::gdk; use gtk::glib; use gtk::prelude::Cast; use image::{ImageReader, Limits}; -use onenote_core::{ResourceId, ResourceStore}; +use onenote_core::{Error, ResourceId, ResourceStatus, ResourceStore}; use std::io::Cursor; use std::sync::Arc; @@ -18,20 +18,44 @@ pub(crate) struct DecodedImage { pub(crate) bytes: Vec, } -pub(crate) fn decode(resources: &ResourceStore, id: &ResourceId) -> Option { - let encoded = resources.read_limited(id, MAX_ENCODED_IMAGE_BYTES).ok()?; +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum ImageDecodeFailure { + Unavailable, + CannotDisplay, +} + +pub(crate) fn decode( + resources: &ResourceStore, + id: &ResourceId, +) -> Result { + if resources.status(id).ok() != Some(ResourceStatus::Available) { + return Err(ImageDecodeFailure::Unavailable); + } + let encoded = resources + .read_limited(id, MAX_ENCODED_IMAGE_BYTES) + .map_err(|error| match error { + Error::ResourceTooLarge { .. } => ImageDecodeFailure::CannotDisplay, + _ => ImageDecodeFailure::Unavailable, + })?; + decode_encoded(id, encoded) +} + +fn decode_encoded(id: &ResourceId, encoded: Vec) -> Result { let mut reader = ImageReader::new(Cursor::new(encoded)) .with_guessed_format() - .ok()?; + .map_err(|_| ImageDecodeFailure::CannotDisplay)?; let mut limits = Limits::default(); limits.max_image_width = Some(MAX_IMAGE_DIMENSION); limits.max_image_height = Some(MAX_IMAGE_DIMENSION); limits.max_alloc = Some(MAX_DECODED_IMAGE_BYTES); reader.limits(limits); - let decoded = reader.decode().ok()?.into_rgba8(); - let width = i32::try_from(decoded.width()).ok()?; - let height = i32::try_from(decoded.height()).ok()?; - Some(DecodedImage { + let decoded = reader + .decode() + .map_err(|_| ImageDecodeFailure::CannotDisplay)? + .into_rgba8(); + let width = i32::try_from(decoded.width()).map_err(|_| ImageDecodeFailure::CannotDisplay)?; + let height = i32::try_from(decoded.height()).map_err(|_| ImageDecodeFailure::CannotDisplay)?; + Ok(DecodedImage { id: id.clone(), width, height, @@ -57,10 +81,38 @@ pub(crate) fn texture(decoded: DecodedImage) -> (ResourceId, gdk::Texture, usize pub(crate) fn spawn_decode( resources: Arc, id: ResourceId, - callback: impl FnOnce(ResourceId, Option) + Send + 'static, + callback: impl FnOnce(ResourceId, Result) + Send + 'static, ) { std::thread::spawn(move || { let decoded = decode(&resources, &id); callback(id, decoded); }); } + +#[cfg(test)] +mod tests { + use super::{decode_encoded, ImageDecodeFailure}; + use image::{DynamicImage, ImageFormat}; + use onenote_core::ResourceId; + use std::io::Cursor; + + #[test] + fn decodes_a_supported_picture() { + let mut encoded = Cursor::new(Vec::new()); + DynamicImage::new_rgba8(1, 1) + .write_to(&mut encoded, ImageFormat::Png) + .unwrap(); + + let decoded = decode_encoded(&ResourceId::new("valid"), encoded.into_inner()).unwrap(); + + assert_eq!((decoded.width, decoded.height), (1, 1)); + assert_eq!(decoded.bytes.len(), 4); + } + + #[test] + fn invalid_picture_data_has_a_display_failure() { + let result = decode_encoded(&ResourceId::new("invalid"), b"not an image".to_vec()); + + assert!(matches!(result, Err(ImageDecodeFailure::CannotDisplay))); + } +} diff --git a/crates/onenote-render/src/builder.rs b/crates/onenote-render/src/builder.rs index 027f48a..f0c2bef 100644 --- a/crates/onenote-render/src/builder.rs +++ b/crates/onenote-render/src/builder.rs @@ -1371,6 +1371,7 @@ mod tests { z_index: 0, kind: ObjectKind::Image(Image { resource: unavailable("image", ResourceStatus::Invalid), + web_fallback: None, width: None, height: None, alt_text: None, @@ -1391,6 +1392,7 @@ mod tests { z_index: 1, kind: ObjectKind::Attachment(Attachment { resource: unavailable("report.pdf", ResourceStatus::Missing), + icon: None, width: None, height: None, }), diff --git a/docs/specs/public-api.md b/docs/specs/public-api.md index 3d532f7..bd5f20a 100644 --- a/docs/specs/public-api.md +++ b/docs/specs/public-api.md @@ -52,7 +52,9 @@ payload lengths. It returns typed read, write, cancellation, size-limit, and size-mismatch errors. Callers choose their worker/thread model and own durable destination publication; the core library never selects paths, creates a cache, or launches an attachment. This additive contract is exposed by core -API version 6. +API version 6. API version 7 adds optional lazy resource handles for OneNote's +browser-compatible image representation and embedded-file icon; primary +payload handles remain unchanged. The public model includes: diff --git a/packaging/flatpak/cargo-sources.json b/packaging/flatpak/cargo-sources.json index 82cc72c..3785826 100644 --- a/packaging/flatpak/cargo-sources.json +++ b/packaging/flatpak/cargo-sources.json @@ -1,9 +1,9 @@ [ { "type": "git", - "url": "https://github.com/msiemens/onenote.rs", - "commit": "fa4d7a044324af3bfe68727704a9789a08b36a3c", - "dest": "flatpak-cargo/git/onenote.rs-fa4d7a0" + "url": "https://github.com/emsi/onenote.rs", + "commit": "57694b1ca128d4a6c1fb222f31049be3e6830599", + "dest": "flatpak-cargo/git/onenote.rs-57694b1" }, { "type": "archive", @@ -2348,7 +2348,7 @@ { "type": "shell", "commands": [ - "cp -r --reflink=auto \"flatpak-cargo/git/onenote.rs-fa4d7a0/crates/parser\" \"cargo/vendor/onenote_parser\"" + "cp -r --reflink=auto \"flatpak-cargo/git/onenote.rs-57694b1/crates/parser\" \"cargo/vendor/onenote_parser\"" ] }, { @@ -2366,7 +2366,7 @@ { "type": "shell", "commands": [ - "cp -r --reflink=auto \"flatpak-cargo/git/onenote.rs-fa4d7a0/crates/parser-macros\" \"cargo/vendor/onenote_parser-macros\"" + "cp -r --reflink=auto \"flatpak-cargo/git/onenote.rs-57694b1/crates/parser-macros\" \"cargo/vendor/onenote_parser-macros\"" ] }, { @@ -5100,7 +5100,7 @@ }, { "type": "inline", - "contents": "[source.vendored-sources]\ndirectory = \"cargo/vendor\"\n\n[source.crates-io]\nreplace-with = \"vendored-sources\"\n\n[source.\"https://github.com/msiemens/onenote.rs\"]\ngit = \"https://github.com/msiemens/onenote.rs\"\nreplace-with = \"vendored-sources\"\ntag = \"v2.0.0\"\n", + "contents": "[source.vendored-sources]\ndirectory = \"cargo/vendor\"\n\n[source.crates-io]\nreplace-with = \"vendored-sources\"\n\n[source.\"https://github.com/emsi/onenote.rs\"]\ngit = \"https://github.com/emsi/onenote.rs\"\nreplace-with = \"vendored-sources\"\nrev = \"57694b1ca128d4a6c1fb222f31049be3e6830599\"\n", "dest": "cargo", "dest-filename": "config" }