From 8f0cb71db8310bcfd71b9489137f230496d2eecf Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Mon, 17 Mar 2025 23:22:53 +0100 Subject: [PATCH] Reformat + add support for exif --- .../src/visualize/image/raster.rs | 20 ++--- crates/typst-pdf/src/convert.rs | 6 +- crates/typst-pdf/src/embed.rs | 2 +- crates/typst-pdf/src/image.rs | 73 +++++++++++++++---- crates/typst-pdf/src/link.rs | 8 +- crates/typst-pdf/src/metadata.rs | 12 +-- crates/typst-pdf/src/outline.rs | 2 +- crates/typst-pdf/src/paint.rs | 9 ++- crates/typst-pdf/src/text.rs | 2 +- crates/typst-pdf/src/util.rs | 2 +- 10 files changed, 90 insertions(+), 46 deletions(-) diff --git a/crates/typst-library/src/visualize/image/raster.rs b/crates/typst-library/src/visualize/image/raster.rs index 7160606e5..79efae00d 100644 --- a/crates/typst-library/src/visualize/image/raster.rs +++ b/crates/typst-library/src/visualize/image/raster.rs @@ -3,17 +3,17 @@ use std::hash::{Hash, Hasher}; use std::io; use std::sync::Arc; +use crate::diag::{bail, StrResult}; +use crate::foundations::{cast, dict, Bytes, Cast, Dict, Smart, Value}; use ecow::{eco_format, EcoString}; use image::codecs::gif::GifDecoder; use image::codecs::jpeg::JpegDecoder; use image::codecs::png::PngDecoder; +use image::imageops::rotate180; use image::{ guess_format, DynamicImage, ImageBuffer, ImageDecoder, ImageResult, Limits, Pixel, }; -use crate::diag::{bail, StrResult}; -use crate::foundations::{cast, dict, Bytes, Cast, Dict, Smart, Value}; - /// A decoded raster image. #[derive(Clone, Hash)] pub struct RasterImage(Arc); @@ -23,7 +23,7 @@ struct Repr { data: Bytes, format: RasterFormat, dynamic: Arc, - is_rotated: bool, + exif_rotation: Option, icc: Option, dpi: Option, } @@ -51,7 +51,7 @@ impl RasterImage { format: RasterFormat, icc: Smart, ) -> StrResult { - let mut is_rotated = false; + let mut exif_rot = None; let (dynamic, icc, dpi) = match format { RasterFormat::Exchange(format) => { @@ -88,7 +88,7 @@ impl RasterImage { // Apply rotation from EXIF metadata. if let Some(rotation) = exif.as_ref().and_then(exif_rotation) { apply_rotation(&mut dynamic, rotation); - is_rotated = rotation != 1; + exif_rot = Some(rotation); } // Extract pixel density. @@ -143,7 +143,7 @@ impl RasterImage { Ok(Self(Arc::new(Repr { data, format, - is_rotated, + exif_rotation: exif_rot, dynamic: Arc::new(dynamic), icc, dpi, @@ -170,9 +170,9 @@ impl RasterImage { self.dynamic().height() } - /// Whether the image has been rotated due to EXIF metadata. - pub fn is_rotated(&self) -> bool { - self.0.is_rotated + /// TODO. + pub fn exif_rotation(&self) -> Option { + self.0.exif_rotation } /// The image's pixel density in pixels per inch, if known. diff --git a/crates/typst-pdf/src/convert.rs b/crates/typst-pdf/src/convert.rs index 7ce903ca4..c284a0d87 100644 --- a/crates/typst-pdf/src/convert.rs +++ b/crates/typst-pdf/src/convert.rs @@ -3,13 +3,13 @@ use std::num::NonZeroU64; use ecow::EcoVec; use krilla::error::KrillaError; +use krilla::interactive::annotation::Annotation; +use krilla::interactive::destination::{NamedDestination, XyzDestination}; +use krilla::interchange::embed::EmbedError; use krilla::page::PageLabel; use krilla::path::PathBuilder; use krilla::surface::Surface; use krilla::{Configuration, Document, PageSettings, SerializeSettings, ValidationError}; -use krilla::interactive::annotation::Annotation; -use krilla::interactive::destination::{NamedDestination, XyzDestination}; -use krilla::interchange::embed::EmbedError; use krilla_svg::render_svg_glyph; use typst_library::diag::{bail, error, SourceResult}; use typst_library::foundations::NativeElement; diff --git a/crates/typst-pdf/src/embed.rs b/crates/typst-pdf/src/embed.rs index 307ab5255..6604fe5de 100644 --- a/crates/typst-pdf/src/embed.rs +++ b/crates/typst-pdf/src/embed.rs @@ -1,7 +1,7 @@ use std::sync::Arc; -use krilla::Document; use krilla::interchange::embed::{AssociationKind, EmbeddedFile}; +use krilla::Document; use typst_library::diag::{bail, SourceResult}; use typst_library::foundations::{NativeElement, StyleChain}; use typst_library::layout::PagedDocument; diff --git a/crates/typst-pdf/src/image.rs b/crates/typst-pdf/src/image.rs index 98b8682b8..0dbcc5385 100644 --- a/crates/typst-pdf/src/image.rs +++ b/crates/typst-pdf/src/image.rs @@ -7,7 +7,7 @@ use krilla::surface::Surface; use krilla_svg::{SurfaceExt, SvgSettings}; use typst_library::diag::{bail, SourceResult}; use typst_library::foundations::Smart; -use typst_library::layout::Size; +use typst_library::layout::{Abs, Angle, Ratio, Size, Transform}; use typst_library::visualize::{ ExchangeFormat, Image, ImageKind, ImageScaling, RasterFormat, RasterImage, }; @@ -31,6 +31,8 @@ pub(crate) fn handle_image( match image.kind() { ImageKind::Raster(raster) => { + let (exif_transform, new_size) = exif_transform(raster, size); + surface.push_transform(&exif_transform.to_krilla()); let image = match convert_raster(raster.clone(), interpolate) { None => bail!(span, "failed to process image"), Some(i) => i, @@ -41,7 +43,8 @@ pub(crate) fn handle_image( gc.image_spans.insert(span); } - surface.draw_image(image, size.to_krilla()); + surface.draw_image(image, new_size.to_krilla()); + surface.pop(); } ImageKind::Svg(svg) => { surface.draw_svg( @@ -168,19 +171,61 @@ fn convert_raster( match raster.format() { RasterFormat::Exchange(e) => match e { ExchangeFormat::Jpg => { - if !raster.is_rotated() { - let image_data: Arc + Send + Sync> = - Arc::new(raster.data().clone()); - krilla::graphics::image::Image::from_jpeg(image_data.into(), interpolate) - } else { - // Can't embed original JPEG data if it had to be rotated. - krilla::graphics::image::Image::from_custom(PdfImage::new(raster), interpolate) - } + let image_data: Arc + Send + Sync> = + Arc::new(raster.data().clone()); + krilla::graphics::image::Image::from_jpeg(image_data.into(), interpolate) } - _ => krilla::graphics::image::Image::from_custom(PdfImage::new(raster), interpolate), + _ => krilla::graphics::image::Image::from_custom( + PdfImage::new(raster), + interpolate, + ), }, - RasterFormat::Pixel(_) => { - krilla::graphics::image::Image::from_custom(PdfImage::new(raster), interpolate) - } + RasterFormat::Pixel(_) => krilla::graphics::image::Image::from_custom( + PdfImage::new(raster), + interpolate, + ), + } +} + +fn exif_transform(image: &RasterImage, size: Size) -> (Transform, Size) { + let base = |hp: bool, vp: bool, mut base_ts: Transform, size: Size| { + if hp { + // Flip horizontally in-place. + base_ts = base_ts.pre_concat( + Transform::scale(-Ratio::one(), Ratio::one()) + .pre_concat(Transform::translate(-size.x, Abs::zero())), + ) + } + + if vp { + // Flip vertically in-place. + base_ts = base_ts.pre_concat( + Transform::scale(Ratio::one(), -Ratio::one()) + .pre_concat(Transform::translate(Abs::zero(), -size.y)), + ) + } + + base_ts + }; + + let no_flipping = + |hp: bool, vp: bool| (base(hp, vp, Transform::identity(), size), size); + + let with_flipping = |hp: bool, vp: bool| { + let base_ts = Transform::rotate_at(Angle::deg(90.0), Abs::zero(), Abs::zero()) + .pre_concat(Transform::scale(Ratio::one(), -Ratio::one())); + let inv_size = Size::new(size.y, size.x); + (base(hp, vp, base_ts, inv_size), inv_size) + }; + + match image.exif_rotation() { + Some(2) => no_flipping(true, false), + Some(3) => no_flipping(true, true), + Some(4) => no_flipping(false, true), + Some(5) => with_flipping(false, false), + Some(6) => with_flipping(false, true), + Some(7) => with_flipping(true, true), + Some(8) => with_flipping(true, false), + _ => no_flipping(false, false), } } diff --git a/crates/typst-pdf/src/link.rs b/crates/typst-pdf/src/link.rs index 156f7351b..65a3d363b 100644 --- a/crates/typst-pdf/src/link.rs +++ b/crates/typst-pdf/src/link.rs @@ -65,9 +65,11 @@ pub(crate) fn handle_link( LinkAnnotation::new( rect, None, - Target::Destination(krilla::interactive::destination::Destination::Named( - nd.clone(), - )), + Target::Destination( + krilla::interactive::destination::Destination::Named( + nd.clone(), + ), + ), ) .into(), ); diff --git a/crates/typst-pdf/src/metadata.rs b/crates/typst-pdf/src/metadata.rs index d46c0d826..b22f7c646 100644 --- a/crates/typst-pdf/src/metadata.rs +++ b/crates/typst-pdf/src/metadata.rs @@ -1,19 +1,15 @@ +use crate::convert::GlobalContext; +use crate::Timezone; use ecow::EcoString; use krilla::interchange::metadata::{Metadata, TextDirection}; use typst_library::foundations::{Datetime, Smart}; use typst_library::layout::Dir; use typst_library::text::Lang; -use crate::convert::GlobalContext; -use crate::Timezone; pub(crate) fn build_metadata(gc: &GlobalContext) -> Metadata { let creator = format!("Typst {}", env!("CARGO_PKG_VERSION")); - let lang = gc - .languages - .iter() - .max_by_key(|(_, &count)| count) - .map(|(&l, _)| l); + let lang = gc.languages.iter().max_by_key(|(_, &count)| count).map(|(&l, _)| l); let dir = if lang.map(Lang::dir) == Some(Dir::RTL) { TextDirection::RightToLeft @@ -59,7 +55,7 @@ pub(crate) fn build_metadata(gc: &GlobalContext) -> Metadata { if let Some(date) = date.and_then(|d| convert_date(d, tz)) { metadata = metadata.modification_date(date).creation_date(date); } - + metadata = metadata.text_direction(dir); metadata diff --git a/crates/typst-pdf/src/outline.rs b/crates/typst-pdf/src/outline.rs index 92364994a..9e3ccd0bb 100644 --- a/crates/typst-pdf/src/outline.rs +++ b/crates/typst-pdf/src/outline.rs @@ -1,6 +1,6 @@ -use std::num::NonZeroUsize; use krilla::interactive::destination::XyzDestination; use krilla::interchange::outline::{Outline, OutlineNode}; +use std::num::NonZeroUsize; use typst_library::foundations::{NativeElement, Packed, StyleChain}; use typst_library::layout::Abs; use typst_library::model::HeadingElem; diff --git a/crates/typst-pdf/src/paint.rs b/crates/typst-pdf/src/paint.rs index d40860b7e..353c2210f 100644 --- a/crates/typst-pdf/src/paint.rs +++ b/crates/typst-pdf/src/paint.rs @@ -2,7 +2,10 @@ use krilla::geom::NormalizedF32; use krilla::graphics::color::{cmyk, luma, rgb}; -use krilla::graphics::paint::{Fill, LinearGradient, Pattern, RadialGradient, SpreadMethod, Stop, Stroke, StrokeDash, SweepGradient}; +use krilla::graphics::paint::{ + Fill, LinearGradient, Pattern, RadialGradient, SpreadMethod, Stop, Stroke, + StrokeDash, SweepGradient, +}; use krilla::surface::Surface; use typst_library::diag::SourceResult; use typst_library::layout::{Abs, Angle, Quadrant, Ratio, Size, Transform}; @@ -240,9 +243,7 @@ fn convert_gradient( } } -fn convert_gradient_stops( - gradient: &Gradient, -) -> Vec> { +fn convert_gradient_stops(gradient: &Gradient) -> Vec> { let mut stops: Vec> = vec![]; let mut add_single = |color: &Color, offset: Ratio| { diff --git a/crates/typst-pdf/src/text.rs b/crates/typst-pdf/src/text.rs index 8f0a904ef..331b8cc44 100644 --- a/crates/typst-pdf/src/text.rs +++ b/crates/typst-pdf/src/text.rs @@ -2,8 +2,8 @@ use std::ops::Range; use std::sync::Arc; use bytemuck::TransparentWrapper; -use krilla::text::GlyphId; use krilla::surface::{Location, Surface}; +use krilla::text::GlyphId; use typst_library::diag::{bail, SourceResult}; use typst_library::layout::{Abs, Size}; use typst_library::text::{Font, Glyph, TextItem}; diff --git a/crates/typst-pdf/src/util.rs b/crates/typst-pdf/src/util.rs index 4bc96d2c6..5e1c5dcfb 100644 --- a/crates/typst-pdf/src/util.rs +++ b/crates/typst-pdf/src/util.rs @@ -1,7 +1,7 @@ //! Basic utilities for converting typst types to krilla. -use krilla::graphics::color::rgb as kr; use krilla::geom as kg; +use krilla::graphics::color::rgb as kr; use krilla::graphics::paint as kp; use krilla::path::PathBuilder; use typst_library::layout::{Abs, Point, Size, Transform};