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

View File

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

View File

@ -20,7 +20,7 @@ pub struct RasterImage(Arc<Repr>);
struct Repr {
data: Bytes,
format: RasterFormat,
dynamic: image::DynamicImage,
dynamic: Arc<image::DynamicImage>,
icc: Option<Vec<u8>>,
dpi: Option<f64>,
}
@ -59,7 +59,7 @@ impl RasterImage {
// Extract pixel density.
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.
@ -88,8 +88,8 @@ impl RasterImage {
}
/// Access the underlying dynamic image.
pub fn dynamic(&self) -> &image::DynamicImage {
&self.0.dynamic
pub fn dynamic(&self) -> Arc<image::DynamicImage> {
self.0.dynamic.clone()
}
/// 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.
#[typst_macros::time(name = "encode raster image")]
fn encode_raster_image(image: &RasterImage) -> (Vec<u8>, Filter, bool) {
let dynamic = image.dynamic();
let channel_count = dynamic.color().channel_count();
let has_color = channel_count > 2;
if image.format() == RasterFormat::Jpg {
let mut data = Cursor::new(vec![]);
dynamic.write_to(&mut data, image::ImageFormat::Jpeg).unwrap();
(data.into_inner(), Filter::DctDecode, has_color)
} else {
// TODO: Encode flate streams with PNG-predictor?
let data = match (dynamic, channel_count) {
(DynamicImage::ImageLuma8(luma), _) => deflate(luma.as_raw()),
(DynamicImage::ImageRgb8(rgb), _) => deflate(rgb.as_raw()),
// Grayscale image
(_, 1 | 2) => deflate(dynamic.to_luma8().as_raw()),
// Anything else
_ => deflate(dynamic.to_rgb8().as_raw()),
};
(data, Filter::FlateDecode, has_color)
}
// let dynamic = image.dynamic();
// let channel_count = dynamic.color().channel_count();
// let has_color = channel_count > 2;
//
// if image.format() == RasterFormat::Jpg {
// let mut data = Cursor::new(vec![]);
// dynamic.write_to(&mut data, image::ImageFormat::Jpeg).unwrap();
// (data.into_inner(), Filter::DctDecode, has_color)
// } else {
// // TODO: Encode flate streams with PNG-predictor?
// let data = match (dynamic, channel_count) {
// (DynamicImage::ImageLuma8(luma), _) => deflate(luma.as_raw()),
// (DynamicImage::ImageRgb8(rgb), _) => deflate(rgb.as_raw()),
// // Grayscale image
// (_, 1 | 2) => deflate(dynamic.to_luma8().as_raw()),
// // Anything else
// _ => deflate(dynamic.to_rgb8().as_raw()),
// };
// (data, Filter::FlateDecode, has_color)
// }
unimplemented!()
}
/// 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::{PageSettings, SerializeSettings, SvgSettings};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::ops::Range;
use std::sync::Arc;
use std::sync::{Arc, OnceLock};
use bytemuck::TransparentWrapper;
use image::GenericImageView;
use image::{DynamicImage, GenericImageView, Rgba};
use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace};
use krilla::validation::Validator;
use krilla::version::PdfVersion;
use svg2pdf::usvg::{NormalizedF32, Rect};
use svg2pdf::usvg::filter::ColorChannel;
use typst_library::layout::{Frame, FrameItem, GroupItem, Size};
use typst_library::model::Document;
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)]
#[repr(transparent)]
@ -116,7 +119,8 @@ pub fn handle_text(t: &TextItem, surface: &mut Surface, context: &mut ExportCont
krilla::font::Font::new(
// TODO: Don't do to_vec here!
Arc::new(t.font.data().to_vec()),
t.font.index()
t.font.index(),
true
)
// TODO: DOn't 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")]
pub fn handle_image(
image: &Image,
@ -167,8 +238,7 @@ pub fn handle_image(
) {
match image.kind() {
ImageKind::Raster(raster) => {
let image = krilla::image::Image::from_png(raster.data())
.unwrap();
let image = convert_raster(raster.clone());
surface.draw_image(
image,
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) {
let mut path_builder = PathBuilder::new();