diff options
| author | Martin Haug <mhaug@live.de> | 2023-05-02 13:53:20 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-05-02 13:53:20 +0200 |
| commit | 17cef8dcee022ca89ccef62eaec23ff6c0e4cdf9 (patch) | |
| tree | 253ea22f94ac90f0df91d6e20758ecc9d7271ce8 /src/export | |
| parent | 021694de239ca5d6d799dde98423036bb1da405e (diff) | |
Add ICC profiles to images in PDF and update `usvg`, `svg2pdf` (#822)
Diffstat (limited to 'src/export')
| -rw-r--r-- | src/export/pdf/image.rs | 22 | ||||
| -rw-r--r-- | src/export/pdf/outline.rs | 2 | ||||
| -rw-r--r-- | src/export/pdf/page.rs | 4 | ||||
| -rw-r--r-- | src/export/render.rs | 72 |
4 files changed, 67 insertions, 33 deletions
diff --git a/src/export/pdf/image.rs b/src/export/pdf/image.rs index 04d4dcc3..dcd5a45a 100644 --- a/src/export/pdf/image.rs +++ b/src/export/pdf/image.rs @@ -11,6 +11,7 @@ use crate::image::{DecodedImage, RasterFormat}; pub fn write_images(ctx: &mut PdfContext) { for image in ctx.image_map.items() { let image_ref = ctx.alloc.bump(); + let icc_ref = ctx.alloc.bump(); ctx.image_refs.push(image_ref); let width = image.width(); @@ -19,7 +20,7 @@ pub fn write_images(ctx: &mut PdfContext) { // Add the primary image. // TODO: Error if image could not be encoded. match image.decoded() { - DecodedImage::Raster(dynamic, format) => { + DecodedImage::Raster(dynamic, icc, format) => { // TODO: Error if image could not be encoded. let (data, filter, has_color) = encode_image(*format, dynamic).unwrap(); let mut image = ctx.writer.image_xobject(image_ref, &data); @@ -29,7 +30,9 @@ pub fn write_images(ctx: &mut PdfContext) { image.bits_per_component(8); let space = image.color_space(); - if has_color { + if icc.is_some() { + space.icc_based(icc_ref); + } else if has_color { space.device_rgb(); } else { space.device_gray(); @@ -49,6 +52,21 @@ pub fn write_images(ctx: &mut PdfContext) { mask.height(height as i32); mask.color_space().device_gray(); mask.bits_per_component(8); + } else { + image.finish(); + } + + if let Some(icc) = icc { + let compressed = deflate(&icc.0); + let mut stream = ctx.writer.icc_profile(icc_ref, &compressed); + stream.filter(Filter::FlateDecode); + if has_color { + stream.n(3); + stream.alternate().srgb(); + } else { + stream.n(1); + stream.alternate().d65_gray(); + } } } DecodedImage::Svg(svg) => { diff --git a/src/export/pdf/outline.rs b/src/export/pdf/outline.rs index f8f12d71..c156ecaf 100644 --- a/src/export/pdf/outline.rs +++ b/src/export/pdf/outline.rs @@ -118,7 +118,7 @@ fn write_outline_item( let index = pos.page.get() - 1; if let Some(&height) = ctx.page_heights.get(index) { let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero()); - outline.dest_direct().page(ctx.page_refs[index]).xyz( + outline.dest().page(ctx.page_refs[index]).xyz( pos.point.x.to_f32(), height - y.to_f32(), None, diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs index acf5062e..35a4f5dc 100644 --- a/src/export/pdf/page.rs +++ b/src/export/pdf/page.rs @@ -139,7 +139,7 @@ fn write_page(ctx: &mut PdfContext, page: Page) { annotation .action() .action_type(ActionType::GoTo) - .destination_direct() + .destination() .page(ctx.page_refs[index]) .xyz(pos.point.x.to_f32(), height - y.to_f32(), None); } @@ -499,7 +499,7 @@ fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size) if let Some(alt) = image.alt() { let mut image_span = ctx.content.begin_marked_content_with_properties(Name(b"Span")); - let mut image_alt = image_span.properties_direct(); + let mut image_alt = image_span.properties(); image_alt.pair(Name(b"Alt"), pdf_writer::Str(alt.as_bytes())); image_alt.finish(); image_span.finish(); diff --git a/src/export/render.rs b/src/export/render.rs index fa3dc4b5..31e440d1 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -5,9 +5,10 @@ use std::sync::Arc; use image::imageops::FilterType; use image::{GenericImageView, Rgba}; +use resvg::FitTo; use tiny_skia as sk; use ttf_parser::{GlyphId, OutlineBuilder}; -use usvg::{FitTo, NodeExt}; +use usvg::{NodeExt, TreeParsing}; use crate::doc::{Frame, FrameItem, GroupItem, Meta, TextItem}; use crate::geom::{ @@ -38,7 +39,7 @@ pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap { fn render_frame( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, frame: &Frame, ) { for (pos, item) in frame.items() { @@ -73,13 +74,13 @@ fn render_frame( fn render_group( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, group: &GroupItem, ) { let ts = ts.pre_concat(group.transform.into()); let mut mask = mask; - let mut storage; + let storage; if group.clips { let size = group.frame.size(); let w = size.x.to_f32(); @@ -88,22 +89,33 @@ fn render_group( .map(sk::PathBuilder::from_rect) .and_then(|path| path.transform(ts)) { - let result = if let Some(mask) = mask { - storage = mask.clone(); - storage.intersect_path(&path, sk::FillRule::default(), false) + if let Some(mask) = mask { + let mut mask = mask.clone(); + mask.intersect_path( + &path, + sk::FillRule::default(), + false, + sk::Transform::default(), + ); + storage = mask; } else { let pxw = canvas.width(); let pxh = canvas.height(); - storage = sk::ClipMask::new(); - storage.set_path(pxw, pxh, &path, sk::FillRule::default(), false) + let Some(mut mask) = sk::Mask::new(pxw, pxh) else { + // Fails if clipping rect is empty. In that case we just + // clip everything by returning. + return; + }; + + mask.fill_path( + &path, + sk::FillRule::default(), + false, + sk::Transform::default(), + ); + storage = mask; }; - // Clipping fails if clipping rect is empty. In that case we just - // clip everything by returning. - if result.is_none() { - return; - } - mask = Some(&storage); } } @@ -115,7 +127,7 @@ fn render_group( fn render_text( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, text: &TextItem, ) { let mut x = 0.0; @@ -136,7 +148,7 @@ fn render_text( fn render_svg_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, text: &TextItem, id: GlyphId, ) -> Option<()> { @@ -157,8 +169,8 @@ fn render_svg_glyph( // Parse SVG. let opts = usvg::Options::default(); - let tree = usvg::Tree::from_xmltree(&document, &opts.to_ref()).ok()?; - let view_box = tree.svg_node().view_box.rect; + let tree = usvg::Tree::from_xmltree(&document, &opts).ok()?; + let view_box = tree.view_box.rect; // If there's no viewbox defined, use the em square for our scale // transformation ... @@ -182,7 +194,7 @@ fn render_svg_glyph( // See https://github.com/RazrFalcon/resvg/issues/602 for why // using the svg size is problematic here. let mut bbox = usvg::Rect::new_bbox(); - for node in tree.root().descendants() { + for node in tree.root.descendants() { if let Some(rect) = node.calculate_bbox().and_then(|b| b.to_rect()) { bbox = bbox.expand(rect); } @@ -224,14 +236,16 @@ fn render_svg_glyph( &sk::PixmapPaint::default(), sk::Transform::identity(), mask, - ) + ); + + Some(()) } /// Render a bitmap glyph into the canvas. fn render_bitmap_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, text: &TextItem, id: GlyphId, ) -> Option<()> { @@ -255,7 +269,7 @@ fn render_bitmap_glyph( fn render_outline_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, text: &TextItem, id: GlyphId, ) -> Option<()> { @@ -278,7 +292,7 @@ fn render_outline_glyph( // system is Y-up. let scale = text.size.to_f32() / text.font.units_per_em() as f32; let ts = ts.pre_scale(scale, -scale); - canvas.fill_path(&path, &paint, rule, ts, mask)?; + canvas.fill_path(&path, &paint, rule, ts, mask); return Some(()); } @@ -318,7 +332,9 @@ fn render_outline_glyph( &sk::PixmapPaint::default(), sk::Transform::identity(), mask, - ) + ); + + Some(()) } else { let cw = canvas.width() as i32; let ch = canvas.height() as i32; @@ -365,7 +381,7 @@ fn render_outline_glyph( fn render_shape( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, shape: &Shape, ) -> Option<()> { let path = match shape.geometry { @@ -465,7 +481,7 @@ fn convert_path(path: &geom::Path) -> Option<sk::Path> { fn render_image( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, image: &Image, size: Size, ) -> Option<()> { @@ -503,7 +519,7 @@ fn render_image( fn scaled_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> { let mut pixmap = sk::Pixmap::new(w, h)?; match image.decoded() { - DecodedImage::Raster(dynamic, _) => { + DecodedImage::Raster(dynamic, _, _) => { let downscale = w < image.width(); let filter = if downscale { FilterType::Lanczos3 } else { FilterType::CatmullRom }; |
