This commit is contained in:
Laurenz Stampfl 2024-12-01 10:19:31 +01:00
parent eba4fa596f
commit c915f69e87
5 changed files with 122 additions and 41 deletions

14
Cargo.lock generated
View File

@ -1307,19 +1307,22 @@ version = "0.3.0"
dependencies = [ dependencies = [
"base64", "base64",
"bumpalo", "bumpalo",
"comemo",
"flate2", "flate2",
"float-cmp 0.10.0", "float-cmp 0.10.0",
"fontdb 0.22.0", "fontdb 0.22.0",
"gif", "gif",
"image-webp", "image-webp",
"imagesize",
"miniz_oxide", "miniz_oxide",
"once_cell", "once_cell",
"pdf-writer 0.12.0 (git+https://github.com/LaurenzV/pdf-writer?rev=f95a19c)", "pdf-writer 0.12.0 (git+https://github.com/LaurenzV/pdf-writer?rev=f95a19c)",
"rayon",
"resvg 0.44.0", "resvg 0.44.0",
"rustybuzz", "rustybuzz",
"siphasher 1.0.1", "siphasher 1.0.1",
"skrifa", "skrifa",
"subsetter", "subsetter 0.2.0 (git+https://github.com/typst/subsetter?rev=172416a)",
"tiny-skia", "tiny-skia",
"tiny-skia-path", "tiny-skia-path",
"usvg 0.44.0", "usvg 0.44.0",
@ -2483,6 +2486,11 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f98178f34057d4d4de93d68104007c6dea4dfac930204a69ab4622daefa648" checksum = "74f98178f34057d4d4de93d68104007c6dea4dfac930204a69ab4622daefa648"
[[package]]
name = "subsetter"
version = "0.2.0"
source = "git+https://github.com/typst/subsetter?rev=172416a#172416a806246e6e9010d400d5690ca7a026e53d"
[[package]] [[package]]
name = "svg2pdf" name = "svg2pdf"
version = "0.12.0" version = "0.12.0"
@ -2497,7 +2505,7 @@ dependencies = [
"pdf-writer 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "pdf-writer 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"resvg 0.43.0", "resvg 0.43.0",
"siphasher 1.0.1", "siphasher 1.0.1",
"subsetter", "subsetter 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tiny-skia", "tiny-skia",
"ttf-parser", "ttf-parser",
"usvg 0.43.0", "usvg 0.43.0",
@ -3044,7 +3052,7 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
"pdf-writer 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "pdf-writer 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde", "serde",
"subsetter", "subsetter 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"svg2pdf", "svg2pdf",
"ttf-parser", "ttf-parser",
"typst-assets", "typst-assets",

View File

@ -68,7 +68,7 @@ if_chain = "1"
image = { version = "0.25.2", default-features = false, features = ["png", "jpeg", "gif"] } image = { version = "0.25.2", default-features = false, features = ["png", "jpeg", "gif"] }
indexmap = { version = "2", features = ["serde"] } indexmap = { version = "2", features = ["serde"] }
kamadak-exif = "0.5" kamadak-exif = "0.5"
krilla = { path = "../krilla/crates/krilla" } krilla = { path = "../krilla/crates/krilla", features = ["comemo", "rayon"] }
kurbo = "0.11" kurbo = "0.11"
libfuzzer-sys = "0.4" libfuzzer-sys = "0.4"
lipsum = "0.9" lipsum = "0.9"
@ -140,13 +140,6 @@ zip = { version = "2", default-features = false, features = ["deflate"] }
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 2 opt-level = 2
[profile.release]
lto = "thin"
codegen-units = 1
[profile.release.package."typst-cli"]
strip = true
[workspace.lints.clippy] [workspace.lints.clippy]
blocks_in_conditions = "allow" blocks_in_conditions = "allow"
comparison_chain = "allow" comparison_chain = "allow"

View File

@ -20,7 +20,7 @@ pub struct RasterImage(Arc<Repr>);
struct Repr { struct Repr {
data: Bytes, data: Bytes,
format: RasterFormat, format: RasterFormat,
dynamic: image::DynamicImage, dynamic: Arc<image::DynamicImage>,
icc: Option<Vec<u8>>, icc: Option<Vec<u8>>,
dpi: Option<f64>, dpi: Option<f64>,
} }
@ -59,7 +59,7 @@ impl RasterImage {
// Extract pixel density. // Extract pixel density.
let dpi = determine_dpi(&data, exif.as_ref()); let dpi = determine_dpi(&data, exif.as_ref());
Ok(Self(Arc::new(Repr { data, format, dynamic, icc, dpi }))) Ok(Self(Arc::new(Repr { data, format, dynamic: Arc::new(dynamic), icc, dpi })))
} }
/// The raw image data. /// The raw image data.
@ -88,8 +88,8 @@ impl RasterImage {
} }
/// Access the underlying dynamic image. /// Access the underlying dynamic image.
pub fn dynamic(&self) -> &image::DynamicImage { pub fn dynamic(&self) -> Arc<image::DynamicImage> {
&self.0.dynamic self.0.dynamic.clone()
} }
/// Access the ICC profile, if any. /// Access the ICC profile, if any.

View File

@ -167,26 +167,27 @@ pub fn deferred_image(
/// Skips the alpha channel as that's encoded separately. /// Skips the alpha channel as that's encoded separately.
#[typst_macros::time(name = "encode raster image")] #[typst_macros::time(name = "encode raster image")]
fn encode_raster_image(image: &RasterImage) -> (Vec<u8>, Filter, bool) { fn encode_raster_image(image: &RasterImage) -> (Vec<u8>, Filter, bool) {
let dynamic = image.dynamic(); // let dynamic = image.dynamic();
let channel_count = dynamic.color().channel_count(); // let channel_count = dynamic.color().channel_count();
let has_color = channel_count > 2; // let has_color = channel_count > 2;
//
if image.format() == RasterFormat::Jpg { // if image.format() == RasterFormat::Jpg {
let mut data = Cursor::new(vec![]); // let mut data = Cursor::new(vec![]);
dynamic.write_to(&mut data, image::ImageFormat::Jpeg).unwrap(); // dynamic.write_to(&mut data, image::ImageFormat::Jpeg).unwrap();
(data.into_inner(), Filter::DctDecode, has_color) // (data.into_inner(), Filter::DctDecode, has_color)
} else { // } else {
// TODO: Encode flate streams with PNG-predictor? // // TODO: Encode flate streams with PNG-predictor?
let data = match (dynamic, channel_count) { // let data = match (dynamic, channel_count) {
(DynamicImage::ImageLuma8(luma), _) => deflate(luma.as_raw()), // (DynamicImage::ImageLuma8(luma), _) => deflate(luma.as_raw()),
(DynamicImage::ImageRgb8(rgb), _) => deflate(rgb.as_raw()), // (DynamicImage::ImageRgb8(rgb), _) => deflate(rgb.as_raw()),
// Grayscale image // // Grayscale image
(_, 1 | 2) => deflate(dynamic.to_luma8().as_raw()), // (_, 1 | 2) => deflate(dynamic.to_luma8().as_raw()),
// Anything else // // Anything else
_ => deflate(dynamic.to_rgb8().as_raw()), // _ => deflate(dynamic.to_rgb8().as_raw()),
}; // };
(data, Filter::FlateDecode, has_color) // (data, Filter::FlateDecode, has_color)
} // }
unimplemented!()
} }
/// Encode an image's alpha channel if present. /// Encode an image's alpha channel if present.

View File

@ -7,17 +7,20 @@ use krilla::path::{Fill, PathBuilder, Stroke};
use krilla::surface::Surface; use krilla::surface::Surface;
use krilla::{PageSettings, SerializeSettings, SvgSettings}; use krilla::{PageSettings, SerializeSettings, SvgSettings};
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::ops::Range; use std::ops::Range;
use std::sync::Arc; use std::sync::{Arc, OnceLock};
use bytemuck::TransparentWrapper; use bytemuck::TransparentWrapper;
use image::GenericImageView; use image::{DynamicImage, GenericImageView, Rgba};
use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace};
use krilla::validation::Validator; use krilla::validation::Validator;
use krilla::version::PdfVersion; use krilla::version::PdfVersion;
use svg2pdf::usvg::{NormalizedF32, Rect}; use svg2pdf::usvg::{NormalizedF32, Rect};
use svg2pdf::usvg::filter::ColorChannel;
use typst_library::layout::{Frame, FrameItem, GroupItem, Size}; use typst_library::layout::{Frame, FrameItem, GroupItem, Size};
use typst_library::model::Document; use typst_library::model::Document;
use typst_library::text::{Font, Glyph, TextItem}; use typst_library::text::{Font, Glyph, TextItem};
use typst_library::visualize::{ColorSpace, FillRule, FixedStroke, Geometry, Image, ImageKind, LineCap, LineJoin, Paint, Path, PathItem, Shape}; use typst_library::visualize::{ColorSpace, FillRule, FixedStroke, Geometry, Image, ImageKind, LineCap, LineJoin, Paint, Path, PathItem, RasterFormat, RasterImage, Shape};
#[derive(TransparentWrapper)] #[derive(TransparentWrapper)]
#[repr(transparent)] #[repr(transparent)]
@ -116,7 +119,8 @@ pub fn handle_text(t: &TextItem, surface: &mut Surface, context: &mut ExportCont
krilla::font::Font::new( krilla::font::Font::new(
// TODO: Don't do to_vec here! // TODO: Don't do to_vec here!
Arc::new(t.font.data().to_vec()), Arc::new(t.font.data().to_vec()),
t.font.index() t.font.index(),
true
) )
// TODO: DOn't unwrap // TODO: DOn't unwrap
.unwrap() .unwrap()
@ -158,6 +162,73 @@ pub fn handle_text(t: &TextItem, surface: &mut Surface, context: &mut ExportCont
} }
} }
#[derive(Clone)]
struct PdfImage {
raster: RasterImage,
alpha_channel: OnceLock<Option<Arc<Vec<u8>>>>,
actual_dynamic: OnceLock<Arc<DynamicImage>>
}
impl PdfImage {
pub fn new(raster: RasterImage) -> Self {
Self {
raster,
alpha_channel: OnceLock::new(),
actual_dynamic: OnceLock::new(),
}
}
}
impl Hash for PdfImage {
fn hash<H: Hasher>(&self, state: &mut H) {
self.raster.hash(state);
}
}
impl CustomImage for PdfImage {
fn color_channel(&self) -> &[u8] {
self.actual_dynamic.get_or_init(|| {
let dynamic = self.raster.dynamic();
let channel_count = dynamic.color().channel_count();
match (dynamic.as_ref(), channel_count) {
(DynamicImage::ImageLuma8(_), _) => dynamic.clone(),
(DynamicImage::ImageRgb8(_), _) => dynamic.clone(),
(_, 1 | 2) => Arc::new(DynamicImage::ImageLuma8(dynamic.to_luma8())),
_ => Arc::new(DynamicImage::ImageRgb8(dynamic.to_rgb8())),
}
}).as_bytes()
}
fn alpha_channel(&self) -> Option<&[u8]> {
self.alpha_channel.get_or_init(||
self.raster.dynamic().color().has_alpha()
.then(|| Arc::new(self.raster
.dynamic()
.pixels()
.map(|(_, _, Rgba([_, _, _, a]))| a)
.collect())
)
).as_ref().map(|v| &***v)
}
fn bits_per_component(&self) -> BitsPerComponent {
BitsPerComponent::Eight
}
fn size(&self) -> (u32, u32) {
(self.raster.width(), self.raster.height())
}
fn color_space(&self) -> ImageColorspace {
if self.raster.dynamic().color().has_color() {
ImageColorspace::Rgb
} else {
ImageColorspace::Luma
}
}
}
#[typst_macros::time(name = "handle image")] #[typst_macros::time(name = "handle image")]
pub fn handle_image( pub fn handle_image(
image: &Image, image: &Image,
@ -167,8 +238,7 @@ pub fn handle_image(
) { ) {
match image.kind() { match image.kind() {
ImageKind::Raster(raster) => { ImageKind::Raster(raster) => {
let image = krilla::image::Image::from_png(raster.data()) let image = convert_raster(raster.clone());
.unwrap();
surface.draw_image( surface.draw_image(
image, image,
krilla::geom::Size::from_wh(size.x.to_f32(), size.y.to_f32()).unwrap(), krilla::geom::Size::from_wh(size.x.to_f32(), size.y.to_f32()).unwrap(),
@ -184,6 +254,15 @@ pub fn handle_image(
} }
} }
#[comemo::memoize]
fn convert_raster(raster: RasterImage) -> krilla::image::Image {
match raster.format() {
// TODO: Remove to_vec
RasterFormat::Jpg => krilla::image::Image::from_jpeg(Arc::new(raster.data().to_vec())),
_ => krilla::image::Image::from_custom(PdfImage::new(raster))
}.unwrap()
}
pub fn handle_shape(shape: &Shape, surface: &mut Surface) { pub fn handle_shape(shape: &Shape, surface: &mut Surface) {
let mut path_builder = PathBuilder::new(); let mut path_builder = PathBuilder::new();