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(())
}
enum ExportImageFormat {
Png,
Svg,
}
/// Export into the target format.
fn export(document: &Document, command: &CompileCommand) -> StrResult<()> {
match command.output().extension() {
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") => {
export_image(document, command, ExportImageFormat::Svg)
export_image(document, command, ImageExportFormat::Svg)
}
_ => export_pdf(document, command),
}
@ -121,18 +116,24 @@ fn export_pdf(document: &Document, command: &CompileCommand) -> StrResult<()> {
Ok(())
}
/// An image format to export in.
enum ImageExportFormat {
Png,
Svg,
}
/// Export to one or multiple PNGs.
fn export_image(
document: &Document,
command: &CompileCommand,
fmt: ExportImageFormat,
fmt: ImageExportFormat,
) -> StrResult<()> {
// Determine whether we have a `{n}` numbering.
let output = command.output();
let string = output.to_str().unwrap_or_default();
let numbered = string.contains("{n}");
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
@ -149,13 +150,13 @@ fn export_image(
output.as_path()
};
match fmt {
ExportImageFormat::Png => {
ImageExportFormat::Png => {
let pixmap =
typst::export::render(frame, command.ppi / 72.0, Color::WHITE);
pixmap.save_png(path).map_err(|_| "failed to write PNG file")?;
}
ExportImageFormat::Svg => {
let svg = typst::export::svg_frame(frame);
ImageExportFormat::Svg => {
let svg = typst::export::svg(frame);
fs::write(path, svg).map_err(|_| "failed to write SVG file")?;
}
}

View File

@ -5,5 +5,5 @@ mod render;
mod svg;
pub use self::pdf::pdf;
pub use self::render::render;
pub use self::svg::{svg, svg_frame};
pub use self::render::{render, render_merged};
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
}
/// 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.
fn render_frame(
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();
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();
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.
fn render(frames: &[Frame]) -> sk::Pixmap {
let pixel_per_pt = 2.0;
let pixmaps: Vec<_> = frames
.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 padding = Abs::pt(5.0);
let pad = (5.0 * pixel_per_pt).round() as u32;
let pxw = 2 * pad + pixmaps.iter().map(sk::Pixmap::width).max().unwrap_or_default();
let pxh = pad + pixmaps.iter().map(|pixmap| pixmap.height() + pad).sum::<u32>();
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;
for frame in frames {
let limit = Abs::cm(100.0);
if frame.width() > limit || frame.height() > limit {
panic!("overlarge frame: {:?}", frame.size());
}
}
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.