Refactor SVG export a bit

This commit is contained in:
Laurenz 2023-08-08 12:59:21 +02:00
parent 61e4ad6bba
commit 2ea451b83b
5 changed files with 594 additions and 533 deletions

View File

@ -95,19 +95,14 @@ pub fn compile_once(
Ok(()) Ok(())
} }
enum ExportImageFormat {
Png,
Svg,
}
/// Export into the target format. /// Export into the target format.
fn export(document: &Document, command: &CompileCommand) -> StrResult<()> { fn export(document: &Document, command: &CompileCommand) -> StrResult<()> {
match command.output().extension() { match command.output().extension() {
Some(ext) if ext.eq_ignore_ascii_case("png") => { Some(ext) if ext.eq_ignore_ascii_case("png") => {
export_image(document, command, ExportImageFormat::Png) export_image(document, command, ImageExportFormat::Png)
} }
Some(ext) if ext.eq_ignore_ascii_case("svg") => { Some(ext) if ext.eq_ignore_ascii_case("svg") => {
export_image(document, command, ExportImageFormat::Svg) export_image(document, command, ImageExportFormat::Svg)
} }
_ => export_pdf(document, command), _ => export_pdf(document, command),
} }
@ -121,18 +116,24 @@ fn export_pdf(document: &Document, command: &CompileCommand) -> StrResult<()> {
Ok(()) Ok(())
} }
/// An image format to export in.
enum ImageExportFormat {
Png,
Svg,
}
/// Export to one or multiple PNGs. /// Export to one or multiple PNGs.
fn export_image( fn export_image(
document: &Document, document: &Document,
command: &CompileCommand, command: &CompileCommand,
fmt: ExportImageFormat, fmt: ImageExportFormat,
) -> StrResult<()> { ) -> StrResult<()> {
// Determine whether we have a `{n}` numbering. // Determine whether we have a `{n}` numbering.
let output = command.output(); let output = command.output();
let string = output.to_str().unwrap_or_default(); let string = output.to_str().unwrap_or_default();
let numbered = string.contains("{n}"); let numbered = string.contains("{n}");
if !numbered && document.pages.len() > 1 { if !numbered && document.pages.len() > 1 {
bail!("cannot export multiple PNGs without `{{n}}` in output path"); bail!("cannot export multiple images without `{{n}}` in output path");
} }
// Find a number width that accommodates all pages. For instance, the // Find a number width that accommodates all pages. For instance, the
@ -149,13 +150,13 @@ fn export_image(
output.as_path() output.as_path()
}; };
match fmt { match fmt {
ExportImageFormat::Png => { ImageExportFormat::Png => {
let pixmap = let pixmap =
typst::export::render(frame, command.ppi / 72.0, Color::WHITE); typst::export::render(frame, command.ppi / 72.0, Color::WHITE);
pixmap.save_png(path).map_err(|_| "failed to write PNG file")?; pixmap.save_png(path).map_err(|_| "failed to write PNG file")?;
} }
ExportImageFormat::Svg => { ImageExportFormat::Svg => {
let svg = typst::export::svg_frame(frame); let svg = typst::export::svg(frame);
fs::write(path, svg).map_err(|_| "failed to write SVG file")?; fs::write(path, svg).map_err(|_| "failed to write SVG file")?;
} }
} }

View File

@ -5,5 +5,5 @@ mod render;
mod svg; mod svg;
pub use self::pdf::pdf; pub use self::pdf::pdf;
pub use self::render::render; pub use self::render::{render, render_merged};
pub use self::svg::{svg, svg_frame}; pub use self::svg::{svg, svg_merged};

View File

@ -37,6 +37,47 @@ pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap {
canvas canvas
} }
/// Export multiple frames into a single raster image.
///
/// The padding will be added around and between the individual frames.
pub fn render_merged(
frames: &[Frame],
pixel_per_pt: f32,
frame_fill: Color,
padding: Abs,
padding_fill: Color,
) -> sk::Pixmap {
let pixmaps: Vec<_> = frames
.iter()
.map(|frame| typst::export::render(frame, pixel_per_pt, frame_fill))
.collect();
let padding = (pixel_per_pt * padding.to_f32()).round() as u32;
let pxw =
2 * padding + pixmaps.iter().map(sk::Pixmap::width).max().unwrap_or_default();
let pxh =
padding + pixmaps.iter().map(|pixmap| pixmap.height() + padding).sum::<u32>();
let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap();
canvas.fill(padding_fill.into());
let [x, mut y] = [padding; 2];
for pixmap in pixmaps {
canvas.draw_pixmap(
x as i32,
y as i32,
pixmap.as_ref(),
&sk::PixmapPaint::default(),
sk::Transform::identity(),
None,
);
y += pixmap.height() + padding;
}
canvas
}
/// Render a frame into the canvas. /// Render a frame into the canvas.
fn render_frame( fn render_frame(
canvas: &mut sk::Pixmap, canvas: &mut sk::Pixmap,

File diff suppressed because it is too large Load Diff

View File

@ -429,7 +429,7 @@ fn test(
fs::create_dir_all(png_path.parent().unwrap()).unwrap(); fs::create_dir_all(png_path.parent().unwrap()).unwrap();
canvas.save_png(png_path).unwrap(); canvas.save_png(png_path).unwrap();
let svg = typst::export::svg(&document); let svg = typst::export::svg_merged(&document.pages, Abs::pt(5.0));
fs::create_dir_all(svg_path.parent().unwrap()).unwrap(); fs::create_dir_all(svg_path.parent().unwrap()).unwrap();
std::fs::write(svg_path, svg).unwrap(); std::fs::write(svg_path, svg).unwrap();
@ -898,42 +898,33 @@ fn test_spans_impl(output: &mut String, node: &SyntaxNode, within: Range<u64>) -
/// Draw all frames into one image with padding in between. /// Draw all frames into one image with padding in between.
fn render(frames: &[Frame]) -> sk::Pixmap { fn render(frames: &[Frame]) -> sk::Pixmap {
let pixel_per_pt = 2.0; let pixel_per_pt = 2.0;
let pixmaps: Vec<_> = frames let padding = Abs::pt(5.0);
.iter()
.map(|frame| {
let limit = Abs::cm(100.0);
if frame.width() > limit || frame.height() > limit {
panic!("overlarge frame: {:?}", frame.size());
}
typst::export::render(frame, pixel_per_pt, Color::WHITE)
})
.collect();
let pad = (5.0 * pixel_per_pt).round() as u32; for frame in frames {
let pxw = 2 * pad + pixmaps.iter().map(sk::Pixmap::width).max().unwrap_or_default(); let limit = Abs::cm(100.0);
let pxh = pad + pixmaps.iter().map(|pixmap| pixmap.height() + pad).sum::<u32>(); if frame.width() > limit || frame.height() > limit {
panic!("overlarge frame: {:?}", frame.size());
let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap(); }
canvas.fill(sk::Color::BLACK);
let [x, mut y] = [pad; 2];
for (frame, mut pixmap) in frames.iter().zip(pixmaps) {
let ts = sk::Transform::from_scale(pixel_per_pt, pixel_per_pt);
render_links(&mut pixmap, ts, frame);
canvas.draw_pixmap(
x as i32,
y as i32,
pixmap.as_ref(),
&sk::PixmapPaint::default(),
sk::Transform::identity(),
None,
);
y += pixmap.height() + pad;
} }
canvas let mut pixmap = typst::export::render_merged(
frames,
pixel_per_pt,
Color::WHITE,
padding,
Color::BLACK,
);
let padding = (pixel_per_pt * padding.to_pt() as f32).round();
let [x, mut y] = [padding; 2];
for frame in frames {
let ts =
sk::Transform::from_scale(pixel_per_pt, pixel_per_pt).post_translate(x, y);
render_links(&mut pixmap, ts, frame);
y += (pixel_per_pt * frame.height().to_pt() as f32).round().max(1.0) + padding;
}
pixmap
} }
/// Draw extra boxes for links so we can see whether they are there. /// Draw extra boxes for links so we can see whether they are there.