mirror of
https://github.com/typst/typst
synced 2025-08-15 07:28:32 +08:00
Reformat + add support for exif
This commit is contained in:
parent
c990ced644
commit
8f0cb71db8
@ -3,17 +3,17 @@ use std::hash::{Hash, Hasher};
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::Arc;
|
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 ecow::{eco_format, EcoString};
|
||||||
use image::codecs::gif::GifDecoder;
|
use image::codecs::gif::GifDecoder;
|
||||||
use image::codecs::jpeg::JpegDecoder;
|
use image::codecs::jpeg::JpegDecoder;
|
||||||
use image::codecs::png::PngDecoder;
|
use image::codecs::png::PngDecoder;
|
||||||
|
use image::imageops::rotate180;
|
||||||
use image::{
|
use image::{
|
||||||
guess_format, DynamicImage, ImageBuffer, ImageDecoder, ImageResult, Limits, Pixel,
|
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.
|
/// A decoded raster image.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct RasterImage(Arc<Repr>);
|
pub struct RasterImage(Arc<Repr>);
|
||||||
@ -23,7 +23,7 @@ struct Repr {
|
|||||||
data: Bytes,
|
data: Bytes,
|
||||||
format: RasterFormat,
|
format: RasterFormat,
|
||||||
dynamic: Arc<DynamicImage>,
|
dynamic: Arc<DynamicImage>,
|
||||||
is_rotated: bool,
|
exif_rotation: Option<u32>,
|
||||||
icc: Option<Bytes>,
|
icc: Option<Bytes>,
|
||||||
dpi: Option<f64>,
|
dpi: Option<f64>,
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ impl RasterImage {
|
|||||||
format: RasterFormat,
|
format: RasterFormat,
|
||||||
icc: Smart<Bytes>,
|
icc: Smart<Bytes>,
|
||||||
) -> StrResult<RasterImage> {
|
) -> StrResult<RasterImage> {
|
||||||
let mut is_rotated = false;
|
let mut exif_rot = None;
|
||||||
|
|
||||||
let (dynamic, icc, dpi) = match format {
|
let (dynamic, icc, dpi) = match format {
|
||||||
RasterFormat::Exchange(format) => {
|
RasterFormat::Exchange(format) => {
|
||||||
@ -88,7 +88,7 @@ impl RasterImage {
|
|||||||
// Apply rotation from EXIF metadata.
|
// Apply rotation from EXIF metadata.
|
||||||
if let Some(rotation) = exif.as_ref().and_then(exif_rotation) {
|
if let Some(rotation) = exif.as_ref().and_then(exif_rotation) {
|
||||||
apply_rotation(&mut dynamic, rotation);
|
apply_rotation(&mut dynamic, rotation);
|
||||||
is_rotated = rotation != 1;
|
exif_rot = Some(rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract pixel density.
|
// Extract pixel density.
|
||||||
@ -143,7 +143,7 @@ impl RasterImage {
|
|||||||
Ok(Self(Arc::new(Repr {
|
Ok(Self(Arc::new(Repr {
|
||||||
data,
|
data,
|
||||||
format,
|
format,
|
||||||
is_rotated,
|
exif_rotation: exif_rot,
|
||||||
dynamic: Arc::new(dynamic),
|
dynamic: Arc::new(dynamic),
|
||||||
icc,
|
icc,
|
||||||
dpi,
|
dpi,
|
||||||
@ -170,9 +170,9 @@ impl RasterImage {
|
|||||||
self.dynamic().height()
|
self.dynamic().height()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the image has been rotated due to EXIF metadata.
|
/// TODO.
|
||||||
pub fn is_rotated(&self) -> bool {
|
pub fn exif_rotation(&self) -> Option<u32> {
|
||||||
self.0.is_rotated
|
self.0.exif_rotation
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The image's pixel density in pixels per inch, if known.
|
/// The image's pixel density in pixels per inch, if known.
|
||||||
|
@ -3,13 +3,13 @@ use std::num::NonZeroU64;
|
|||||||
|
|
||||||
use ecow::EcoVec;
|
use ecow::EcoVec;
|
||||||
use krilla::error::KrillaError;
|
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::page::PageLabel;
|
||||||
use krilla::path::PathBuilder;
|
use krilla::path::PathBuilder;
|
||||||
use krilla::surface::Surface;
|
use krilla::surface::Surface;
|
||||||
use krilla::{Configuration, Document, PageSettings, SerializeSettings, ValidationError};
|
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 krilla_svg::render_svg_glyph;
|
||||||
use typst_library::diag::{bail, error, SourceResult};
|
use typst_library::diag::{bail, error, SourceResult};
|
||||||
use typst_library::foundations::NativeElement;
|
use typst_library::foundations::NativeElement;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use krilla::Document;
|
|
||||||
use krilla::interchange::embed::{AssociationKind, EmbeddedFile};
|
use krilla::interchange::embed::{AssociationKind, EmbeddedFile};
|
||||||
|
use krilla::Document;
|
||||||
use typst_library::diag::{bail, SourceResult};
|
use typst_library::diag::{bail, SourceResult};
|
||||||
use typst_library::foundations::{NativeElement, StyleChain};
|
use typst_library::foundations::{NativeElement, StyleChain};
|
||||||
use typst_library::layout::PagedDocument;
|
use typst_library::layout::PagedDocument;
|
||||||
|
@ -7,7 +7,7 @@ use krilla::surface::Surface;
|
|||||||
use krilla_svg::{SurfaceExt, SvgSettings};
|
use krilla_svg::{SurfaceExt, SvgSettings};
|
||||||
use typst_library::diag::{bail, SourceResult};
|
use typst_library::diag::{bail, SourceResult};
|
||||||
use typst_library::foundations::Smart;
|
use typst_library::foundations::Smart;
|
||||||
use typst_library::layout::Size;
|
use typst_library::layout::{Abs, Angle, Ratio, Size, Transform};
|
||||||
use typst_library::visualize::{
|
use typst_library::visualize::{
|
||||||
ExchangeFormat, Image, ImageKind, ImageScaling, RasterFormat, RasterImage,
|
ExchangeFormat, Image, ImageKind, ImageScaling, RasterFormat, RasterImage,
|
||||||
};
|
};
|
||||||
@ -31,6 +31,8 @@ pub(crate) fn handle_image(
|
|||||||
|
|
||||||
match image.kind() {
|
match image.kind() {
|
||||||
ImageKind::Raster(raster) => {
|
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) {
|
let image = match convert_raster(raster.clone(), interpolate) {
|
||||||
None => bail!(span, "failed to process image"),
|
None => bail!(span, "failed to process image"),
|
||||||
Some(i) => i,
|
Some(i) => i,
|
||||||
@ -41,7 +43,8 @@ pub(crate) fn handle_image(
|
|||||||
gc.image_spans.insert(span);
|
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) => {
|
ImageKind::Svg(svg) => {
|
||||||
surface.draw_svg(
|
surface.draw_svg(
|
||||||
@ -168,19 +171,61 @@ fn convert_raster(
|
|||||||
match raster.format() {
|
match raster.format() {
|
||||||
RasterFormat::Exchange(e) => match e {
|
RasterFormat::Exchange(e) => match e {
|
||||||
ExchangeFormat::Jpg => {
|
ExchangeFormat::Jpg => {
|
||||||
if !raster.is_rotated() {
|
let image_data: Arc<dyn AsRef<[u8]> + Send + Sync> =
|
||||||
let image_data: Arc<dyn AsRef<[u8]> + Send + Sync> =
|
Arc::new(raster.data().clone());
|
||||||
Arc::new(raster.data().clone());
|
krilla::graphics::image::Image::from_jpeg(image_data.into(), interpolate)
|
||||||
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(_) => {
|
RasterFormat::Pixel(_) => krilla::graphics::image::Image::from_custom(
|
||||||
krilla::graphics::image::Image::from_custom(PdfImage::new(raster), interpolate)
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,9 +65,11 @@ pub(crate) fn handle_link(
|
|||||||
LinkAnnotation::new(
|
LinkAnnotation::new(
|
||||||
rect,
|
rect,
|
||||||
None,
|
None,
|
||||||
Target::Destination(krilla::interactive::destination::Destination::Named(
|
Target::Destination(
|
||||||
nd.clone(),
|
krilla::interactive::destination::Destination::Named(
|
||||||
)),
|
nd.clone(),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
|
use crate::convert::GlobalContext;
|
||||||
|
use crate::Timezone;
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use krilla::interchange::metadata::{Metadata, TextDirection};
|
use krilla::interchange::metadata::{Metadata, TextDirection};
|
||||||
use typst_library::foundations::{Datetime, Smart};
|
use typst_library::foundations::{Datetime, Smart};
|
||||||
use typst_library::layout::Dir;
|
use typst_library::layout::Dir;
|
||||||
use typst_library::text::Lang;
|
use typst_library::text::Lang;
|
||||||
use crate::convert::GlobalContext;
|
|
||||||
use crate::Timezone;
|
|
||||||
|
|
||||||
pub(crate) fn build_metadata(gc: &GlobalContext) -> Metadata {
|
pub(crate) fn build_metadata(gc: &GlobalContext) -> Metadata {
|
||||||
let creator = format!("Typst {}", env!("CARGO_PKG_VERSION"));
|
let creator = format!("Typst {}", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
let lang = gc
|
let lang = gc.languages.iter().max_by_key(|(_, &count)| count).map(|(&l, _)| l);
|
||||||
.languages
|
|
||||||
.iter()
|
|
||||||
.max_by_key(|(_, &count)| count)
|
|
||||||
.map(|(&l, _)| l);
|
|
||||||
|
|
||||||
let dir = if lang.map(Lang::dir) == Some(Dir::RTL) {
|
let dir = if lang.map(Lang::dir) == Some(Dir::RTL) {
|
||||||
TextDirection::RightToLeft
|
TextDirection::RightToLeft
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::num::NonZeroUsize;
|
|
||||||
use krilla::interactive::destination::XyzDestination;
|
use krilla::interactive::destination::XyzDestination;
|
||||||
use krilla::interchange::outline::{Outline, OutlineNode};
|
use krilla::interchange::outline::{Outline, OutlineNode};
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
use typst_library::foundations::{NativeElement, Packed, StyleChain};
|
use typst_library::foundations::{NativeElement, Packed, StyleChain};
|
||||||
use typst_library::layout::Abs;
|
use typst_library::layout::Abs;
|
||||||
use typst_library::model::HeadingElem;
|
use typst_library::model::HeadingElem;
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
use krilla::geom::NormalizedF32;
|
use krilla::geom::NormalizedF32;
|
||||||
use krilla::graphics::color::{cmyk, luma, rgb};
|
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 krilla::surface::Surface;
|
||||||
use typst_library::diag::SourceResult;
|
use typst_library::diag::SourceResult;
|
||||||
use typst_library::layout::{Abs, Angle, Quadrant, Ratio, Size, Transform};
|
use typst_library::layout::{Abs, Angle, Quadrant, Ratio, Size, Transform};
|
||||||
@ -240,9 +243,7 @@ fn convert_gradient(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_gradient_stops(
|
fn convert_gradient_stops(gradient: &Gradient) -> Vec<Stop<rgb::Color>> {
|
||||||
gradient: &Gradient,
|
|
||||||
) -> Vec<Stop<rgb::Color>> {
|
|
||||||
let mut stops: Vec<Stop<rgb::Color>> = vec![];
|
let mut stops: Vec<Stop<rgb::Color>> = vec![];
|
||||||
|
|
||||||
let mut add_single = |color: &Color, offset: Ratio| {
|
let mut add_single = |color: &Color, offset: Ratio| {
|
||||||
|
@ -2,8 +2,8 @@ use std::ops::Range;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bytemuck::TransparentWrapper;
|
use bytemuck::TransparentWrapper;
|
||||||
use krilla::text::GlyphId;
|
|
||||||
use krilla::surface::{Location, Surface};
|
use krilla::surface::{Location, Surface};
|
||||||
|
use krilla::text::GlyphId;
|
||||||
use typst_library::diag::{bail, SourceResult};
|
use typst_library::diag::{bail, SourceResult};
|
||||||
use typst_library::layout::{Abs, Size};
|
use typst_library::layout::{Abs, Size};
|
||||||
use typst_library::text::{Font, Glyph, TextItem};
|
use typst_library::text::{Font, Glyph, TextItem};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Basic utilities for converting typst types to krilla.
|
//! Basic utilities for converting typst types to krilla.
|
||||||
|
|
||||||
use krilla::graphics::color::rgb as kr;
|
|
||||||
use krilla::geom as kg;
|
use krilla::geom as kg;
|
||||||
|
use krilla::graphics::color::rgb as kr;
|
||||||
use krilla::graphics::paint as kp;
|
use krilla::graphics::paint as kp;
|
||||||
use krilla::path::PathBuilder;
|
use krilla::path::PathBuilder;
|
||||||
use typst_library::layout::{Abs, Point, Size, Transform};
|
use typst_library::layout::{Abs, Point, Size, Transform};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user