summaryrefslogtreecommitdiff
path: root/crates/typst-render/src/lib.rs
diff options
context:
space:
mode:
authorSébastien d'Herbais de Thun <sebastien.d.herbais@gmail.com>2023-11-24 15:46:20 +0100
committerGitHub <noreply@github.com>2023-11-24 15:46:20 +0100
commit1756718bab3055597723a9b433419ff07e6b7f02 (patch)
treea06a7a381c994d762c298ec66903db0718877806 /crates/typst-render/src/lib.rs
parent3d2f1d2d6cc34fa64c56abd335dd14ea4c932a6c (diff)
Gradient Part 6 - Pattern fills (#2740)
Diffstat (limited to 'crates/typst-render/src/lib.rs')
-rw-r--r--crates/typst-render/src/lib.rs151
1 files changed, 131 insertions, 20 deletions
diff --git a/crates/typst-render/src/lib.rs b/crates/typst-render/src/lib.rs
index 251f647a..5c1b8482 100644
--- a/crates/typst-render/src/lib.rs
+++ b/crates/typst-render/src/lib.rs
@@ -15,8 +15,8 @@ use typst::layout::{
};
use typst::text::{Font, TextItem};
use typst::visualize::{
- Color, FixedStroke, Geometry, Gradient, GradientRelative, Image, ImageKind, LineCap,
- LineJoin, Paint, Path, PathItem, RasterFormat, Shape,
+ Color, FixedStroke, Geometry, Gradient, Image, ImageKind, LineCap, LineJoin, Paint,
+ Path, PathItem, Pattern, RasterFormat, RelativeTo, Shape,
};
use usvg::{NodeExt, TreeParsing};
@@ -433,7 +433,17 @@ fn render_outline_glyph(
write_bitmap(canvas, &bitmap, &state, sampler)?;
}
Paint::Solid(color) => {
- write_bitmap(canvas, &bitmap, &state, *color)?;
+ write_bitmap(
+ canvas,
+ &bitmap,
+ &state,
+ to_sk_color_u8_without_alpha(*color).premultiply(),
+ )?;
+ }
+ Paint::Pattern(pattern) => {
+ let pixmap = render_pattern_frame(&state, pattern);
+ let sampler = PatternSampler::new(pattern, &pixmap, &state, true);
+ write_bitmap(canvas, &bitmap, &state, sampler)?;
}
}
@@ -458,7 +468,7 @@ fn write_bitmap<S: PaintSampler>(
for x in 0..mw {
for y in 0..mh {
let alpha = bitmap.coverage[(y * mw + x) as usize];
- let color = to_sk_color_u8_without_alpha(sampler.sample((x, y)));
+ let color = sampler.sample((x, y));
pixmap.pixels_mut()[((y + 1) * (mw + 2) + (x + 1)) as usize] =
sk::ColorU8::from_rgba(
color.red(),
@@ -504,8 +514,7 @@ fn write_bitmap<S: PaintSampler>(
}
let color = sampler.sample((x as _, y as _));
- let color =
- bytemuck::cast(to_sk_color_u8_without_alpha(color).premultiply());
+ let color = bytemuck::cast(color);
let pi = (y * cw + x) as usize;
if cov == 255 {
pixels[pi] = color;
@@ -746,11 +755,22 @@ fn scaled_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
/// abstraction over solid colors and gradients.
trait PaintSampler: Copy {
/// Sample the color at the `pos` in the pixmap.
- fn sample(self, pos: (u32, u32)) -> Color;
+ fn sample(self, pos: (u32, u32)) -> sk::PremultipliedColorU8;
+
+ /// Write the sampler to a pixmap.
+ fn write_to_pixmap(self, canvas: &mut sk::Pixmap) {
+ let width = canvas.width();
+ for x in 0..canvas.width() {
+ for y in 0..canvas.height() {
+ let color = self.sample((x, y));
+ canvas.pixels_mut()[(y * width + x) as usize] = color;
+ }
+ }
+ }
}
-impl PaintSampler for Color {
- fn sample(self, _: (u32, u32)) -> Color {
+impl PaintSampler for sk::PremultipliedColorU8 {
+ fn sample(self, _: (u32, u32)) -> sk::PremultipliedColorU8 {
self
}
}
@@ -775,13 +795,13 @@ impl<'a> GradientSampler<'a> {
) -> Self {
let relative = gradient.unwrap_relative(on_text);
let container_size = match relative {
- GradientRelative::Self_ => item_size,
- GradientRelative::Parent => state.size,
+ RelativeTo::Self_ => item_size,
+ RelativeTo::Parent => state.size,
};
let fill_transform = match relative {
- GradientRelative::Self_ => sk::Transform::identity(),
- GradientRelative::Parent => state.container_transform.invert().unwrap(),
+ RelativeTo::Self_ => sk::Transform::identity(),
+ RelativeTo::Parent => state.container_transform.invert().unwrap(),
};
Self {
@@ -794,16 +814,69 @@ impl<'a> GradientSampler<'a> {
impl PaintSampler for GradientSampler<'_> {
/// Samples a single point in a glyph.
- fn sample(self, (x, y): (u32, u32)) -> Color {
+ fn sample(self, (x, y): (u32, u32)) -> sk::PremultipliedColorU8 {
// Compute the point in the gradient's coordinate space.
let mut point = sk::Point { x: x as f32, y: y as f32 };
self.transform_to_parent.map_point(&mut point);
// Sample the gradient
- self.gradient.sample_at(
+ to_sk_color_u8_without_alpha(self.gradient.sample_at(
(point.x, point.y),
(self.container_size.x.to_f32(), self.container_size.y.to_f32()),
- )
+ ))
+ .premultiply()
+ }
+}
+
+/// State used when sampling patterns for text.
+///
+/// It caches the inverse transform to the parent, so that we can
+/// reuse it instead of recomputing it for each pixel.
+#[derive(Clone, Copy)]
+struct PatternSampler<'a> {
+ size: Size,
+ transform_to_parent: sk::Transform,
+ pixmap: &'a sk::Pixmap,
+ pixel_per_pt: f32,
+}
+
+impl<'a> PatternSampler<'a> {
+ fn new(
+ pattern: &'a Pattern,
+ pixmap: &'a sk::Pixmap,
+ state: &State,
+ on_text: bool,
+ ) -> Self {
+ let relative = pattern.unwrap_relative(on_text);
+ let fill_transform = match relative {
+ RelativeTo::Self_ => sk::Transform::identity(),
+ RelativeTo::Parent => state.container_transform.invert().unwrap(),
+ };
+
+ Self {
+ pixmap,
+ size: (pattern.size_abs() + pattern.spacing_abs())
+ * state.pixel_per_pt as f64,
+ transform_to_parent: fill_transform,
+ pixel_per_pt: state.pixel_per_pt,
+ }
+ }
+}
+
+impl PaintSampler for PatternSampler<'_> {
+ /// Samples a single point in a glyph.
+ fn sample(self, (x, y): (u32, u32)) -> sk::PremultipliedColorU8 {
+ // Compute the point in the pattern's coordinate space.
+ let mut point = sk::Point { x: x as f32, y: y as f32 };
+ self.transform_to_parent.map_point(&mut point);
+
+ let x =
+ (point.x * self.pixel_per_pt).rem_euclid(self.size.x.to_f32()).floor() as u32;
+ let y =
+ (point.y * self.pixel_per_pt).rem_euclid(self.size.y.to_f32()).floor() as u32;
+
+ // Sample the pattern
+ self.pixmap.pixel(x, y).unwrap()
}
}
@@ -859,13 +932,13 @@ fn to_sk_paint<'a>(
Paint::Gradient(gradient) => {
let relative = gradient.unwrap_relative(on_text);
let container_size = match relative {
- GradientRelative::Self_ => item_size,
- GradientRelative::Parent => state.size,
+ RelativeTo::Self_ => item_size,
+ RelativeTo::Parent => state.size,
};
let fill_transform = match relative {
- GradientRelative::Self_ => fill_transform.unwrap_or_default(),
- GradientRelative::Parent => state
+ RelativeTo::Self_ => fill_transform.unwrap_or_default(),
+ RelativeTo::Parent => state
.container_transform
.post_concat(state.transform.invert().unwrap()),
};
@@ -892,11 +965,49 @@ fn to_sk_paint<'a>(
sk_paint.anti_alias = gradient.anti_alias();
}
+ Paint::Pattern(pattern) => {
+ let relative = pattern.unwrap_relative(on_text);
+
+ let fill_transform = match relative {
+ RelativeTo::Self_ => fill_transform.unwrap_or_default(),
+ RelativeTo::Parent => state
+ .container_transform
+ .post_concat(state.transform.invert().unwrap()),
+ };
+
+ let canvas = render_pattern_frame(&state, pattern);
+ *pixmap = Some(Arc::new(canvas));
+
+ // Create the shader
+ sk_paint.shader = sk::Pattern::new(
+ pixmap.as_ref().unwrap().as_ref().as_ref(),
+ sk::SpreadMode::Repeat,
+ sk::FilterQuality::Nearest,
+ 1.0,
+ fill_transform
+ .pre_scale(1.0 / state.pixel_per_pt, 1.0 / state.pixel_per_pt),
+ );
+ }
}
sk_paint
}
+fn render_pattern_frame(state: &State, pattern: &Pattern) -> sk::Pixmap {
+ let size = pattern.size_abs() + pattern.spacing_abs();
+ let mut canvas = sk::Pixmap::new(
+ (size.x.to_f32() * state.pixel_per_pt).round() as u32,
+ (size.y.to_f32() * state.pixel_per_pt).round() as u32,
+ )
+ .unwrap();
+
+ // Render the pattern into a new canvas.
+ let ts = sk::Transform::from_scale(state.pixel_per_pt, state.pixel_per_pt);
+ let temp_state = State::new(pattern.size_abs(), ts, state.pixel_per_pt);
+ render_frame(&mut canvas, temp_state, pattern.frame());
+ canvas
+}
+
fn to_sk_color(color: Color) -> sk::Color {
let [r, g, b, a] = color.to_rgb().to_vec4_u8();
sk::Color::from_rgba8(r, g, b, a)