add a simple PDF export

This commit is contained in:
Sebastian Köln 2019-06-15 17:50:25 +03:00
parent 636ff6dbf8
commit 4039658155
7 changed files with 430 additions and 1 deletions

View File

@ -10,10 +10,12 @@ members = [
"examples/canvas_moire", "examples/canvas_moire",
"examples/canvas_text", "examples/canvas_text",
"examples/lottie_basic", "examples/lottie_basic",
"examples/svg2pdf",
"geometry", "geometry",
"gl", "gl",
"gpu", "gpu",
"lottie", "lottie",
"pdf",
"renderer", "renderer",
"simd", "simd",
"svg", "svg",

View File

@ -0,0 +1,12 @@
[package]
name = "svg2pdf"
version = "0.1.0"
authors = ["Sebastian Köln <sebk@rynx.org>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pathfinder_svg = { path = "../../svg" }
pathfinder_pdf = { path = "../../pdf" }
usvg = "*"

View File

@ -0,0 +1,20 @@
use std::fs::File;
use std::io::{Read, BufWriter};
use std::error::Error;
use pathfinder_svg::BuiltSVG;
use pathfinder_pdf::make_pdf;
use usvg::{Tree, Options};
fn main() -> Result<(), Box<Error>> {
let mut args = std::env::args().skip(1);
let input = args.next().expect("no input given");
let output = args.next().expect("no output given");
let mut data = Vec::new();
File::open(input)?.read_to_end(&mut data)?;
let svg = BuiltSVG::from_tree(Tree::from_data(&data, &Options::default()).unwrap());
make_pdf(BufWriter::new(File::create(output)?), &svg.scene);
Ok(())
}

10
pdf/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "pathfinder_pdf"
version = "0.1.0"
authors = ["Sebastian Köln <sebk@rynx.org>"]
edition = "2018"
[dependencies]
pathfinder_renderer = { path = "../renderer" }
pathfinder_geometry = { path = "../geometry" }
deflate = "*"

72
pdf/src/lib.rs Normal file
View File

@ -0,0 +1,72 @@
use pathfinder_renderer::{scene::Scene};
use pathfinder_geometry::{outline::Outline, color::ColorF, segment::{Segment, SegmentKind}, basic::{vector::Vector2F, rect::RectF}};
use std::io::Write;
mod pdf;
use pdf::Pdf;
pub struct PdfBuilder {
pdf: Pdf
}
impl PdfBuilder {
pub fn new() -> PdfBuilder {
PdfBuilder {
pdf: Pdf::new()
}
}
pub fn add_scene(&mut self, scene: &Scene) {
let view_box = scene.view_box();
self.pdf.add_page(view_box.size());
let height = view_box.size().y();
let tr = |v: Vector2F| -> Vector2F {
let r = v - view_box.origin();
Vector2F::new(r.x(), height - r.y())
};
for (paint, outline) in scene.paths() {
self.pdf.set_fill_color(paint.color);
for contour in outline.contours() {
for (segment_index, segment) in contour.iter().enumerate() {
if segment_index == 0 {
self.pdf.move_to(tr(segment.baseline.from()));
}
match segment.kind {
SegmentKind::None => {}
SegmentKind::Line => self.pdf.line_to(tr(segment.baseline.to())),
SegmentKind::Quadratic => {
let current = segment.baseline.from();
let c = segment.ctrl.from();
let p = segment.baseline.to();
let c1 = Vector2F::splat(2./3.) * c + Vector2F::splat(1./3.) * current;
let c2 = Vector2F::splat(2./3.) * c + Vector2F::splat(1./3.) * p;
self.pdf.cubic_to(c1, c2, p);
}
SegmentKind::Cubic => self.pdf.cubic_to(tr(segment.ctrl.from()), tr(segment.ctrl.to()), tr(segment.baseline.to()))
}
}
if contour.is_closed() {
self.pdf.close();
}
}
// closes implicitly
self.pdf.fill();
}
}
pub fn write<W: Write>(mut self, out: W) {
self.pdf.write_to(out);
}
}
pub fn make_pdf<W: Write>(mut writer: W, scene: &Scene) {
let mut pdf = PdfBuilder::new();
pdf.add_scene(scene);
pdf.write(writer);
}

293
pdf/src/pdf.rs Normal file
View File

@ -0,0 +1,293 @@
//! This is a heavily modified version of the pdfpdf crate by Benjamin Kimock <kimockb@gmail.com> (aka. saethlin)
use pathfinder_geometry::basic::{vector::Vector2F, rect::RectF};
use pathfinder_geometry::color::ColorU;
use std::io::{self, Write, Cursor, Seek};
use deflate::Compression;
struct Counter<T> {
inner: T,
count: u64
}
impl<T> Counter<T> {
pub fn new(inner: T) -> Counter<T> {
Counter {
inner,
count: 0
}
}
pub fn pos(&self) -> u64 {
self.count
}
pub fn into_inner(self) -> T {
self.inner
}
}
impl<W: Write> Write for Counter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self.inner.write(buf) {
Ok(n) => {
self.count += n as u64;
Ok(n)
},
Err(e) => Err(e)
}
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.inner.write_all(buf)?;
self.count += buf.len() as u64;
Ok(())
}
}
/// Represents a PDF internal object
struct PdfObject {
contents: Vec<u8>,
is_page: bool,
is_xobject: bool,
offset: Option<u64>,
}
/// The top-level struct that represents a (partially) in-memory PDF file
pub struct Pdf {
page_buffer: Vec<u8>,
objects: Vec<PdfObject>,
page_size: Option<Vector2F>,
compression: Option<Compression>,
}
impl Default for Pdf {
fn default() -> Self {
Self::new()
}
}
impl Pdf {
/// Create a new blank PDF document
#[inline]
pub fn new() -> Self {
Self {
page_buffer: Vec::new(),
objects: vec![
PdfObject {
contents: Vec::new(),
is_page: false,
is_xobject: false,
offset: None,
},
PdfObject {
contents: Vec::new(),
is_page: false,
is_xobject: false,
offset: None,
},
],
page_size: None,
compression: Some(Compression::Fast)
}
}
fn add_object(&mut self, data: Vec<u8>, is_page: bool, is_xobject: bool) -> usize {
self.objects.push(PdfObject {
contents: data,
is_page,
is_xobject,
offset: None,
});
self.objects.len()
}
/// Sets the compression level for this document
/// Calls to this method do not affect data produced by operations before the last .add_page
#[inline]
pub fn set_compression(&mut self, compression: Option<Compression>) {
self.compression = compression;
}
/// Set the PDF clipping box for the current page
#[inline]
pub fn set_clipping_box(&mut self, rect: RectF) {
let origin = rect.origin();
let size = rect.size();
writeln!(self.page_buffer, "{} {} {} {} re W n",
origin.x(),
origin.y(),
size.x(),
size.y()
);
}
/// Set the current line width
#[inline]
pub fn set_line_width(&mut self, width: f32) {
writeln!(self.page_buffer, "{} w", width);
}
/// Set the color for all subsequent drawing operations
#[inline]
pub fn set_stroke_color(&mut self, color: ColorU) {
let norm = |color| f32::from(color) / 255.0;
writeln!(self.page_buffer, "{} {} {} RG",
norm(color.r),
norm(color.g),
norm(color.b)
);
}
/// Set the color for all subsequent drawing operations
#[inline]
pub fn set_fill_color(&mut self, color: ColorU) {
let norm = |color| f32::from(color) / 255.0;
writeln!(self.page_buffer, "{} {} {} rg",
norm(color.r),
norm(color.g),
norm(color.b)
);
}
/// Move to a new page in the PDF document
#[inline]
pub fn add_page(&mut self, size: Vector2F) {
// Compress and write out the previous page if it exists
if !self.page_buffer.is_empty() {
self.end_page();
self.page_buffer.clear();
}
self.page_buffer
.extend("/DeviceRGB cs /DeviceRGB CS\n1 j 1 J\n".bytes());
self.page_size = Some(size);
}
pub fn move_to(&mut self, p: Vector2F) {
writeln!(self.page_buffer, "{} {} m", p.x(), p.y());
}
pub fn line_to(&mut self, p: Vector2F) {
writeln!(self.page_buffer, "{} {} l", p.x(), p.y());
}
pub fn cubic_to(&mut self, c1: Vector2F, c2: Vector2F, p: Vector2F) {
writeln!(self.page_buffer, "{} {} {} {} {} {} c", c1.x(), c1.y(), c2.x(), c2.y(), p.x(), p.y());
}
pub fn fill(&mut self) {
writeln!(self.page_buffer, "f");
}
pub fn stroke(&mut self) {
writeln!(self.page_buffer, "s");
}
pub fn close(&mut self) {
writeln!(self.page_buffer, "h");
}
/// Dump a page out to disk
fn end_page(&mut self) {
let size = match self.page_size.take() {
Some(size) => size,
None => return // no page started
};
let page_stream = if let Some(level) = self.compression {
let compressed = deflate::deflate_bytes_zlib_conf(&self.page_buffer, level);
let mut page = format!(
"<< /Length {} /Filter [/FlateDecode] >>\nstream\n",
compressed.len()
)
.into_bytes();
page.extend_from_slice(&compressed);
page.extend(b"endstream\n");
page
} else {
let mut page = Vec::new();
page.extend(format!("<< /Length {} >>\nstream\n", self.page_buffer.len()).bytes());
page.extend(&self.page_buffer);
page.extend(b"endstream\n");
page
};
// Create the stream object for this page
let stream_object_id = self.add_object(page_stream, false, false);
// Create the page object, which describes settings for the whole page
let mut page_object = b"<< /Type /Page\n \
/Parent 2 0 R\n \
/Resources <<\n"
.to_vec();
for (idx, obj) in self.objects.iter().enumerate().filter(|&(_, o)| o.is_xobject) {
write!(page_object, "/XObject {} 0 R ", idx+1);
}
write!(page_object,
" >>\n \
/MediaBox [0 0 {} {}]\n \
/Contents {} 0 R\n\
>>\n",
size.x(), size.y(), stream_object_id
);
self.add_object(page_object, true, false);
}
/// Write the in-memory PDF representation to disk
pub fn write_to<W>(&mut self, writer: W) -> io::Result<()> where W: Write {
let mut out = Counter::new(writer);
out.write_all(b"%PDF-1.7\n%\xB5\xED\xAE\xFB\n")?;
if !self.page_buffer.is_empty() {
self.end_page();
}
// Write out each object
for (idx, obj) in self.objects.iter_mut().enumerate().skip(2) {
obj.offset = Some(out.pos());
write!(out, "{} 0 obj\n", idx+1)?;
out.write_all(&obj.contents)?;
out.write_all(b"endobj\n");
}
// Write out the page tree object
self.objects[1].offset = Some(out.pos());
out.write_all(b"2 0 obj\n")?;
out.write_all(b"<< /Type /Pages\n")?;
write!(out,
"/Count {}\n",
self.objects.iter().filter(|o| o.is_page).count()
)?;
out.write_all(b"/Kids [")?;
for (idx, obj) in self.objects.iter().enumerate().filter(|&(_, obj)| obj.is_page) {
write!(out, "{} 0 R ", idx + 1);
}
out.write_all(b"] >>\nendobj\n")?;
// Write out the catalog dictionary object
self.objects[0].offset = Some(out.pos());
out.write_all(b"1 0 obj\n<< /Type /Catalog\n/Pages 2 0 R >>\nendobj\n")?;
// Write the cross-reference table
let startxref = out.pos() + 1; // NOTE: apparently there's some 1-based indexing??
out.write_all(b"xref\n")?;
write!(out, "0 {}\n", self.objects.len() + 1)?;
out.write_all(b"0000000000 65535 f \n")?;
for obj in &self.objects {
write!(out, "{:010} 00000 f \n", obj.offset.unwrap())?;
}
// Write the document trailer
out.write_all(b"trailer\n")?;
write!(out, "<< /Size {}\n", self.objects.len())?;
out.write_all(b"/Root 1 0 R >>\n");
// Write the offset to the xref table
write!(out, "startxref\n{}\n", startxref)?;
// Write the PDF EOF
out.write_all(b"%%EOF")?;
Ok(())
}
}

View File

@ -198,8 +198,28 @@ impl Scene {
writeln!(writer, "</svg>")?; writeln!(writer, "</svg>")?;
Ok(()) Ok(())
} }
pub fn paths<'a>(&'a self) -> PathIter {
PathIter {
scene: self,
pos: 0
}
}
}
pub struct PathIter<'a> {
scene: &'a Scene,
pos: usize
}
impl<'a> Iterator for PathIter<'a> {
type Item = (&'a Paint, &'a Outline);
fn next(&mut self) -> Option<Self::Item> {
let item = self.scene.paths.get(self.pos).map(|path_object| {
(self.scene.paints.get(path_object.paint.0 as usize).unwrap(), &path_object.outline)
});
self.pos += 1;
item
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PathObject { pub struct PathObject {
outline: Outline, outline: Outline,