Move all scene exports into the export crate.

SVG, PDF and PS can now be created with Scene::export.
This commit is contained in:
Sebastian Köln 2019-06-25 11:26:37 +03:00
parent a02e370675
commit 090c21a20a
6 changed files with 173 additions and 220 deletions

View File

@ -7,6 +7,6 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
pathfinder_export = { path = "../../export" }
pathfinder_svg = { path = "../../svg" } pathfinder_svg = { path = "../../svg" }
pathfinder_pdf = { path = "../../pdf" }
usvg = "*" usvg = "*"

View File

@ -3,7 +3,7 @@ use std::io::{Read, BufWriter};
use std::error::Error; use std::error::Error;
use std::path::PathBuf; use std::path::PathBuf;
use pathfinder_svg::BuiltSVG; use pathfinder_svg::BuiltSVG;
use pathfinder_pdf::make_pdf; use pathfinder_export::{Export, FileFormat};
use usvg::{Tree, Options}; use usvg::{Tree, Options};
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
@ -17,10 +17,11 @@ fn main() -> Result<(), Box<dyn Error>> {
let scene = &svg.scene; let scene = &svg.scene;
let mut writer = BufWriter::new(File::create(&output)?); let mut writer = BufWriter::new(File::create(&output)?);
match output.extension().and_then(|s| s.to_str()) { let format = match output.extension().and_then(|s| s.to_str()) {
Some("pdf") => make_pdf(&mut writer, scene), Some("pdf") => FileFormat::PDF,
Some("ps") => scene.write_ps(&mut writer)?, Some("ps") => FileFormat::PS,
_ => return Err("output filename must have .ps or .pdf extension".into()) _ => return Err("output filename must have .ps or .pdf extension".into())
} };
scene.export(&mut writer, format).unwrap();
Ok(()) Ok(())
} }

View File

