diff --git a/assets/refs/apple_color_emoji.png b/assets/refs/apple_color_emoji.png new file mode 100644 index 00000000..65b34c45 Binary files /dev/null and b/assets/refs/apple_color_emoji.png differ diff --git a/krilla-macros/src/lib.rs b/krilla-macros/src/lib.rs index 827c7e24..93f47191 100644 --- a/krilla-macros/src/lib.rs +++ b/krilla-macros/src/lib.rs @@ -51,31 +51,42 @@ pub fn snapshot(attr: TokenStream, item: TokenStream) -> TokenStream { let impl_ident = Ident::new(&format!("{}_impl", fn_name), fn_name.span()); input_fn.sig.ident = impl_ident.clone(); + let common = quote! { + use crate::tests::SKIP_SNAPSHOT; + + if SKIP_SNAPSHOT.is_some() { + return; + } + }; + let fn_content = match mode { SnapshotMode::SerializerContext => { quote! { + #common let settings = SerializeSettings::#serialize_settings(); let mut sc = SerializerContext::new(settings); #impl_ident(&mut sc); - check_snapshot(#snapshot_name, sc.finish().as_bytes(), false); + check_snapshot(#snapshot_name, sc.finish().unwrap().as_bytes(), false); } } SnapshotMode::SinglePage => { quote! { + #common let settings = SerializeSettings::#serialize_settings(); let mut db = Document::new(settings); let mut page = db.start_page(Size::from_wh(200.0, 200.0).unwrap()); #impl_ident(&mut page); page.finish(); - check_snapshot(#snapshot_name, &db.finish(), true); + check_snapshot(#snapshot_name, &db.finish().unwrap(), true); } } SnapshotMode::Document => { quote! { + #common let settings = SerializeSettings::#serialize_settings(); let mut db = Document::new(settings); #impl_ident(&mut db); - check_snapshot(#snapshot_name, &db.finish(), true); + check_snapshot(#snapshot_name, &db.finish().unwrap(), true); } } }; @@ -113,7 +124,7 @@ impl RendererExt for Renderer { #[proc_macro_attribute] pub fn visreg(attr: TokenStream, item: TokenStream) -> TokenStream { let attrs = parse_macro_input!(attr as AttributeInput); - let serialize_settings = format_ident!("default"); + let mut serialize_settings = format_ident!("default"); let mut pdfium = false; let mut mupdf = false; @@ -136,6 +147,12 @@ pub fn visreg(attr: TokenStream, item: TokenStream) -> TokenStream { for identifier in attrs.identifiers { let string_ident = identifier.to_string(); + + if string_ident.starts_with("settings") { + serialize_settings = identifier.clone(); + continue; + } + match string_ident.as_str() { "pdfium" => pdfium = true, "mupdf" => mupdf = true, @@ -175,7 +192,7 @@ pub fn visreg(attr: TokenStream, item: TokenStream) -> TokenStream { let settings = SerializeSettings::#serialize_settings(); let mut db = Document::new(settings); #impl_ident(&mut db); - let pdf = db.finish(); + let pdf = db.finish().unwrap(); let rendered = render_document(&pdf, &renderer); check_render(stringify!(#fn_name), &renderer, rendered, &pdf, #ignore_renderer); @@ -189,7 +206,7 @@ pub fn visreg(attr: TokenStream, item: TokenStream) -> TokenStream { #impl_ident(&mut surface); surface.finish(); page.finish(); - let pdf = db.finish(); + let pdf = db.finish().unwrap(); let rendered = render_document(&pdf, &renderer); check_render(stringify!(#fn_name), &renderer, rendered, &pdf, #ignore_renderer); @@ -211,12 +228,16 @@ pub fn visreg(attr: TokenStream, item: TokenStream) -> TokenStream { #quartz_snippet #[test] fn #name() { - use crate::tests::{render_document, check_render}; + use crate::tests::{render_document, check_render, SKIP_VISREG}; use crate::Size; use crate::document::Document; use crate::serialize::SerializeSettings; use sitro::Renderer; let renderer = #renderer_ident; + + if SKIP_VISREG.is_some() { + return; + } #fn_body } } diff --git a/src/document.rs b/src/document.rs index b5eb6ce5..9bcc9a61 100644 --- a/src/document.rs +++ b/src/document.rs @@ -1,3 +1,4 @@ +use crate::error::KrillaResult; use crate::object::outline::Outline; use crate::object::page::PageLabel; use crate::serialize::{SerializeSettings, SerializerContext}; @@ -31,7 +32,7 @@ impl Document { self.serializer_context.set_outline(outline); } - pub fn finish(self) -> Vec { - self.serializer_context.finish().finish() + pub fn finish(self) -> KrillaResult> { + Ok(self.serializer_context.finish()?.finish()) } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..9640fad7 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,7 @@ +pub type KrillaResult = Result; + +#[derive(Debug, PartialEq, Eq)] +pub enum KrillaError { + Font(String), + GlyphDrawing(String), +} diff --git a/src/font/bitmap.rs b/src/font/bitmap.rs index 27744f9c..ece89d12 100644 --- a/src/font/bitmap.rs +++ b/src/font/bitmap.rs @@ -1,3 +1,4 @@ +use crate::error::{KrillaError, KrillaResult}; use crate::font::Font; use crate::object::image::Image; use crate::surface::Surface; @@ -5,7 +6,7 @@ use skrifa::raw::TableProvider; use skrifa::{GlyphId, MetadataProvider, Tag}; use tiny_skia_path::{Size, Transform}; -pub fn draw_glyph(font: Font, glyph: GlyphId, surface: &mut Surface) -> Option<()> { +pub fn draw_glyph(font: Font, glyph: GlyphId, surface: &mut Surface) -> KrillaResult> { let metrics = font .font_ref() .metrics(skrifa::instance::Size::unscaled(), font.location_ref()); @@ -22,7 +23,9 @@ pub fn draw_glyph(font: Font, glyph: GlyphId, surface: &mut Surface) -> Option<( let ppem = strike.ppem() as f32; if data.graphic_type() == Tag::new(b"png ") { - let dynamic_image = image::load_from_memory(data.data()).ok().unwrap(); + let dynamic_image = image::load_from_memory(data.data()).map_err(|_| { + KrillaError::GlyphDrawing("failed to decode png of glyph ".to_string()) + })?; let size_factor = upem / (ppem); let width = dynamic_image.width() as f32 * size_factor; let height = dynamic_image.height() as f32 * size_factor; @@ -41,10 +44,10 @@ pub fn draw_glyph(font: Font, glyph: GlyphId, surface: &mut Surface) -> Option<( surface.draw_image(Image::new(&dynamic_image), size); surface.pop(); - return Some(()); + return Ok(Some(())); } } } - None + Ok(None) } diff --git a/src/font/colr.rs b/src/font/colr.rs index 56cb1747..fcf032f0 100644 --- a/src/font/colr.rs +++ b/src/font/colr.rs @@ -1,3 +1,4 @@ +use crate::error::{KrillaError, KrillaResult}; use crate::font::{Font, OutlineBuilder}; use crate::object::color_space::rgb; use crate::object::color_space::rgb::Rgb; @@ -13,16 +14,26 @@ use skrifa::raw::TableProvider; use skrifa::{GlyphId, MetadataProvider}; use tiny_skia_path::{NormalizedF32, Path, PathBuilder, Transform}; -pub fn draw_glyph(font: Font, glyph: GlyphId, surface: &mut Surface) -> Option<()> { +pub fn draw_glyph(font: Font, glyph: GlyphId, surface: &mut Surface) -> KrillaResult> { let colr_glyphs = font.font_ref().color_glyphs(); + if let Some(colr_glyph) = colr_glyphs.get(glyph) { surface.push_transform(&Transform::from_scale(1.0, -1.0)); let mut colr_canvas = ColrCanvas::new(font.clone(), surface); - let _ = colr_glyph.paint(font.location_ref(), &mut colr_canvas); + colr_glyph + .paint(font.location_ref(), &mut colr_canvas) + .map_err(|e| { + KrillaError::GlyphDrawing(format!( + "failed to draw glyph COLR glyph {} of font {}: {}", + glyph, + font.postscript_name().unwrap_or("unknown"), + e + )) + })?; surface.pop(); - return Some(()); + return Ok(Some(())); } else { - return None; + return Ok(None); } } @@ -31,6 +42,7 @@ struct ColrCanvas<'a, 'b> { clips: Vec>, canvas_builder: &'b mut Surface<'a>, transforms: Vec, + error: KrillaResult<()>, } impl<'a, 'b> ColrCanvas<'a, 'b> { @@ -40,6 +52,7 @@ impl<'a, 'b> ColrCanvas<'a, 'b> { canvas_builder, transforms: vec![Transform::identity()], clips: vec![vec![]], + error: Ok(()), } } } @@ -49,39 +62,45 @@ impl<'a, 'b> ColrCanvas<'a, 'b> { &self, palette_index: u16, alpha: f32, - ) -> (rgb::Color, NormalizedF32) { + ) -> KrillaResult<(rgb::Color, NormalizedF32)> { if palette_index != u16::MAX { let color = self .font .font_ref() .cpal() - .unwrap() + .map_err(|_| { + KrillaError::GlyphDrawing("missing CPAL table in COLR font".to_string()) + })? .color_records_array() - .unwrap() - .unwrap()[palette_index as usize]; + .ok_or(KrillaError::GlyphDrawing( + "missing color records array in CPAL table".to_string(), + ))? + .map_err(|_| KrillaError::GlyphDrawing("unable to read CPAL table".to_string()))? + [palette_index as usize]; - ( + Ok(( rgb::Color::new(color.red, color.green, color.blue), NormalizedF32::new(alpha * color.alpha as f32 / 255.0).unwrap(), - ) + )) } else { - (rgb::Color::new(0, 0, 0), NormalizedF32::new(alpha).unwrap()) + Ok((rgb::Color::new(0, 0, 0), NormalizedF32::new(alpha).unwrap())) } } - fn stops(&self, stops: &[ColorStop]) -> Vec> { - stops - .iter() - .map(|s| { - let (color, alpha) = self.palette_index_to_color(s.palette_index, s.alpha); + fn stops(&self, stops: &[ColorStop]) -> KrillaResult>> { + let mut converted_stops = vec![]; - Stop { - offset: NormalizedF32::new(s.offset).unwrap(), - color, - opacity: alpha, - } + for stop in stops { + let (color, alpha) = self.palette_index_to_color(stop.palette_index, stop.alpha)?; + + converted_stops.push(Stop { + offset: NormalizedF32::new(stop.offset).unwrap(), + color, + opacity: alpha, }) - .collect::>() + } + + Ok(converted_stops) } } @@ -102,18 +121,21 @@ impl ExtendExt for skrifa::color::Extend { impl<'a, 'b> ColorPainter for ColrCanvas<'a, 'b> { fn push_transform(&mut self, transform: skrifa::color::Transform) { - let new_transform = self - .transforms - .last() - .unwrap() - .pre_concat(Transform::from_row( - transform.xx, - transform.yx, - transform.xy, - transform.yy, - transform.dx, - transform.dy, + let Some(last_transform) = self.transforms.last() else { + self.error = Err(KrillaError::GlyphDrawing( + "encountered imbalanced transform".to_string(), )); + return; + }; + + let new_transform = last_transform.pre_concat(Transform::from_row( + transform.xx, + transform.yx, + transform.xy, + transform.yy, + transform.dx, + transform.dy, + )); self.transforms.push(new_transform); } @@ -122,22 +144,52 @@ impl<'a, 'b> ColorPainter for ColrCanvas<'a, 'b> { } fn push_clip_glyph(&mut self, glyph_id: GlyphId) { - let mut old = self.clips.last().unwrap().clone(); + let Some(mut old) = self.clips.last().cloned() else { + self.error = Err(KrillaError::GlyphDrawing( + "encountered imbalanced clip".to_string(), + )); + return; + }; let mut glyph_builder = OutlineBuilder(PathBuilder::new()); let outline_glyphs = self.font.font_ref().outline_glyphs(); - let outline_glyph = outline_glyphs.get(glyph_id).unwrap(); - outline_glyph + let Some(outline_glyph) = outline_glyphs.get(glyph_id) else { + self.error = Err(KrillaError::GlyphDrawing(format!( + "missing outline glyph for glyph {}", + glyph_id + ))); + return; + }; + + let drawn_outline_glyph = outline_glyph .draw( DrawSettings::unhinted(skrifa::instance::Size::unscaled(), LocationRef::default()), &mut glyph_builder, ) - .unwrap(); - let path = glyph_builder + .map_err(|e| { + KrillaError::GlyphDrawing(format!( + "failed to draw outline glyph {}: {}", + glyph_id, e + )) + }); + + match drawn_outline_glyph { + Ok(_) => {} + Err(e) => { + self.error = Err(e); + return; + } + } + + let Some(path) = glyph_builder .finish() - .unwrap() - .transform(*self.transforms.last().unwrap()) - .unwrap(); + .and_then(|p| p.transform(*self.transforms.last()?)) + else { + self.error = Err(KrillaError::GlyphDrawing( + "failed to build glyph path".to_string(), + )); + return; + }; old.push(path); @@ -145,7 +197,12 @@ impl<'a, 'b> ColorPainter for ColrCanvas<'a, 'b> { } fn push_clip_box(&mut self, clip_box: BoundingBox) { - let mut old = self.clips.last().unwrap().clone(); + let Some(mut old) = self.clips.last().cloned() else { + self.error = Err(KrillaError::GlyphDrawing( + "encountered imbalanced clip".to_string(), + )); + return; + }; let mut path_builder = PathBuilder::new(); path_builder.move_to(clip_box.x_min, clip_box.y_min); @@ -154,11 +211,15 @@ impl<'a, 'b> ColorPainter for ColrCanvas<'a, 'b> { path_builder.line_to(clip_box.x_max, clip_box.y_min); path_builder.close(); - let path = path_builder + let Some(path) = path_builder .finish() - .unwrap() - .transform(*self.transforms.last().unwrap()) - .unwrap(); + .and_then(|p| p.transform(*self.transforms.last()?)) + else { + self.error = Err(KrillaError::GlyphDrawing( + "failed to build glyph path".to_string(), + )); + return; + }; old.push(path); self.clips.push(old); @@ -174,7 +235,13 @@ impl<'a, 'b> ColorPainter for ColrCanvas<'a, 'b> { palette_index, alpha, } => { - let (color, alpha) = self.palette_index_to_color(palette_index, alpha); + let (color, alpha) = match self.palette_index_to_color(palette_index, alpha) { + Ok(c) => c, + Err(e) => { + self.error = Err(e); + return; + } + }; Some(Fill { paint: Paint::Color(color), opacity: alpha, @@ -187,14 +254,29 @@ impl<'a, 'b> ColorPainter for ColrCanvas<'a, 'b> { color_stops, extend, } => { + let stops = match self.stops(color_stops) { + Ok(s) => s, + Err(e) => { + self.error = Err(e); + return; + } + }; + + let Some(transform) = self.transforms.last().copied() else { + self.error = Err(KrillaError::GlyphDrawing( + "encountered imbalanced transforms".to_string(), + )); + return; + }; + let linear = LinearGradient { x1: p0.x, y1: p0.y, x2: p1.x, y2: p1.y, - stops: self.stops(color_stops), + stops, spread_method: extend.to_spread_method(), - transform: *self.transforms.last().unwrap(), + transform, }; Some(Fill { @@ -211,6 +293,21 @@ impl<'a, 'b> ColorPainter for ColrCanvas<'a, 'b> { color_stops, extend, } => { + let stops = match self.stops(color_stops) { + Ok(s) => s, + Err(e) => { + self.error = Err(e); + return; + } + }; + + let Some(transform) = self.transforms.last().copied() else { + self.error = Err(KrillaError::GlyphDrawing( + "encountered imbalanced transforms".to_string(), + )); + return; + }; + let radial = RadialGradient { fx: c0.x, fy: c0.y, @@ -218,9 +315,9 @@ impl<'a, 'b> ColorPainter for ColrCanvas<'a, 'b> { cx: c1.x, cy: c1.y, cr: r1, - stops: self.stops(color_stops), + stops, spread_method: extend.to_spread_method(), - transform: *self.transforms.last().unwrap(), + transform, }; Some(Fill { @@ -236,44 +333,53 @@ impl<'a, 'b> ColorPainter for ColrCanvas<'a, 'b> { color_stops, extend, } => { - if start_angle == end_angle - && (matches!( - extend, - skrifa::color::Extend::Reflect | skrifa::color::Extend::Repeat - )) - { - None - } else { - let sweep = SweepGradient { - cx: c0.x, - cy: c0.y, - start_angle, - end_angle, - stops: self.stops(color_stops), - spread_method: extend.to_spread_method(), - // COLR gradients run in the different direction - transform: *self.transforms.last().unwrap(), - }; - - Some(Fill { - paint: Paint::SweepGradient(sweep), - opacity: NormalizedF32::ONE, - rule: Default::default(), - }) - } + let stops = match self.stops(color_stops) { + Ok(s) => s, + Err(e) => { + self.error = Err(e); + return; + } + }; + + let Some(transform) = self.transforms.last().copied() else { + self.error = Err(KrillaError::GlyphDrawing( + "encountered imbalanced transforms".to_string(), + )); + return; + }; + + let sweep = SweepGradient { + cx: c0.x, + cy: c0.y, + start_angle, + end_angle, + stops, + spread_method: extend.to_spread_method(), + transform, + }; + + Some(Fill { + paint: Paint::SweepGradient(sweep), + opacity: NormalizedF32::ONE, + rule: Default::default(), + }) } } { // The proper implementation would be to apply all clip paths and then draw the // whole "visible" area with the fill. However, this seems to produce artifacts in // Google Chrome when zooming. So instead, what we do is that we apply all clip paths except // for the last one, and the last one we use to actually perform the fill. - let mut clips = self - .clips - .last() - .unwrap() - .iter() - .map(|p| (p.clone(), FillRule::NonZero)) - .collect::>(); + let Some(mut clips) = self.clips.last().map(|paths| { + paths + .iter() + .map(|p| (p.clone(), FillRule::NonZero)) + .collect::>() + }) else { + self.error = Err(KrillaError::GlyphDrawing( + "failed to apply fill glyph".to_string(), + )); + return; + }; let filled = clips.split_off(clips.len() - 1); diff --git a/src/font/mod.rs b/src/font/mod.rs index 9f1ddcba..47d88f0b 100644 --- a/src/font/mod.rs +++ b/src/font/mod.rs @@ -1,3 +1,4 @@ +use crate::error::KrillaResult; use crate::serialize::SvgSettings; use crate::surface::Surface; use crate::type3_font::Type3ID; @@ -266,7 +267,7 @@ impl Font { } } -#[derive(PartialEq, Eq, Copy, Clone)] +#[derive(PartialEq, Eq, Copy, Clone, Debug)] pub enum GlyphType { Colr, Svg, @@ -279,24 +280,24 @@ pub fn draw_glyph( svg_settings: SvgSettings, glyph: GlyphId, surface: &mut Surface, -) -> Option { +) -> KrillaResult> { let mut glyph_type = None; surface.push_transform(&Transform::from_scale(1.0, -1.0)); - if let Some(()) = colr::draw_glyph(font.clone(), glyph, surface) { + if let Some(()) = colr::draw_glyph(font.clone(), glyph, surface)? { glyph_type = Some(GlyphType::Colr); - } else if let Some(()) = svg::draw_glyph(font.clone(), svg_settings, glyph, surface) { + } else if let Some(()) = svg::draw_glyph(font.clone(), svg_settings, glyph, surface)? { glyph_type = Some(GlyphType::Svg); - } else if let Some(()) = bitmap::draw_glyph(font.clone(), glyph, surface) { + } else if let Some(()) = bitmap::draw_glyph(font.clone(), glyph, surface)? { glyph_type = Some(GlyphType::Bitmap); - } else if let Some(()) = outline::draw_glyph(font.clone(), glyph, surface) { + } else if let Some(()) = outline::draw_glyph(font.clone(), glyph, surface)? { glyph_type = Some(GlyphType::Outline); } surface.pop(); - glyph_type + Ok(glyph_type) } #[derive(Clone, Debug, Hash, Eq, PartialEq)] diff --git a/src/font/outline.rs b/src/font/outline.rs index 4ab7e8d1..21a311ee 100644 --- a/src/font/outline.rs +++ b/src/font/outline.rs @@ -1,3 +1,4 @@ +use crate::error::{KrillaError, KrillaResult}; use crate::font::{Font, OutlineBuilder}; use crate::object::color_space::luma::DeviceGray; use crate::surface::Surface; @@ -6,15 +7,22 @@ use skrifa::outline::DrawSettings; use skrifa::{GlyphId, MetadataProvider}; use tiny_skia_path::Transform; -pub fn draw_glyph(font: Font, glyph: GlyphId, surface: &mut Surface) -> Option<()> { +pub fn draw_glyph(font: Font, glyph: GlyphId, surface: &mut Surface) -> KrillaResult> { let outline_glyphs = font.font_ref().outline_glyphs(); let mut outline_builder = OutlineBuilder::new(); if let Some(outline_glyph) = outline_glyphs.get(glyph) { - let _ = outline_glyph.draw( + let drawn = outline_glyph.draw( DrawSettings::unhinted(skrifa::instance::Size::unscaled(), font.location_ref()), &mut outline_builder, ); + + if let Err(err) = drawn { + return Err(KrillaError::GlyphDrawing(format!( + "failed to draw outline glyph: {}", + err + ))); + } } if let Some(path) = outline_builder.finish() { @@ -22,8 +30,8 @@ pub fn draw_glyph(font: Font, glyph: GlyphId, surface: &mut Surface) -> Option<( surface.fill_path_impl(&path, Fill::::default(), true); surface.pop(); - return Some(()); + return Ok(Some(())); } - None + Ok(None) } diff --git a/src/font/svg.rs b/src/font/svg.rs index 5619f2c4..b376b0c1 100644 --- a/src/font/svg.rs +++ b/src/font/svg.rs @@ -1,3 +1,4 @@ +use crate::error::{KrillaError, KrillaResult}; use crate::font::Font; use crate::serialize::SvgSettings; use crate::surface::Surface; @@ -12,7 +13,7 @@ pub fn draw_glyph( svg_settings: SvgSettings, glyph: GlyphId, builder: &mut Surface, -) -> Option<()> { +) -> KrillaResult> { if let Ok(Some(svg_data)) = font .font_ref() .svg() @@ -23,16 +24,22 @@ pub fn draw_glyph( let mut decoded = vec![]; if data.starts_with(&[0x1f, 0x8b]) { let mut decoder = flate2::read::GzDecoder::new(data); - decoder.read_to_end(&mut decoded).unwrap(); + decoder.read_to_end(&mut decoded).map_err(|_| { + KrillaError::GlyphDrawing("failed to parse SVG for glyph".to_string()) + })?; data = &decoded; } - let xml = std::str::from_utf8(data).ok().unwrap(); - let document = roxmltree::Document::parse(xml).ok().unwrap(); + let xml = std::str::from_utf8(data) + .map_err(|_| KrillaError::GlyphDrawing("failed to parse SVG for glyph".to_string()))?; + let document = roxmltree::Document::parse(xml) + .map_err(|_| KrillaError::GlyphDrawing("failed to parse SVG for glyph".to_string()))?; // TODO: Add cache for SVG glyphs let opts = usvg::Options::default(); - let tree = usvg::Tree::from_xmltree(&document, &opts).unwrap(); + let tree = usvg::Tree::from_xmltree(&document, &opts).map_err(|_| { + KrillaError::GlyphDrawing("failed to convert SVG for glyph".to_string()) + })?; if let Some(node) = tree.node_by_id(&format!("glyph{}", glyph.to_u32())) { svg::render_node(&node, tree.fontdb().clone(), svg_settings, builder) } else { @@ -41,8 +48,8 @@ pub fn draw_glyph( svg::render_tree(&tree, svg_settings, builder) }; - return Some(()); + return Ok(Some(())); }; - None + Ok(None) } diff --git a/src/lib.rs b/src/lib.rs index 754685a3..db3d0b0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ pub mod svg; pub mod transform; pub mod util; +mod error; #[cfg(test)] pub mod tests; diff --git a/src/object/annotation.rs b/src/object/annotation.rs index 69ddcb74..05c08176 100644 --- a/src/object/annotation.rs +++ b/src/object/annotation.rs @@ -1,3 +1,4 @@ +use crate::error::KrillaResult; use crate::object::action::Action; use crate::object::destination::Destination; use crate::serialize::{Object, SerializerContext}; @@ -16,7 +17,7 @@ impl Annotation { sc: &mut SerializerContext, root_ref: Ref, page_size: f32, - ) -> Chunk { + ) -> KrillaResult { match self { Annotation::Link(link) => link.serialize_into(sc, root_ref, page_size), } @@ -40,13 +41,18 @@ impl Into for LinkAnnotation { } impl LinkAnnotation { - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref, page_size: f32) -> Chunk { + fn serialize_into( + &self, + sc: &mut SerializerContext, + root_ref: Ref, + page_size: f32, + ) -> KrillaResult { let mut chunk = Chunk::new(); let target_ref = sc.new_ref(); match &self.target { - Target::Destination(dest) => chunk.extend(&dest.serialize_into(sc, target_ref)), + Target::Destination(dest) => chunk.extend(&dest.serialize_into(sc, target_ref)?), Target::Action(_) => {} }; @@ -69,7 +75,7 @@ impl LinkAnnotation { annotation.finish(); - chunk + Ok(chunk) } } diff --git a/src/object/cid_font.rs b/src/object/cid_font.rs index b652e5ce..2fa9bb47 100644 --- a/src/object/cid_font.rs +++ b/src/object/cid_font.rs @@ -1,3 +1,4 @@ +use crate::error::{KrillaError, KrillaResult}; use crate::font::{CIDIdentifer, Font, FontIdentifier}; use crate::serialize::{FilterStream, SerializerContext, SipHashable}; use crate::util::{RectExt, SliceExt}; @@ -92,7 +93,11 @@ impl CIDFont { FontIdentifier::Cid(CIDIdentifer(self.font.clone())) } - pub(crate) fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + pub(crate) fn serialize_into( + &self, + sc: &mut SerializerContext, + root_ref: Ref, + ) -> KrillaResult { let mut chunk = Chunk::new(); let cid_ref = sc.new_ref(); @@ -100,6 +105,7 @@ impl CIDFont { let cmap_ref = sc.new_ref(); let data_ref = sc.new_ref(); + let postscript_name = self.font.postscript_name().unwrap_or("unknown"); let glyph_remapper = &self.glyph_remapper; let is_cff = self.font.font_ref().cff().is_ok(); @@ -111,14 +117,16 @@ impl CIDFont { self.font.index(), glyph_remapper, ) - .unwrap() - }; + .map_err(|_| KrillaError::Font(format!("failed to subset font {}", postscript_name))) + }?; let font_stream = { let mut data = subsetted.as_slice(); // If we have a CFF font, only embed the standalone CFF program. - let subsetted_ref = skrifa::FontRef::new(data).unwrap(); + let subsetted_ref = skrifa::FontRef::new(data).map_err(|_| { + KrillaError::Font(format!("failed to read font subset of {}", postscript_name)) + })?; if let Some(cff) = subsetted_ref.data_for_tag(Cff::TAG) { data = cff.as_bytes(); } @@ -126,7 +134,6 @@ impl CIDFont { FilterStream::new_from_binary_data(data, &sc.serialize_settings) }; - let postscript_name = self.font.postscript_name().unwrap_or("unknown"); let subset_tag = subset_tag(font_stream.encoded_data()); let base_font = format!("{subset_tag}+{postscript_name}"); @@ -227,7 +234,7 @@ impl CIDFont { stream.finish(); - chunk + Ok(chunk) } } diff --git a/src/object/color_space.rs b/src/object/color_space.rs index 4d1bb254..faf64255 100644 --- a/src/object/color_space.rs +++ b/src/object/color_space.rs @@ -1,5 +1,6 @@ use crate::color_space::device_cmyk::DeviceCmyk; use crate::color_space::luma::{DeviceGray, SGray}; +use crate::error::KrillaResult; use crate::rgb::{DeviceRgb, Srgb}; use crate::serialize::{FilterStream, SerializerContext}; use pdf_writer::{Chunk, Finish, Name, Ref}; @@ -33,7 +34,7 @@ pub trait ColorSpace: Debug + Hash + Eq + PartialEq + Clone + Copy { struct ICCBasedColorSpace(Arc>, u8); impl ICCBasedColorSpace { - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { let icc_ref = sc.new_ref(); let mut chunk = Chunk::new(); @@ -54,7 +55,7 @@ impl ICCBasedColorSpace { icc_stream.write_filters(icc_profile.deref_mut().deref_mut()); icc_profile.finish(); - chunk + Ok(chunk) } } @@ -147,6 +148,7 @@ pub mod rgb { use std::sync::Arc; use crate::chunk_container::ChunkContainer; + use crate::error::KrillaResult; use pdf_writer::{Chunk, Ref}; /// An RGB color. @@ -222,7 +224,7 @@ pub mod rgb { &mut cc.color_spaces } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { let icc_based = ICCBasedColorSpace(Arc::new(SRGB_ICC), 3); icc_based.serialize_into(sc, root_ref) } @@ -240,6 +242,7 @@ pub mod rgb { /// A module for dealing with device luma (= grayscale) colors. pub mod luma { use crate::chunk_container::ChunkContainer; + use crate::error::KrillaResult; use crate::object::color_space::{ ColorSpace, ColorSpaceType, ICCBasedColorSpace, InternalColor, }; @@ -313,7 +316,7 @@ pub mod luma { &mut cc.color_spaces } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { let icc_based = ICCBasedColorSpace(Arc::new(GREY_ICC), 1); icc_based.serialize_into(sc, root_ref) } @@ -348,11 +351,11 @@ mod tests { #[snapshot] fn color_space_sgray(sc: &mut SerializerContext) { - sc.add_object(ColorSpaceResource::SGray(SGray)); + sc.add_object(ColorSpaceResource::SGray(SGray)).unwrap(); } #[snapshot] fn color_space_srgb(sc: &mut SerializerContext) { - sc.add_object(ColorSpaceResource::Srgb(Srgb)); + sc.add_object(ColorSpaceResource::Srgb(Srgb)).unwrap(); } } diff --git a/src/object/destination.rs b/src/object/destination.rs index a29d517f..ab6469db 100644 --- a/src/object/destination.rs +++ b/src/object/destination.rs @@ -1,4 +1,5 @@ use crate::chunk_container::ChunkContainer; +use crate::error::KrillaResult; use crate::serialize::{Object, SerializerContext}; use pdf_writer::{Chunk, Ref}; use std::hash::{Hash, Hasher}; @@ -14,7 +15,7 @@ impl Object for Destination { &mut cc.destinations } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { match self { Destination::Xyz(xyz) => xyz.serialize_into(sc, root_ref), } @@ -52,7 +53,7 @@ impl Object for XyzDestination { &mut cc.destinations } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { let page_ref = sc.page_infos()[self.page_index].ref_; let page_size = sc.page_infos()[self.page_index].media_box.height(); @@ -68,6 +69,6 @@ impl Object for XyzDestination { .page(page_ref) .xyz(mapped_point.x, mapped_point.y, None); - chunk + Ok(chunk) } } diff --git a/src/object/ext_g_state.rs b/src/object/ext_g_state.rs index 5ef8abce..ccd7760b 100644 --- a/src/object/ext_g_state.rs +++ b/src/object/ext_g_state.rs @@ -1,4 +1,5 @@ use crate::chunk_container::ChunkContainer; +use crate::error::KrillaResult; use crate::object::mask::Mask; use crate::serialize::{Object, SerializerContext}; use pdf_writer::types::BlendMode; @@ -101,14 +102,14 @@ impl Object for ExtGState { &mut cc.ext_g_states } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { let mut chunk = Chunk::new(); - let mask_ref = self - .0 - .mask - .clone() - .map(|ma| sc.add_object(Arc::unwrap_or_clone(ma))); + let mask_ref = if let Some(mask) = self.0.mask.clone() { + Some(sc.add_object(Arc::unwrap_or_clone(mask))?) + } else { + None + }; let mut ext_st = chunk.ext_graphics(root_ref); if let Some(nsa) = self.0.non_stroking_alpha { @@ -129,7 +130,7 @@ impl Object for ExtGState { ext_st.finish(); - chunk + Ok(chunk) } } @@ -148,7 +149,7 @@ mod tests { #[snapshot] pub fn ext_g_state_empty(sc: &mut SerializerContext) { let ext_state = ExtGState::new(); - sc.add_object(ext_state); + sc.add_object(ext_state).unwrap(); } #[snapshot] @@ -157,7 +158,7 @@ mod tests { .non_stroking_alpha(NormalizedF32::ONE) .stroking_alpha(NormalizedF32::ONE) .blend_mode(BlendMode::Normal); - sc.add_object(ext_state); + sc.add_object(ext_state).unwrap(); } #[snapshot] @@ -168,6 +169,6 @@ mod tests { .stroking_alpha(NormalizedF32::new(0.6).unwrap()) .blend_mode(BlendMode::Difference) .mask(mask); - sc.add_object(ext_state); + sc.add_object(ext_state).unwrap(); } } diff --git a/src/object/image.rs b/src/object/image.rs index 77d8c217..a1468799 100644 --- a/src/object/image.rs +++ b/src/object/image.rs @@ -1,4 +1,5 @@ use crate::chunk_container::ChunkContainer; +use crate::error::KrillaResult; use crate::object::color_space::DEVICE_GRAY; use crate::serialize::{FilterStream, Object, SerializerContext}; use crate::util::{NameExt, Prehashed, SizeWrapper}; @@ -46,7 +47,7 @@ impl Object for Image { &mut cc.images } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { let mut chunk = Chunk::new(); let alpha_mask = self.0.mask_data.as_ref().map(|mask_data| { @@ -85,7 +86,7 @@ impl Object for Image { } image_x_object.finish(); - chunk + Ok(chunk) } } diff --git a/src/object/mask.rs b/src/object/mask.rs index 4801fc43..eefc57dd 100644 --- a/src/object/mask.rs +++ b/src/object/mask.rs @@ -1,4 +1,5 @@ use crate::chunk_container::ChunkContainer; +use crate::error::KrillaResult; use crate::object::shading_function::{GradientProperties, ShadingFunction}; use crate::object::xobject::XObject; use crate::serialize::{Object, SerializerContext}; @@ -97,7 +98,7 @@ impl Object for Mask { &mut cc.masks } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { let mut chunk = Chunk::new(); let x_ref = sc.add_object(XObject::new( @@ -105,7 +106,7 @@ impl Object for Mask { false, true, self.custom_bbox.map(|c| c.0), - )); + ))?; let mut dict = chunk.indirect(root_ref).dict(); dict.pair(Name(b"Type"), Name(b"Mask")); @@ -114,7 +115,7 @@ impl Object for Mask { dict.finish(); - chunk + Ok(chunk) } } @@ -148,7 +149,7 @@ mod tests { ); surface.finish(); let mask = Mask::new(stream_builder.finish(), mask_type); - sc.add_object(mask); + sc.add_object(mask).unwrap(); } #[snapshot] diff --git a/src/object/outline.rs b/src/object/outline.rs index 2fad9814..c078c367 100644 --- a/src/object/outline.rs +++ b/src/object/outline.rs @@ -1,3 +1,4 @@ +use crate::error::KrillaResult; use crate::object::destination::XyzDestination; use crate::serialize::{Object, SerializerContext}; use pdf_writer::{Chunk, Finish, Name, Ref, TextStr}; @@ -17,7 +18,11 @@ impl Outline { self.children.push(node) } - pub(crate) fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + pub(crate) fn serialize_into( + &self, + sc: &mut SerializerContext, + root_ref: Ref, + ) -> KrillaResult { let mut chunk = Chunk::new(); let mut sub_chunks = vec![]; @@ -40,7 +45,7 @@ impl Outline { last = cur.unwrap(); - sub_chunks.push(self.children[i].serialize_into(sc, root_ref, last, next, prev)); + sub_chunks.push(self.children[i].serialize_into(sc, root_ref, last, next, prev)?); prev = cur; cur = next; @@ -57,7 +62,7 @@ impl Outline { chunk.extend(&sub_chunk); } - chunk + Ok(chunk) } } @@ -90,7 +95,7 @@ impl OutlineNode { root: Ref, next: Option, prev: Option, - ) -> Chunk { + ) -> KrillaResult { let mut chunk = Chunk::new(); let mut sub_chunks = vec![]; @@ -122,7 +127,7 @@ impl OutlineNode { last = cur.unwrap(); - sub_chunks.push(self.children[i].serialize_into(sc, root, last, next, prev)); + sub_chunks.push(self.children[i].serialize_into(sc, root, last, next, prev)?); prev = cur; cur = next; @@ -139,7 +144,7 @@ impl OutlineNode { let dest = XyzDestination::new(self.page_index as usize, self.pos); let dest_ref = sc.new_ref(); - sub_chunks.push(dest.serialize_into(sc, dest_ref)); + sub_chunks.push(dest.serialize_into(sc, dest_ref)?); outline_entry.pair(Name(b"Dest"), dest_ref); @@ -149,7 +154,7 @@ impl OutlineNode { chunk.extend(&sub_chunk); } - chunk + Ok(chunk) } } diff --git a/src/object/page.rs b/src/object/page.rs index 85cef523..324a4979 100644 --- a/src/object/page.rs +++ b/src/object/page.rs @@ -1,3 +1,4 @@ +use crate::error::KrillaResult; use crate::object::annotation::Annotation; use crate::serialize::{FilterStream, SerializerContext}; use crate::stream::Stream; @@ -36,7 +37,11 @@ impl Page { } } - pub(crate) fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + pub(crate) fn serialize_into( + &self, + sc: &mut SerializerContext, + root_ref: Ref, + ) -> KrillaResult { let stream_ref = sc.new_ref(); let mut chunk = Chunk::new(); @@ -46,7 +51,7 @@ impl Page { if !self.annotations.is_empty() { for annotation in &self.annotations { let annot_ref = sc.new_ref(); - chunk.extend(&annotation.serialize_into(sc, annot_ref, self.media_box.height())); + chunk.extend(&annotation.serialize_into(sc, annot_ref, self.media_box.height())?); annotation_refs.push(annot_ref); } } @@ -54,7 +59,7 @@ impl Page { let mut page = chunk.page(root_ref); self.stream .resource_dictionary() - .to_pdf_resources(sc, &mut page); + .to_pdf_resources(sc, &mut page)?; page.media_box(self.media_box.to_pdf_rect()); page.parent(sc.page_tree_ref()); @@ -74,7 +79,7 @@ impl Page { stream.finish(); - chunk + Ok(chunk) } } @@ -139,7 +144,7 @@ impl<'a> PageLabelContainer<'a> { }; } - pub fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + pub fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { // Will always contain at least one entry, since we ensured that a PageLabelContainer cannot // be empty let mut filtered_entries = vec![]; @@ -172,7 +177,7 @@ impl<'a> PageLabelContainer<'a> { nums.finish(); num_tree.finish(); - chunk + Ok(chunk) } } diff --git a/src/object/shading_function.rs b/src/object/shading_function.rs index 9ca00d89..7a72f80f 100644 --- a/src/object/shading_function.rs +++ b/src/object/shading_function.rs @@ -1,4 +1,5 @@ use crate::chunk_container::ChunkContainer; +use crate::error::KrillaResult; use crate::object::color_space::{Color, ColorSpace}; use crate::paint::SpreadMethod; use crate::serialize::{Object, SerializerContext}; @@ -201,7 +202,7 @@ impl Object for ShadingFunction { &mut cc.shading_functions } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { let mut chunk = Chunk::new(); match &self.0.properties { @@ -213,7 +214,7 @@ impl Object for ShadingFunction { } } - chunk + Ok(chunk) } } diff --git a/src/object/shading_pattern.rs b/src/object/shading_pattern.rs index b84069b1..1d8f1ae9 100644 --- a/src/object/shading_pattern.rs +++ b/src/object/shading_pattern.rs @@ -1,4 +1,5 @@ use crate::chunk_container::ChunkContainer; +use crate::error::KrillaResult; use crate::object::shading_function::{GradientProperties, ShadingFunction}; use crate::serialize::{Object, SerializerContext}; use crate::transform::TransformWrapper; @@ -33,16 +34,16 @@ impl Object for ShadingPattern { &mut cc.patterns } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { let mut chunk = Chunk::new(); - let shading_ref = sc.add_object(self.0.shading_function.clone()); + let shading_ref = sc.add_object(self.0.shading_function.clone())?; let mut shading_pattern = chunk.shading_pattern(root_ref); shading_pattern.pair(Name(b"Shading"), shading_ref); shading_pattern.matrix(self.0.shading_transform.0.to_pdf_transform()); shading_pattern.finish(); - chunk + Ok(chunk) } } diff --git a/src/object/tiling_pattern.rs b/src/object/tiling_pattern.rs index aaecc293..da35975f 100644 --- a/src/object/tiling_pattern.rs +++ b/src/object/tiling_pattern.rs @@ -1,4 +1,5 @@ use crate::chunk_container::ChunkContainer; +use crate::error::KrillaResult; use crate::serialize::{FilterStream, Object, SerializerContext}; use crate::stream::Stream; use crate::surface::StreamBuilder; @@ -59,7 +60,7 @@ impl Object for TilingPattern { &mut cc.patterns } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { let mut chunk = Chunk::new(); let pattern_stream = @@ -69,7 +70,7 @@ impl Object for TilingPattern { self.stream .resource_dictionary() - .to_pdf_resources(sc, &mut tiling_pattern); + .to_pdf_resources(sc, &mut tiling_pattern)?; let final_bbox = pdf_writer::Rect::new(0.0, 0.0, self.width.get(), self.height.get()); @@ -83,6 +84,6 @@ impl Object for TilingPattern { tiling_pattern.finish(); - chunk + Ok(chunk) } } diff --git a/src/object/type3_font.rs b/src/object/type3_font.rs index 6fb1b11b..0ffb1bde 100644 --- a/src/object/type3_font.rs +++ b/src/object/type3_font.rs @@ -1,3 +1,4 @@ +use crate::error::KrillaResult; use crate::font; use crate::font::{Font, FontIdentifier, GlyphType, Type3Identifier}; use crate::object::xobject::XObject; @@ -97,9 +98,14 @@ impl Type3Font { FontIdentifier::Type3(Type3Identifier(self.font.clone(), self.index)) } - pub(crate) fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + pub(crate) fn serialize_into( + &self, + sc: &mut SerializerContext, + root_ref: Ref, + ) -> KrillaResult { let mut chunk = Chunk::new(); let svg_settings = sc.serialize_settings.svg_settings; + let ignore_invalid_glyphs = sc.serialize_settings.ignore_invalid_glyphs; let mut rd_builder = ResourceDictionaryBuilder::new(); let mut bbox = Rect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(); @@ -111,14 +117,29 @@ impl Type3Font { .map(|(index, glyph_id)| { let mut stream_surface = StreamBuilder::new(sc); let mut surface = stream_surface.surface(); - let glyph_type = - font::draw_glyph(self.font.clone(), svg_settings, *glyph_id, &mut surface); + + let glyph_type = match font::draw_glyph( + self.font.clone(), + svg_settings, + *glyph_id, + &mut surface, + ) { + Ok(g) => g, + Err(e) => { + if ignore_invalid_glyphs { + surface.reset(); + None + } else { + return Err(e); + } + } + }; + surface.finish(); let stream = stream_surface.finish(); - let mut content = Content::new(); - let stream = if glyph_type == Some(GlyphType::Outline) { + let stream = if glyph_type == Some(GlyphType::Outline) || stream.is_empty() { let bbox = stream.bbox(); content.start_shape_glyph( self.widths[index], @@ -154,9 +175,9 @@ impl Type3Font { let mut stream = chunk.stream(stream_ref, &font_stream.encoded_data()); font_stream.write_filters(stream.deref_mut()); - stream_ref + Ok(stream_ref) }) - .collect::>(); + .collect::>>()?; let resource_dictionary = rd_builder.finish(); @@ -194,7 +215,7 @@ impl Type3Font { font_descriptor.finish(); let mut type3_font = chunk.type3_font(root_ref); - resource_dictionary.to_pdf_resources(sc, &mut type3_font); + resource_dictionary.to_pdf_resources(sc, &mut type3_font)?; type3_font.bbox(bbox.to_pdf_rect()); type3_font.to_unicode(cmap_ref); @@ -239,7 +260,7 @@ impl Type3Font { }; chunk.cmap(cmap_ref, &cmap.finish()); - chunk + Ok(chunk) } } diff --git a/src/object/xobject.rs b/src/object/xobject.rs index 03a90b25..ba21ad46 100644 --- a/src/object/xobject.rs +++ b/src/object/xobject.rs @@ -1,4 +1,5 @@ use crate::chunk_container::ChunkContainer; +use crate::error::KrillaResult; use crate::serialize::{FilterStream, Object, SerializerContext}; use crate::stream::Stream; use crate::util::{RectExt, RectWrapper}; @@ -39,7 +40,7 @@ impl Object for XObject { &mut cc.x_objects } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { let cs = sc.rgb(); let mut chunk = Chunk::new(); @@ -50,7 +51,7 @@ impl Object for XObject { self.stream .resource_dictionary() - .to_pdf_resources(sc, &mut x_object); + .to_pdf_resources(sc, &mut x_object)?; x_object.bbox( self.custom_bbox .map(|c| c.0) @@ -76,6 +77,6 @@ impl Object for XObject { x_object.finish(); - chunk + Ok(chunk) } } diff --git a/src/resource.rs b/src/resource.rs index 6121cc4a..aea2824b 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,4 +1,5 @@ use crate::chunk_container::ChunkContainer; +use crate::error::KrillaResult; use crate::font::FontIdentifier; use crate::object::color_space::luma::SGray; use crate::object::color_space::rgb::Srgb; @@ -121,7 +122,7 @@ impl Object for XObjectResource { } } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { match self { XObjectResource::XObject(x) => x.serialize_into(sc, root_ref), XObjectResource::Image(i) => i.serialize_into(sc, root_ref), @@ -150,7 +151,7 @@ impl Object for PatternResource { &mut cc.patterns } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { match self { PatternResource::ShadingPattern(sp) => sp.serialize_into(sc, root_ref), PatternResource::TilingPattern(tp) => tp.serialize_into(sc, root_ref), @@ -238,7 +239,11 @@ pub(crate) struct ResourceDictionary { } impl ResourceDictionary { - pub fn to_pdf_resources(&self, sc: &mut SerializerContext, parent: &mut T) + pub fn to_pdf_resources( + &self, + sc: &mut SerializerContext, + parent: &mut T, + ) -> KrillaResult<()> where T: ResourcesExt, { @@ -249,12 +254,14 @@ impl ResourceDictionary { ProcSet::ImageColor, ProcSet::ImageGrayscale, ]); - write_resource_type(sc, resources, &self.color_spaces); - write_resource_type(sc, resources, &self.ext_g_states); - write_resource_type(sc, resources, &self.patterns); - write_resource_type(sc, resources, &self.x_objects); - write_resource_type(sc, resources, &self.shadings); - write_resource_type(sc, resources, &self.fonts); + write_resource_type(sc, resources, &self.color_spaces)?; + write_resource_type(sc, resources, &self.ext_g_states)?; + write_resource_type(sc, resources, &self.patterns)?; + write_resource_type(sc, resources, &self.x_objects)?; + write_resource_type(sc, resources, &self.shadings)?; + write_resource_type(sc, resources, &self.fonts)?; + + Ok(()) } } @@ -262,18 +269,21 @@ fn write_resource_type( sc: &mut SerializerContext, resources: &mut Resources, resource_list: &ResourceList, -) where +) -> KrillaResult<()> +where T: Hash + Eq + ResourceTrait + Into + Debug + Clone, { if resource_list.len() > 0 { let mut dict = T::get_dict(resources); for (name, entry) in resource_list.get_entries() { - dict.pair(name.to_pdf_name(), sc.add_resource(entry)); + dict.pair(name.to_pdf_name(), sc.add_resource(entry)?); } dict.finish(); } + + Ok(()) } #[derive(Debug, Eq, PartialEq, Hash, Clone)] @@ -371,7 +381,7 @@ impl Object for ColorSpaceResource { &mut cc.color_spaces } - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk { + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult { match self { ColorSpaceResource::Srgb(srgb) => srgb.serialize_into(sc, root_ref), ColorSpaceResource::SGray(sgray) => sgray.serialize_into(sc, root_ref), diff --git a/src/serialize.rs b/src/serialize.rs index 066232fd..e35e7573 100644 --- a/src/serialize.rs +++ b/src/serialize.rs @@ -1,4 +1,5 @@ use crate::chunk_container::ChunkContainer; +use crate::error::KrillaResult; use crate::font::{Font, FontIdentifier, FontInfo}; use crate::object::cid_font::CIDFont; use crate::object::color_space::luma::SGray; @@ -46,8 +47,9 @@ pub struct SerializeSettings { pub ascii_compatible: bool, pub compress_content_streams: bool, pub no_device_cs: bool, - pub force_type3_fonts: bool, pub svg_settings: SvgSettings, + pub(crate) force_type3_fonts: bool, + pub(crate) ignore_invalid_glyphs: bool, } #[cfg(test)] @@ -59,6 +61,7 @@ impl SerializeSettings { no_device_cs: false, force_type3_fonts: false, svg_settings: SvgSettings::default(), + ignore_invalid_glyphs: false, } } @@ -68,6 +71,13 @@ impl SerializeSettings { ..Self::settings_1() } } + + pub fn settings_3() -> Self { + Self { + ignore_invalid_glyphs: true, + ..Self::settings_1() + } + } } impl Default for SerializeSettings { @@ -77,6 +87,7 @@ impl Default for SerializeSettings { compress_content_streams: true, no_device_cs: false, force_type3_fonts: false, + ignore_invalid_glyphs: false, svg_settings: SvgSettings::default(), } } @@ -85,9 +96,9 @@ impl Default for SerializeSettings { pub trait Object: SipHashable { fn chunk_container<'a>(&self, cc: &'a mut ChunkContainer) -> &'a mut Vec; - fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk; + fn serialize_into(&self, sc: &mut SerializerContext, root_ref: Ref) -> KrillaResult; - fn serialize(&self, sc: &mut SerializerContext) -> Chunk { + fn serialize(&self, sc: &mut SerializerContext) -> KrillaResult { let root_ref = sc.new_ref(); self.serialize_into(sc, root_ref) } @@ -186,7 +197,7 @@ impl SerializerContext { pub fn rgb(&mut self) -> CSWrapper { if self.serialize_settings.no_device_cs { - CSWrapper::Ref(self.add_object(ColorSpaceResource::Srgb(Srgb))) + CSWrapper::Ref(self.add_object(ColorSpaceResource::Srgb(Srgb)).unwrap()) } else { CSWrapper::Name(DEVICE_RGB.to_pdf_name()) } @@ -194,27 +205,27 @@ impl SerializerContext { pub fn gray(&mut self) -> CSWrapper { if self.serialize_settings.no_device_cs { - CSWrapper::Ref(self.add_object(ColorSpaceResource::SGray(SGray))) + CSWrapper::Ref(self.add_object(ColorSpaceResource::SGray(SGray)).unwrap()) } else { CSWrapper::Name(DEVICE_GRAY.to_pdf_name()) } } - pub fn add_object(&mut self, object: T) -> Ref + pub fn add_object(&mut self, object: T) -> KrillaResult where T: Object, { let hash = object.sip_hash(); if let Some(_ref) = self.cached_mappings.get(&hash) { - *_ref + Ok(*_ref) } else { let root_ref = self.new_ref(); - let chunk = object.serialize_into(self, root_ref); + let chunk = object.serialize_into(self, root_ref)?; self.cached_mappings.insert(hash, root_ref); object .chunk_container(&mut self.chunk_container) .push(chunk); - root_ref + Ok(root_ref) } } @@ -254,7 +265,7 @@ impl SerializerContext { }) } - pub(crate) fn add_resource(&mut self, resource: impl Into) -> Ref { + pub(crate) fn add_resource(&mut self, resource: impl Into) -> KrillaResult { match resource.into() { Resource::XObject(xr) => self.add_object(xr), Resource::Pattern(pr) => self.add_object(pr), @@ -264,11 +275,11 @@ impl SerializerContext { Resource::Font(f) => { let hash = f.sip_hash(); if let Some(_ref) = self.cached_mappings.get(&hash) { - *_ref + Ok(*_ref) } else { let root_ref = self.new_ref(); self.cached_mappings.insert(hash, root_ref); - root_ref + Ok(root_ref) } } } @@ -306,7 +317,7 @@ impl SerializerContext { } // Always needs to be called. - pub fn finish(mut self) -> Pdf { + pub fn finish(mut self) -> KrillaResult { // TODO: Get rid of all the clones if let Some(container) = PageLabelContainer::new( @@ -317,14 +328,14 @@ impl SerializerContext { .collect::>(), ) { let page_label_tree_ref = self.new_ref(); - let chunk = container.serialize_into(&mut self, page_label_tree_ref); + let chunk = container.serialize_into(&mut self, page_label_tree_ref)?; self.chunk_container.page_label_tree = Some((page_label_tree_ref, chunk)); } let outline = std::mem::take(&mut self.outline); if let Some(outline) = &outline { let outline_ref = self.new_ref(); - let chunk = outline.serialize_into(&mut self, outline_ref); + let chunk = outline.serialize_into(&mut self, outline_ref)?; self.chunk_container.outline = Some((outline_ref, chunk)); } @@ -333,14 +344,14 @@ impl SerializerContext { match &*font_container.borrow() { FontContainer::Type3(font_mapper) => { for t3_font in font_mapper.fonts() { - let ref_ = self.add_resource(t3_font.identifier()); - let chunk = t3_font.serialize_into(&mut self, ref_); + let ref_ = self.add_resource(t3_font.identifier())?; + let chunk = t3_font.serialize_into(&mut self, ref_)?; self.chunk_container.fonts.push(chunk); } } FontContainer::CIDFont(cid_font) => { - let ref_ = self.add_resource(cid_font.identifier()); - let chunk = cid_font.serialize_into(&mut self, ref_); + let ref_ = self.add_resource(cid_font.identifier())?; + let chunk = cid_font.serialize_into(&mut self, ref_)?; self.chunk_container.fonts.push(chunk); } } @@ -348,7 +359,7 @@ impl SerializerContext { let pages = std::mem::take(&mut self.pages); for (ref_, page) in &pages { - let chunk = page.serialize_into(&mut self, *ref_); + let chunk = page.serialize_into(&mut self, *ref_)?; self.chunk_container.pages.push(chunk); } @@ -365,7 +376,7 @@ impl SerializerContext { assert!(self.font_map.is_empty()); assert!(self.pages.is_empty()); - self.chunk_container.finish(self.serialize_settings) + Ok(self.chunk_container.finish(self.serialize_settings)) } } diff --git a/src/stream.rs b/src/stream.rs index db81b357..fd454dc4 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -61,6 +61,10 @@ impl Stream { self.0.bbox.0 } + pub fn is_empty(&self) -> bool { + self.0.content.is_empty() + } + pub(crate) fn resource_dictionary(&self) -> &ResourceDictionary { &self.0.resource_dictionary } diff --git a/src/surface.rs b/src/surface.rs index 2b56bfa8..2fb5684f 100644 --- a/src/surface.rs +++ b/src/surface.rs @@ -140,6 +140,12 @@ impl<'a> Surface<'a> { self.sub_builders.push(ContentBuilder::new()); } + pub fn reset(&mut self) { + self.push_instructions = vec![]; + self.sub_builders = vec![]; + self.root_builder = ContentBuilder::new(); + } + pub fn push_opacified(&mut self, opacity: NormalizedF32) { self.push_instructions .push(PushInstruction::Opacity(opacity)); @@ -228,8 +234,8 @@ impl Drop for Surface<'_> { fn drop(&mut self) { // Replace with a dummy builder. let root_builder = std::mem::replace(&mut self.root_builder, ContentBuilder::new()); - assert!(self.sub_builders.is_empty()); - assert!(self.push_instructions.is_empty()); + debug_assert!(self.sub_builders.is_empty()); + debug_assert!(self.push_instructions.is_empty()); (self.finish_fn)(root_builder.finish()) } } diff --git a/src/tests/manual.rs b/src/tests/manual.rs index 5261f7d5..e0bff7b3 100644 --- a/src/tests/manual.rs +++ b/src/tests/manual.rs @@ -78,7 +78,7 @@ fn simple_shape_demo() { surface.finish(); builder.finish(); - let pdf = document_builder.finish(); + let pdf = document_builder.finish().unwrap(); write_manual_to_store("simple_shape", &pdf); } @@ -141,7 +141,7 @@ fn cosmic_text_integration() { surface.finish(); builder.finish(); - let pdf = document_builder.finish(); + let pdf = document_builder.finish().unwrap(); write_manual_to_store("cosmic_text", &pdf); } @@ -151,7 +151,7 @@ fn twitter_color_emoji() { let font_data = std::fs::read("/Library/Fonts/TwitterColorEmoji-SVGinOT.ttf").unwrap(); let mut document = Document::new(SerializeSettings::settings_1()); all_glyphs_to_pdf(Arc::new(font_data), None, &mut document); - write_manual_to_store("twitter_color_emoji", &document.finish()); + write_manual_to_store("twitter_color_emoji", &document.finish().unwrap()); } #[ignore] @@ -165,7 +165,7 @@ fn colr_test_glyphs() { let mut document = Document::new(SerializeSettings::settings_1()); all_glyphs_to_pdf(font_data, Some(glyphs), &mut document); - write_manual_to_store("colr_test_glyphs", &document.finish()); + write_manual_to_store("colr_test_glyphs", &document.finish().unwrap()); } #[ignore] @@ -179,7 +179,7 @@ fn noto_sans() { let mut document = Document::new(SerializeSettings::settings_1()); all_glyphs_to_pdf(font_data, Some(glyphs), &mut document); - write_manual_to_store("noto_sans", &document.finish()); + write_manual_to_store("noto_sans", &document.finish().unwrap()); } #[ignore] @@ -189,7 +189,7 @@ fn apple_color_emoji() { let mut document = Document::new(SerializeSettings::settings_1()); all_glyphs_to_pdf(Arc::new(font_data), None, &mut document); - write_manual_to_store("apple_color_emoji", &document.finish()); + write_manual_to_store("apple_color_emoji", &document.finish().unwrap()); } #[ignore] @@ -198,7 +198,7 @@ fn noto_color_emoji() { let font_data = std::fs::read("/Library/Fonts/NotoColorEmoji-Regular.ttf").unwrap(); let mut document = Document::new(SerializeSettings::settings_1()); all_glyphs_to_pdf(Arc::new(font_data), None, &mut document); - write_manual_to_store("noto_color_emoji", &document.finish()); + write_manual_to_store("noto_color_emoji", &document.finish().unwrap()); } #[ignore] @@ -207,7 +207,7 @@ fn segoe_ui_emoji() { let font_data = std::fs::read("/Library/Fonts/seguiemj.ttf").unwrap(); let mut document = Document::new(SerializeSettings::settings_1()); all_glyphs_to_pdf(Arc::new(font_data), None, &mut document); - write_manual_to_store("segoe_ui_emoji", &document.finish()); + write_manual_to_store("segoe_ui_emoji", &document.finish().unwrap()); } pub fn all_glyphs_to_pdf( diff --git a/src/tests/mod.rs b/src/tests/mod.rs index cbd47a17..2e073ceb 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -20,6 +20,8 @@ mod visreg; const REPLACE: Option<&str> = option_env!("REPLACE"); const STORE: Option<&str> = option_env!("STORE"); +pub const SKIP_VISREG: Option<&str> = option_env!("SKIP_VISREG"); +pub const SKIP_SNAPSHOT: Option<&str> = option_env!("SKIP_SNAPSHOT"); static ASSETS_PATH: LazyLock = LazyLock::new(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("assets")); diff --git a/src/tests/visreg.rs b/src/tests/visreg.rs index 5f03980d..793edcc4 100644 --- a/src/tests/visreg.rs +++ b/src/tests/visreg.rs @@ -10,6 +10,7 @@ use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping}; use fontdb::{Database, Source}; use krilla_macros::visreg; use skrifa::GlyphId; +use std::sync::Arc; use tiny_skia_path::{NormalizedF32, PathBuilder, Rect, Transform}; #[visreg(all)] @@ -108,7 +109,7 @@ fn cosmic_text(surface: &mut Surface) { } } -#[visreg(document)] +#[visreg(document, settings_3)] fn colr_test_glyphs(document: &mut Document) { let font_data = COLR_TEST_GLYPHS.clone(); @@ -125,6 +126,13 @@ fn noto_color_emoji(document: &mut Document) { all_glyphs_to_pdf(font_data, None, document); } +#[visreg(document)] +#[cfg(target_os = "macos")] +fn apple_color_emoji(document: &mut Document) { + let font_data = Arc::new(std::fs::read("/Library/Fonts/Apple Color Emoji.ttc").unwrap()); + all_glyphs_to_pdf(font_data, None, document); +} + #[visreg(document)] fn twitter_color_emoji(document: &mut Document) { let font_data = TWITTER_COLOR_EMOJI.clone();