mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Refactor SVG export a bit
This commit is contained in:
parent
61e4ad6bba
commit
2ea451b83b
@ -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")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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};
|
||||||
|
@ -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
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user