@ -1,73 +1,173 @@
use pathfinder_renderer::{scene::Scene}; use pathfinder_renderer::scene::Scene;
use pathfinder_geometry::{vector::Vector2F, rect::RectF}; use pathfinder_geometry::vector::Vector2F;
use pathfinder_content::{outline::Outline, segment::{Segment, SegmentKind}, color::ColorF}; use pathfinder_content::segment::SegmentKind;
use std::io::Write; use std::io::{self, Write};
use std::fmt;
mod pdf; mod pdf;
use pdf::Pdf; use pdf::Pdf;
pub struct PdfBuilder { pub enum FileFormat {
pdf: Pdf /// Scalable Vector Graphics
SVG,
/// Portable Document Format
PDF,
/// PostScript
PS,
} }
impl PdfBuilder { pub trait Export {
pub fn new() -> PdfBuilder { fn export<W: Write>(&self, writer: &mut W, format: FileFormat) -> io::Result<()>;
PdfBuilder { }
pdf: Pdf::new() impl Export for Scene {
fn export<W: Write>(&self, writer: &mut W, format: FileFormat) -> io::Result<()> {
match format {
FileFormat::SVG => export_svg(self, writer),
FileFormat::PDF => export_pdf(self, writer),
FileFormat::PS => export_ps(self, writer)
} }
} }
}
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 { fn export_svg<W: Write>(scene: &Scene, writer: &mut W) -> io::Result<()> {
SegmentKind::None => {} let view_box = scene.view_box();
SegmentKind::Line => self.pdf.line_to(tr(segment.baseline.to())), writeln!(
SegmentKind::Quadratic => { writer,
let current = segment.baseline.from(); "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"{} {} {} {}\">",
let c = segment.ctrl.from(); view_box.origin().x(),
let p = segment.baseline.to(); view_box.origin().y(),
let c1 = Vector2F::splat(2./3.) * c + Vector2F::splat(1./3.) * current; view_box.size().x(),
let c2 = Vector2F::splat(2./3.) * c + Vector2F::splat(1./3.) * p; view_box.size().y()
self.pdf.cubic_to(c1, c2, p); )?;
} for (paint, outline, name) in scene.paths() {
SegmentKind::Cubic => self.pdf.cubic_to(tr(segment.ctrl.from()), tr(segment.ctrl.to()), tr(segment.baseline.to())) write!(writer, " <path")?;
} if !name.is_empty() {
write!(writer, " id=\"{}\"", name)?;
}
writeln!(
writer,
" fill=\"{:?}\" d=\"{:?}\" />",
paint.color, outline
)?;
}
writeln!(writer, "</svg>")?;
Ok(())
}
fn export_pdf<W: Write>(scene: &Scene, writer: &mut W) -> io::Result<()> {
let mut pdf = Pdf::new();
let view_box = scene.view_box();
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() {
pdf.set_fill_color(paint.color);
for contour in outline.contours() {
for (segment_index, segment) in contour.iter().enumerate() {
if segment_index == 0 {
pdf.move_to(tr(segment.baseline.from()));
} }
if contour.is_closed() { match segment.kind {
self.pdf.close(); SegmentKind::None => {}
SegmentKind::Line => 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;
pdf.cubic_to(c1, c2, p);
}
SegmentKind::Cubic => pdf.cubic_to(tr(segment.ctrl.from()), tr(segment.ctrl.to()), tr(segment.baseline.to()))
} }
} }
// closes implicitly if contour.is_closed() {
self.pdf.fill(); pdf.close();
}
}
// closes implicitly
pdf.fill();
}
pdf.write_to(writer)
}
fn export_ps<W: Write>(scene: &Scene, writer: &mut W) -> io::Result<()> {
struct P(Vector2F);
impl fmt::Display for P {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.0.x(), self.0.y())
} }
} }
let view_box = scene.view_box();
writeln!(writer, "%!PS-Adobe-3.0 EPSF-3.0")?;
writeln!(writer, "%%BoundingBox: {:.0} {:.0}",
P(view_box.origin()),
P(view_box.size()),
)?;
writeln!(writer, "%%HiResBoundingBox: {} {}",
P(view_box.origin()),
P(view_box.size()),
)?;
writeln!(writer, "0 {} translate", view_box.size().y())?;
writeln!(writer, "1 -1 scale")?;
pub fn write<W: Write>(mut self, out: W) { for (paint, outline, name) in scene.paths() {
self.pdf.write_to(out); if !name.is_empty() {
writeln!(writer, "newpath % {}", name)?;
} else {
writeln!(writer, "newpath")?;
}
let color = paint.color.to_f32();
for contour in outline.contours() {
for (segment_index, segment) in contour.iter().enumerate() {
if segment_index == 0 {
writeln!(writer, "{} moveto", P(segment.baseline.from()))?;
}
match segment.kind {
SegmentKind::None => {}
SegmentKind::Line => {
writeln!(writer, "{} lineto", P(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;
writeln!(writer, "{} {} {} curveto", P(c1), P(c2), P(p))?;
}
SegmentKind::Cubic => {
writeln!(writer, "{} {} {} curveto",
P(segment.ctrl.from()),
P(segment.ctrl.to()),
P(segment.baseline.to())
)?;
}
}
}
if contour.is_closed() {
writeln!(writer, "closepath")?;
}
}
writeln!(writer, "{} {} {} setrgbcolor", color.r(), color.g(), color.b())?;
writeln!(writer, "fill")?;
} }
writeln!(writer, "showpage")?;
Ok(())
} }
pub fn make_pdf<W: Write>(mut writer: W, scene: &Scene) {
let mut pdf = PdfBuilder::new();
pdf.add_scene(scene);
pdf.write(writer);
}

View File

