mod image;
mod paint;
mod shape;
mod text;
use tiny_skia as sk;
use typst::layout::{
Abs, Axes, Frame, FrameItem, FrameKind, GroupItem, Page, Point, Size, Transform,
};
use typst::model::Document;
use typst::visualize::{Color, Geometry, Paint};
#[typst_macros::time(name = "render")]
pub fn render(page: &Page, pixel_per_pt: f32) -> sk::Pixmap {
let size = page.frame.size();
let pxw = (pixel_per_pt * size.x.to_f32()).round().max(1.0) as u32;
let pxh = (pixel_per_pt * size.y.to_f32()).round().max(1.0) as u32;
let ts = sk::Transform::from_scale(pixel_per_pt, pixel_per_pt);
let state = State::new(size, ts, pixel_per_pt);
let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap();
if let Some(fill) = page.fill_or_white() {
if let Paint::Solid(color) = fill {
canvas.fill(paint::to_sk_color(color));
} else {
let rect = Geometry::Rect(page.frame.size()).filled(fill);
shape::render_shape(&mut canvas, state, &rect);
}
}
render_frame(&mut canvas, state, &page.frame);
canvas
}
pub fn render_merged(
document: &Document,
pixel_per_pt: f32,
gap: Abs,
fill: Option<Color>,
) -> sk::Pixmap {
let pixmaps: Vec<_> =
document.pages.iter().map(|page| render(page, pixel_per_pt)).collect();
let gap = (pixel_per_pt * gap.to_f32()).round() as u32;
let pxw = pixmaps.iter().map(sk::Pixmap::width).max().unwrap_or_default();
let pxh = pixmaps.iter().map(|pixmap| pixmap.height()).sum::<u32>()
+ gap * pixmaps.len().saturating_sub(1) as u32;
let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap();
if let Some(fill) = fill {
canvas.fill(paint::to_sk_color(fill));
}
let mut y = 0;
for pixmap in pixmaps {
canvas.draw_pixmap(
0,
y as i32,
pixmap.as_ref(),
&sk::PixmapPaint::default(),
sk::Transform::identity(),
None,
);
y += pixmap.height() + gap;
}
canvas
}
#[derive(Clone, Copy, Default)]
struct State<'a> {
transform: sk::Transform,
container_transform: sk::Transform,
mask: Option<&'a sk::Mask>,
pixel_per_pt: f32,
size: Size,
}
impl<'a> State<'a> {
fn new(size: Size, transform: sk::Transform, pixel_per_pt: f32) -> Self {
Self {
size,
transform,
container_transform: transform,
pixel_per_pt,
..Default::default()
}
}
fn pre_translate(self, pos: Point) -> Self {
Self {
transform: self.transform.pre_translate(pos.x.to_f32(), pos.y.to_f32()),
..self
}
}
fn pre_scale(self, scale: Axes<Abs>) -> Self {
Self {
transform: self.transform.pre_scale(scale.x.to_f32(), scale.y.to_f32()),
..self
}
}
fn pre_concat(self, transform: sk::Transform) -> Self {
Self {
transform: self.transform.pre_concat(transform),
..self
}
}
fn with_mask(self, mask: Option<&sk::Mask>) -> State<'_> {
if mask.is_some() {
State { mask, ..self }
} else {
State { mask: None, ..self }
}
}
fn with_size(self, size: Size) -> Self {
Self { size, ..self }
}
fn pre_concat_container(self, transform: sk::Transform) -> Self {
Self {
container_transform: self.container_transform.pre_concat(transform),
..self
}
}
}
fn render_frame(canvas: &mut sk::Pixmap, state: State, frame: &Frame) {
for (pos, item) in frame.items() {
match item {
FrameItem::Group(group) => {
render_group(canvas, state, *pos, group);
}
FrameItem::Text(text) => {
text::render_text(canvas, state.pre_translate(*pos), text);
}
FrameItem::Shape(shape, _) => {
shape::render_shape(canvas, state.pre_translate(*pos), shape);
}
FrameItem::Image(image, size, _) => {
image::render_image(canvas, state.pre_translate(*pos), image, *size);
}
FrameItem::Link(_, _) => {}
FrameItem::Tag(_) => {}
}
}
}
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(sk_transform),
FrameKind::Hard => state
.pre_translate(pos)
.pre_concat(sk_transform)
.pre_concat_container(
state
.transform
.post_concat(state.container_transform.invert().unwrap()),
)
.pre_concat_container(to_sk_transform(&Transform::translate(pos.x, pos.y)))
.pre_concat_container(sk_transform)
.with_size(group.frame.size()),
};
let mut mask = state.mask;
let storage;
if let Some(clip_path) = group.clip_path.as_ref() {
if let Some(path) = shape::convert_path(clip_path)
.and_then(|path| path.transform(state.transform))
{
if let Some(mask) = mask {
let mut mask = mask.clone();
mask.intersect_path(
&path,
sk::FillRule::default(),
false,
sk::Transform::default(),
);
storage = mask;
} else {
let pxw = canvas.width();
let pxh = canvas.height();
let Some(mut mask) = sk::Mask::new(pxw, pxh) else {
return;
};
mask.fill_path(
&path,
sk::FillRule::default(),
false,
sk::Transform::default(),
);
storage = mask;
};
mask = Some(&storage);
}
}
render_frame(canvas, state.with_mask(mask), &group.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_f32(),
ty.to_f32(),
)
}
trait AbsExt {
fn to_f32(self) -> f32;
}
impl AbsExt for Abs {
fn to_f32(self) -> f32 {
self.to_pt() as f32
}
}