Reformat + add support for exif

This commit is contained in:
Laurenz Stampfl 2025-03-17 23:22:53 +01:00
parent c990ced644
commit 8f0cb71db8
10 changed files with 90 additions and 46 deletions

View File

@ -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<Repr>);
@ -23,7 +23,7 @@ struct Repr {
data: Bytes,
format: RasterFormat,
dynamic: Arc<DynamicImage>,
is_rotated: bool,
exif_rotation: Option<u32>,
icc: Option<Bytes>,
dpi: Option<f64>,
}
@ -51,7 +51,7 @@ impl RasterImage {
format: RasterFormat,
icc: Smart<Bytes>,
) -> StrResult<RasterImage> {
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<u32> {
self.0.exif_rotation
}
/// The image's pixel density in pixels per inch, if known.

View File

@ -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;

View File

@ -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;

View File

@ -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<dyn AsRef<[u8]> + 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)
}
}
_ => 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),
}
}

View File

@ -65,9 +65,11 @@ pub(crate) fn handle_link(
LinkAnnotation::new(
rect,
None,
Target::Destination(krilla::interactive::destination::Destination::Named(
Target::Destination(
krilla::interactive::destination::Destination::Named(
nd.clone(),
)),
),
),
)
.into(),
);

View File

@ -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

View File

@ -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;

View File

@ -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<Stop<rgb::Color>> {
fn convert_gradient_stops(gradient: &Gradient) -> Vec<Stop<rgb::Color>> {
let mut stops: Vec<Stop<rgb::Color>> = vec![];
let mut add_single = |color: &Color, offset: Ratio| {

View File

@ -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};

View File

@ -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};