@ -1,8 +1,8 @@
//! This is a heavily modified version of the pdfpdf crate by Benjamin Kimock <kimockb@gmail.com> (aka. saethlin) //! This is a heavily modified version of the pdfpdf crate by Benjamin Kimock <kimockb@gmail.com> (aka. saethlin)
use pathfinder_geometry::{vector::Vector2F, rect::RectF}; use pathfinder_geometry::vector::Vector2F;
use pathfinder_content::color::ColorU; use pathfinder_content::color::ColorU;
use std::io::{self, Write, Cursor, Seek}; use std::io::{self, Write};
use deflate::Compression; use deflate::Compression;
struct Counter<T> { struct Counter<T> {
@ -19,9 +19,6 @@ impl<T> Counter<T> {
pub fn pos(&self) -> u64 { pub fn pos(&self) -> u64 {
self.count self.count
} }
pub fn into_inner(self) -> T {
self.inner
}
} }
impl<W: Write> Write for Counter<W> { impl<W: Write> Write for Counter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
@ -101,43 +98,6 @@ impl Pdf {
self.objects.len() 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()
).unwrap();
}
/// Set the current line width
#[inline]
pub fn set_line_width(&mut self, width: f32) {
writeln!(self.page_buffer, "{} w", width).unwrap();
}
/// 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)
).unwrap();
}
/// Set the color for all subsequent drawing operations /// Set the color for all subsequent drawing operations
#[inline] #[inline]
pub fn set_fill_color(&mut self, color: ColorU) { pub fn set_fill_color(&mut self, color: ColorU) {
@ -178,10 +138,6 @@ impl Pdf {
writeln!(self.page_buffer, "f").unwrap(); writeln!(self.page_buffer, "f").unwrap();
} }
pub fn stroke(&mut self) {
writeln!(self.page_buffer, "s").unwrap();
}
pub fn close(&mut self) { pub fn close(&mut self) {
writeln!(self.page_buffer, "h").unwrap(); writeln!(self.page_buffer, "h").unwrap();
} }
@ -218,7 +174,7 @@ impl Pdf {
/Resources <<\n" /Resources <<\n"
.to_vec(); .to_vec();
for (idx, obj) in self.objects.iter().enumerate().filter(|&(_, o)| o.is_xobject) { for (idx, _obj) in self.objects.iter().enumerate().filter(|&(_, o)| o.is_xobject) {
write!(page_object, "/XObject {} 0 R ", idx+1).unwrap(); write!(page_object, "/XObject {} 0 R ", idx+1).unwrap();
} }
@ -258,7 +214,7 @@ impl Pdf {
self.objects.iter().filter(|o| o.is_page).count() self.objects.iter().filter(|o| o.is_page).count()
)?; )?;
out.write_all(b"/Kids [")?; out.write_all(b"/Kids [")?;
for (idx, obj) in self.objects.iter().enumerate().filter(|&(_, obj)| obj.is_page) { for (idx, _obj) in self.objects.iter().enumerate().filter(|&(_, obj)| obj.is_page) {
write!(out, "{} 0 R ", idx + 1)?; write!(out, "{} 0 R ", idx + 1)?;
} }
out.write_all(b"] >>\nendobj\n")?; out.write_all(b"] >>\nendobj\n")?;

View File

@ -89,12 +89,6 @@ impl SceneProxy {
} }
renderer.end_scene(); renderer.end_scene();
} }
pub fn as_svg(&self) -> Vec<u8> {
let (sender, receiver) = mpsc::channel();
self.sender.send(MainToWorkerMsg::GetSVG(sender)).unwrap();
receiver.recv().unwrap()
}
} }
fn scene_thread<E>(mut scene: Scene, fn scene_thread<E>(mut scene: Scene,
@ -105,12 +99,7 @@ fn scene_thread<E>(mut scene: Scene,
match msg { match msg {
MainToWorkerMsg::ReplaceScene(new_scene) => scene = new_scene, MainToWorkerMsg::ReplaceScene(new_scene) => scene = new_scene,
MainToWorkerMsg::SetViewBox(new_view_box) => scene.set_view_box(new_view_box), MainToWorkerMsg::SetViewBox(new_view_box) => scene.set_view_box(new_view_box),
MainToWorkerMsg::Build(options, listener) => scene.build(options, listener, &executor), MainToWorkerMsg::Build(options, listener) => scene.build(options, listener, &executor)
MainToWorkerMsg::GetSVG(sender) => {
let mut bytes = vec![];
scene.write_svg(&mut bytes).unwrap();
sender.send(bytes).unwrap();
}
} }
} }
} }
@ -118,8 +107,7 @@ fn scene_thread<E>(mut scene: Scene,
enum MainToWorkerMsg { enum MainToWorkerMsg {
ReplaceScene(Scene), ReplaceScene(Scene),
SetViewBox(RectF), SetViewBox(RectF),
Build(BuildOptions, Box<dyn RenderCommandListener>), Build(BuildOptions, Box<dyn RenderCommandListener>)
GetSVG(Sender<Vec<u8>>),
} }
pub struct RenderCommandStream { pub struct RenderCommandStream {

View File

@ -21,7 +21,6 @@ use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::transform2d::Transform2DF; use pathfinder_geometry::transform2d::Transform2DF;
use pathfinder_content::color::ColorU; use pathfinder_content::color::ColorU;
use pathfinder_content::outline::Outline; use pathfinder_content::outline::Outline;
use std::io::{self, Write};
#[derive(Clone)] #[derive(Clone)]
pub struct Scene { pub struct Scene {
@ -173,101 +172,6 @@ impl Scene {
let prepared_options = options.prepare(self.bounds); let prepared_options = options.prepare(self.bounds);
SceneBuilder::new(self, &prepared_options, listener).build(executor) SceneBuilder::new(self, &prepared_options, listener).build(executor)
} }
pub fn write_svg<W>(&self, writer: &mut W) -> io::Result<()> where W: Write {
writeln!(
writer,
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"{} {} {} {}\">",
self.view_box.origin().x(),
self.view_box.origin().y(),
self.view_box.size().x(),
self.view_box.size().y()
)?;
for path_object in &self.paths {
let paint = &self.paints[path_object.paint.0 as usize];
write!(writer, " <path")?;
if !path_object.name.is_empty() {
write!(writer, " id=\"{}\"", path_object.name)?;
}
writeln!(
writer,
" fill=\"{:?}\" d=\"{:?}\" />",
paint.color, path_object.outline
)?;
}
writeln!(writer, "</svg>")?;
Ok(())
}
pub fn write_ps<W>(&self, writer: &mut W) -> io::Result<()>
where
W: Write,
{
use std::fmt;
use pathfinder_content::segment::SegmentKind;
struct P(Vector2F);
impl fmt::Display for P {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.0.x(), self.0.y())
}
}
writeln!(writer, "%!PS-Adobe-3.0 EPSF-3.0")?;
writeln!(writer, "%%BoundingBox: {:.0} {:.0}",
P(self.view_box.origin()),
P(self.view_box.size()),
)?;
writeln!(writer, "%%HiResBoundingBox: {} {}",
P(self.view_box.origin()),
P(self.view_box.size()),
)?;
writeln!(writer, "0 {} translate", self.view_box.size().y())?;
writeln!(writer, "1 -1 scale")?;
for path_object in &self.paths {
writeln!(writer, "newpath")?;
let color = self.paints[path_object.paint.0 as usize].color.to_f32();
for contour in path_object.outline.contours() {
for (segment_index, segment) in contour.iter().enumerate() {
if segment_index == 0 {
writeln!(writer, "{} moveto", P(segment.baseline.from()))?;
}
match segment.kind {
SegmentKind::None => {}
SegmentKind::Line => {
writeln!(writer, "{} lineto", P(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;
writeln!(writer, "{} {} {} curveto", P(c1), P(c2), P(p))?;
}
SegmentKind::Cubic => {
writeln!(writer, "{} {} {} curveto",
P(segment.ctrl.from()),
P(segment.ctrl.to()),
P(segment.baseline.to())
)?;
}
}
}
if contour.is_closed() {
writeln!(writer, "closepath")?;
}
}
writeln!(writer, "{} {} {} setrgbcolor", color.r(), color.g(), color.b())?;
writeln!(writer, "fill")?;
}
writeln!(writer, "showpage")?;
Ok(())
}
pub fn paths<'a>(&'a self) -> PathIter { pub fn paths<'a>(&'a self) -> PathIter {
PathIter { PathIter {
@ -281,10 +185,14 @@ pub struct PathIter<'a> {
pos: usize pos: usize
} }
impl<'a> Iterator for PathIter<'a> { impl<'a> Iterator for PathIter<'a> {
type Item = (&'a Paint, &'a Outline); type Item = (&'a Paint, &'a Outline, &'a str);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let item = self.scene.paths.get(self.pos).map(|path_object| { 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.scene.paints.get(path_object.paint.0 as usize).unwrap(),
&path_object.outline,
&*path_object.name
)
}); });
self.pos += 1; self.pos += 1;
item item