mirror of
https://github.com/typst/typst
synced 2025-06-08 13:16:24 +08:00
more
This commit is contained in:
parent
eba4fa596f
commit
c915f69e87
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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"
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user