From ec04c3de2fcc5b31d94dc2be8561e569f28cc0a1 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 8 Nov 2023 13:11:11 +0100 Subject: [PATCH] Extract `typst-render` crate --- Cargo.lock | 22 ++- Cargo.toml | 1 + crates/typst-cli/Cargo.toml | 1 + crates/typst-cli/src/compile.rs | 2 +- crates/typst-render/Cargo.toml | 29 ++++ .../render.rs => typst-render/src/lib.rs} | 142 +++++++----------- crates/typst/Cargo.toml | 3 - crates/typst/src/export/mod.rs | 2 - tests/Cargo.toml | 1 + tests/src/benches.rs | 2 +- tests/src/tests.rs | 18 ++- 11 files changed, 126 insertions(+), 97 deletions(-) create mode 100644 crates/typst-render/Cargo.toml rename crates/{typst/src/export/render.rs => typst-render/src/lib.rs} (90%) diff --git a/Cargo.lock b/Cargo.lock index c8ca8a48e..4da1e66d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2908,9 +2908,7 @@ dependencies = [ "once_cell", "palette", "pdf-writer", - "pixglyph", "regex", - "resvg", "roxmltree", "rustybuzz", "serde", @@ -2920,7 +2918,6 @@ dependencies = [ "subsetter", "svg2pdf", "time", - "tiny-skia", "toml", "tracing", "ttf-parser", @@ -2976,6 +2973,7 @@ dependencies = [ "tracing-subscriber", "typst", "typst-library", + "typst-render", "ureq", "xz2", "zip", @@ -3065,6 +3063,23 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "typst-render" +version = "0.9.0" +dependencies = [ + "bytemuck", + "comemo", + "flate2", + "image", + "pixglyph", + "resvg", + "roxmltree", + "tiny-skia", + "ttf-parser", + "typst", + "usvg", +] + [[package]] name = "typst-syntax" version = "0.9.0" @@ -3095,6 +3110,7 @@ dependencies = [ "ttf-parser", "typst", "typst-library", + "typst-render", "unscanny", "walkdir", ] diff --git a/Cargo.toml b/Cargo.toml index eb1b5ac1d..b88d880bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ keywords = ["typst"] typst = { path = "crates/typst" } typst-library = { path = "crates/typst-library" } typst-macros = { path = "crates/typst-macros" } +typst-render = { path = "crates/typst-render" } typst-syntax = { path = "crates/typst-syntax" } az = "1.2" base64 = "0.21.2" diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml index 6155c0461..9a96c6b2f 100644 --- a/crates/typst-cli/Cargo.toml +++ b/crates/typst-cli/Cargo.toml @@ -22,6 +22,7 @@ doc = false [dependencies] typst = { workspace = true } typst-library = { workspace = true } +typst-render = { workspace = true } chrono = { workspace = true } clap = { workspace = true } codespan-reporting = { workspace = true } diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index b5cf0e546..80e19f1b7 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -220,7 +220,7 @@ fn export_image( match fmt { ImageExportFormat::Png => { let pixmap = - typst::export::render(frame, command.ppi / 72.0, Color::WHITE); + typst_render::render(frame, command.ppi / 72.0, Color::WHITE); pixmap .save_png(path) .map_err(|err| eco_format!("failed to write PNG file ({err})"))?; diff --git a/crates/typst-render/Cargo.toml b/crates/typst-render/Cargo.toml new file mode 100644 index 000000000..0c552cd3c --- /dev/null +++ b/crates/typst-render/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "typst-render" +description = "Raster image exporter for Typst." +version.workspace = true +rust-version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +categories.workspace = true +keywords.workspace = true + +[lib] +doctest = false +bench = false + +[dependencies] +typst = { workspace = true } +bytemuck = { workspace = true } +comemo = { workspace = true } +flate2 = { workspace = true } +image = { workspace = true } +pixglyph = { workspace = true } +resvg = { workspace = true } +roxmltree = { workspace = true } +tiny-skia = { workspace = true } +ttf-parser = { workspace = true } +usvg = { workspace = true } diff --git a/crates/typst/src/export/render.rs b/crates/typst-render/src/lib.rs similarity index 90% rename from crates/typst/src/export/render.rs rename to crates/typst-render/src/lib.rs index 7a33849fd..4cd003857 100644 --- a/crates/typst/src/export/render.rs +++ b/crates/typst-render/src/lib.rs @@ -9,15 +9,14 @@ use pixglyph::Bitmap; use resvg::tiny_skia::IntRect; use tiny_skia as sk; use ttf_parser::{GlyphId, OutlineBuilder}; -use usvg::{NodeExt, TreeParsing}; - -use crate::doc::{Frame, FrameItem, FrameKind, GroupItem, Meta, TextItem}; -use crate::font::Font; -use crate::geom::{ +use typst::doc::{Frame, FrameItem, FrameKind, GroupItem, Meta, TextItem}; +use typst::font::Font; +use typst::geom::{ self, Abs, Axes, Color, FixedStroke, Geometry, Gradient, LineCap, LineJoin, Paint, PathItem, Point, Ratio, Relative, Shape, Size, Transform, }; -use crate::image::{Image, ImageKind, RasterFormat}; +use typst::image::{Image, ImageKind, RasterFormat}; +use usvg::{NodeExt, TreeParsing}; /// Export a frame into a raster image. /// @@ -29,7 +28,7 @@ pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap { let pxh = (pixel_per_pt * size.y.to_f32()).round().max(1.0) as u32; let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap(); - canvas.fill(fill.into()); + canvas.fill(to_sk_color(fill)); let ts = sk::Transform::from_scale(pixel_per_pt, pixel_per_pt); render_frame(&mut canvas, State::new(size, ts, pixel_per_pt), frame); @@ -49,7 +48,7 @@ pub fn render_merged( ) -> sk::Pixmap { let pixmaps: Vec<_> = frames .iter() - .map(|frame| typst::export::render(frame, pixel_per_pt, frame_fill)) + .map(|frame| render(frame, pixel_per_pt, frame_fill)) .collect(); let padding = (pixel_per_pt * padding.to_f32()).round() as u32; @@ -59,7 +58,7 @@ pub fn render_merged( padding + pixmaps.iter().map(|pixmap| pixmap.height() + padding).sum::(); let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap(); - canvas.fill(padding_fill.into()); + canvas.fill(to_sk_color(padding_fill)); let [x, mut y] = [padding; 2]; for pixmap in pixmaps { @@ -173,18 +172,19 @@ fn render_frame(canvas: &mut sk::Pixmap, state: State, frame: &Frame) { /// Render a group frame with optional transform and clipping into the canvas. fn render_group(canvas: &mut sk::Pixmap, state: State, pos: Point, group: &GroupItem) { + let sk_transform = to_sk_transform(&group.transform); let state = match group.frame.kind() { - FrameKind::Soft => state.pre_translate(pos).pre_concat(group.transform.into()), + FrameKind::Soft => state.pre_translate(pos).pre_concat(sk_transform), FrameKind::Hard => state .pre_translate(pos) - .pre_concat(group.transform.into()) + .pre_concat(sk_transform) .pre_concat_container( state .transform .post_concat(state.container_transform.invert().unwrap()), ) - .pre_concat_container(Transform::translate(pos.x, pos.y).into()) - .pre_concat_container(group.transform.into()) + .pre_concat_container(to_sk_transform(&Transform::translate(pos.x, pos.y))) + .pre_concat_container(sk_transform) .with_size(group.frame.size()), }; @@ -456,8 +456,7 @@ fn write_bitmap( for x in 0..mw { for y in 0..mh { let alpha = bitmap.coverage[(y * mw + x) as usize]; - let color: sk::ColorU8 = sampler.sample((x, y)).into(); - + let color = to_sk_color_u8_without_alpha(sampler.sample((x, y))); pixmap.pixels_mut()[((y + 1) * (mw + 2) + (x + 1)) as usize] = sk::ColorU8::from_rgba( color.red(), @@ -502,8 +501,9 @@ fn write_bitmap( continue; } - let color: sk::ColorU8 = sampler.sample((x as _, y as _)).into(); - let color = bytemuck::cast(color.premultiply()); + let color = sampler.sample((x as _, y as _)); + let color = + bytemuck::cast(to_sk_color_u8_without_alpha(color).premultiply()); let pi = (y * cw + x) as usize; if cov == 255 { pixels[pi] = color; @@ -621,8 +621,8 @@ fn render_shape(canvas: &mut sk::Pixmap, state: State, shape: &Shape) -> Option< ); let stroke = sk::Stroke { width, - line_cap: line_cap.into(), - line_join: line_join.into(), + line_cap: to_sk_line_cap(*line_cap), + line_join: to_sk_line_join(*line_join), dash, miter_limit: miter_limit.get() as f32, }; @@ -740,34 +740,6 @@ fn scaled_texture(image: &Image, w: u32, h: u32) -> Option> { Some(Arc::new(pixmap)) } -impl From for sk::Transform { - fn from(transform: Transform) -> Self { - let Transform { sx, ky, kx, sy, tx, ty } = transform; - sk::Transform::from_row( - sx.get() as _, - ky.get() as _, - kx.get() as _, - sy.get() as _, - tx.to_f32(), - ty.to_f32(), - ) - } -} - -impl From for Transform { - fn from(value: sk::Transform) -> Self { - let sk::Transform { sx, ky, kx, sy, tx, ty } = value; - Self { - sx: Ratio::new(sx as _), - ky: Ratio::new(ky as _), - kx: Ratio::new(kx as _), - sy: Ratio::new(sy as _), - tx: Abs::raw(tx as _), - ty: Abs::raw(ty as _), - } - } -} - /// Trait for sampling of a paint, used as a generic /// abstraction over solid colors and gradients. trait PaintSampler: Copy { @@ -860,18 +832,16 @@ fn to_sk_paint<'a>( let mut pixmap = sk::Pixmap::new(width.max(1), height.max(1)).unwrap(); for x in 0..width { for y in 0..height { - let color: sk::Color = gradient - .sample_at( - ( - (x as f32 + offset.x.to_f32()) * scale.x.get() as f32, - (y as f32 + offset.y.to_f32()) * scale.y.get() as f32, - ), - (width as f32, height as f32), - ) - .into(); + let color = gradient.sample_at( + ( + (x as f32 + offset.x.to_f32()) * scale.x.get() as f32, + (y as f32 + offset.y.to_f32()) * scale.y.get() as f32, + ), + (width as f32, height as f32), + ); pixmap.pixels_mut()[(y * width + x) as usize] = - color.premultiply().to_color_u8(); + to_sk_color(color).premultiply().to_color_u8(); } } @@ -881,7 +851,7 @@ fn to_sk_paint<'a>( let mut sk_paint: sk::Paint<'_> = sk::Paint::default(); match paint { Paint::Solid(color) => { - sk_paint.set_color((*color).into()); + sk_paint.set_color(to_sk_color(*color)); sk_paint.anti_alias = true; } Paint::Gradient(gradient) => { @@ -925,31 +895,42 @@ fn to_sk_paint<'a>( sk_paint } -impl From for sk::Color { - fn from(color: Color) -> Self { - let [r, g, b, a] = color.to_rgba().to_vec4_u8(); - sk::Color::from_rgba8(r, g, b, a) +fn to_sk_color(color: Color) -> sk::Color { + let [r, g, b, a] = color.to_rgba().to_vec4_u8(); + sk::Color::from_rgba8(r, g, b, a) +} + +fn to_sk_color_u8_without_alpha(color: Color) -> sk::ColorU8 { + let [r, g, b, _] = color.to_rgba().to_vec4_u8(); + sk::ColorU8::from_rgba(r, g, b, 255) +} + +fn to_sk_line_cap(cap: LineCap) -> sk::LineCap { + match cap { + LineCap::Butt => sk::LineCap::Butt, + LineCap::Round => sk::LineCap::Round, + LineCap::Square => sk::LineCap::Square, } } -impl From<&LineCap> for sk::LineCap { - fn from(line_cap: &LineCap) -> Self { - match line_cap { - LineCap::Butt => sk::LineCap::Butt, - LineCap::Round => sk::LineCap::Round, - LineCap::Square => sk::LineCap::Square, - } +fn to_sk_line_join(join: LineJoin) -> sk::LineJoin { + match join { + LineJoin::Miter => sk::LineJoin::Miter, + LineJoin::Round => sk::LineJoin::Round, + LineJoin::Bevel => sk::LineJoin::Bevel, } } -impl From<&LineJoin> for sk::LineJoin { - fn from(line_join: &LineJoin) -> Self { - match line_join { - LineJoin::Miter => sk::LineJoin::Miter, - LineJoin::Round => sk::LineJoin::Round, - LineJoin::Bevel => sk::LineJoin::Bevel, - } - } +fn to_sk_transform(transform: &Transform) -> sk::Transform { + let Transform { sx, ky, kx, sy, tx, ty } = *transform; + sk::Transform::from_row( + sx.get() as _, + ky.get() as _, + kx.get() as _, + sy.get() as _, + tx.to_f32(), + ty.to_f32(), + ) } /// Allows to build tiny-skia paths from glyph outlines. @@ -989,13 +970,6 @@ impl AbsExt for Abs { } } -impl From for sk::ColorU8 { - fn from(value: Color) -> Self { - let [r, g, b, _] = value.to_rgba().to_vec4_u8(); - sk::ColorU8::from_rgba(r, g, b, 255) - } -} - // Alpha multiplication and blending are ported from: // https://skia.googlesource.com/skia/+/refs/heads/main/include/core/SkColorPriv.h diff --git a/crates/typst/Cargo.toml b/crates/typst/Cargo.toml index 1e820a68e..340639797 100644 --- a/crates/typst/Cargo.toml +++ b/crates/typst/Cargo.toml @@ -34,9 +34,7 @@ miniz_oxide = { workspace = true } once_cell = { workspace = true } palette = { workspace = true } pdf-writer = { workspace = true } -pixglyph = { workspace = true } regex = { workspace = true } -resvg = { workspace = true } roxmltree = { workspace = true } rustybuzz = { workspace = true } serde = { workspace = true } @@ -45,7 +43,6 @@ smallvec = { workspace = true } subsetter = { workspace = true } svg2pdf = { workspace = true } time = { workspace = true } -tiny-skia = { workspace = true } toml = { workspace = true } tracing = { workspace = true } ttf-parser = { workspace = true } diff --git a/crates/typst/src/export/mod.rs b/crates/typst/src/export/mod.rs index 4f7f8a704..ef24c97b0 100644 --- a/crates/typst/src/export/mod.rs +++ b/crates/typst/src/export/mod.rs @@ -1,9 +1,7 @@ //! Exporting into external formats. mod pdf; -mod render; mod svg; pub use self::pdf::{pdf, PdfPageLabel, PdfPageLabelStyle}; -pub use self::render::{render, render_merged}; pub use self::svg::{svg, svg_merged}; diff --git a/tests/Cargo.toml b/tests/Cargo.toml index a5a18a5b0..023776eac 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -9,6 +9,7 @@ publish = false [dev-dependencies] typst = { workspace = true } typst-library = { workspace = true } +typst-render = { workspace = true } clap = { workspace = true } comemo = { workspace = true } ecow = { workspace = true } diff --git a/tests/src/benches.rs b/tests/src/benches.rs index 8d74ca9c3..666bb8a01 100644 --- a/tests/src/benches.rs +++ b/tests/src/benches.rs @@ -90,7 +90,7 @@ fn bench_render(iai: &mut Iai) { let world = BenchWorld::new(); let mut tracer = Tracer::new(); let document = typst::compile(&world, &mut tracer).unwrap(); - iai.run(|| typst::export::render(&document.pages[0], 1.0, Color::WHITE)) + iai.run(|| typst_render::render(&document.pages[0], 1.0, Color::WHITE)) } struct BenchWorld { diff --git a/tests/src/tests.rs b/tests/src/tests.rs index d974b7313..07dba1778 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -26,7 +26,7 @@ use typst::eval::{ eco_format, func, Bytes, Datetime, Library, NoneValue, Repr, Tracer, Value, }; use typst::font::{Font, FontBook}; -use typst::geom::{Abs, Color, Smart}; +use typst::geom::{Abs, Color, Smart, Transform}; use typst::syntax::{FileId, PackageVersion, Source, SyntaxNode, VirtualPath}; use typst::{World, WorldExt}; use typst_library::layout::{Margin, PageElem}; @@ -906,7 +906,7 @@ fn render(frames: &[Frame]) -> sk::Pixmap { } } - let mut pixmap = typst::export::render_merged( + let mut pixmap = typst_render::render_merged( frames, pixel_per_pt, Color::WHITE, @@ -932,7 +932,7 @@ fn render_links(canvas: &mut sk::Pixmap, ts: sk::Transform, frame: &Frame) { let ts = ts.pre_translate(pos.x.to_pt() as f32, pos.y.to_pt() as f32); match *item { FrameItem::Group(ref group) => { - let ts = ts.pre_concat(group.transform.into()); + let ts = ts.pre_concat(to_sk_transform(&group.transform)); render_links(canvas, ts, &group.frame); } FrameItem::Meta(Meta::Link(_), size) => { @@ -948,6 +948,18 @@ fn render_links(canvas: &mut sk::Pixmap, ts: sk::Transform, frame: &Frame) { } } +fn to_sk_transform(transform: &Transform) -> sk::Transform { + let Transform { sx, ky, kx, sy, tx, ty } = *transform; + sk::Transform::from_row( + sx.get() as _, + ky.get() as _, + kx.get() as _, + sy.get() as _, + tx.to_pt() as f32, + ty.to_pt() as f32, + ) +} + /// A Linear-feedback shift register using XOR as its shifting function. /// Can be used as PRNG. struct LinearShift(u64);