From 090c21a20a81e9abc12bd959f44c0fe97ad5b6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=B6ln?= Date: Tue, 25 Jun 2019 11:26:37 +0300 Subject: [PATCH] Move all scene exports into the export crate. SVG, PDF and PS can now be created with Scene::export. --- examples/convert/Cargo.toml | 2 +- examples/convert/src/main.rs | 11 +- export/src/lib.rs | 208 ++++++++++++++++++------- export/src/pdf.rs | 52 +------ renderer/src/concurrent/scene_proxy.rs | 16 +- renderer/src/scene.rs | 104 +------------ 6 files changed, 173 insertions(+), 220 deletions(-) diff --git a/examples/convert/Cargo.toml b/examples/convert/Cargo.toml index c36e9dfa..5d972ff5 100644 --- a/examples/convert/Cargo.toml +++ b/examples/convert/Cargo.toml @@ -7,6 +7,6 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +pathfinder_export = { path = "../../export" } pathfinder_svg = { path = "../../svg" } -pathfinder_pdf = { path = "../../pdf" } usvg = "*" diff --git a/examples/convert/src/main.rs b/examples/convert/src/main.rs index 29720cf9..63726a4c 100644 --- a/examples/convert/src/main.rs +++ b/examples/convert/src/main.rs @@ -3,7 +3,7 @@ use std::io::{Read, BufWriter}; use std::error::Error; use std::path::PathBuf; use pathfinder_svg::BuiltSVG; -use pathfinder_pdf::make_pdf; +use pathfinder_export::{Export, FileFormat}; use usvg::{Tree, Options}; fn main() -> Result<(), Box> { @@ -17,10 +17,11 @@ fn main() -> Result<(), Box> { let scene = &svg.scene; let mut writer = BufWriter::new(File::create(&output)?); - match output.extension().and_then(|s| s.to_str()) { - Some("pdf") => make_pdf(&mut writer, scene), - Some("ps") => scene.write_ps(&mut writer)?, + let format = match output.extension().and_then(|s| s.to_str()) { + Some("pdf") => FileFormat::PDF, + Some("ps") => FileFormat::PS, _ => return Err("output filename must have .ps or .pdf extension".into()) - } + }; + scene.export(&mut writer, format).unwrap(); Ok(()) } diff --git a/export/src/lib.rs b/export/src/lib.rs index fcab6a75..4640c3c0 100644 --- a/export/src/lib.rs +++ b/export/src/lib.rs @@ -1,73 +1,173 @@ -use pathfinder_renderer::{scene::Scene}; -use pathfinder_geometry::{vector::Vector2F, rect::RectF}; -use pathfinder_content::{outline::Outline, segment::{Segment, SegmentKind}, color::ColorF}; -use std::io::Write; +use pathfinder_renderer::scene::Scene; +use pathfinder_geometry::vector::Vector2F; +use pathfinder_content::segment::SegmentKind; +use std::io::{self, Write}; +use std::fmt; mod pdf; use pdf::Pdf; -pub struct PdfBuilder { - pdf: Pdf +pub enum FileFormat { + /// Scalable Vector Graphics + SVG, + + /// Portable Document Format + PDF, + + /// PostScript + PS, } -impl PdfBuilder { - pub fn new() -> PdfBuilder { - PdfBuilder { - pdf: Pdf::new() +pub trait Export { + fn export(&self, writer: &mut W, format: FileFormat) -> io::Result<()>; +} +impl Export for Scene { + fn export(&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 { - 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())) - } +fn export_svg(scene: &Scene, writer: &mut W) -> io::Result<()> { + let view_box = scene.view_box(); + writeln!( + writer, + "", + view_box.origin().x(), + view_box.origin().y(), + view_box.size().x(), + view_box.size().y() + )?; + for (paint, outline, name) in scene.paths() { + write!(writer, " ", + paint.color, outline + )?; + } + writeln!(writer, "")?; + Ok(()) +} + +fn export_pdf(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() { - self.pdf.close(); + match segment.kind { + 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 - self.pdf.fill(); + + if contour.is_closed() { + pdf.close(); + } + } + + // closes implicitly + pdf.fill(); + } + pdf.write_to(writer) +} + +fn export_ps(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(mut self, out: W) { - self.pdf.write_to(out); + for (paint, outline, name) in scene.paths() { + 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(mut writer: W, scene: &Scene) { - let mut pdf = PdfBuilder::new(); - pdf.add_scene(scene); - pdf.write(writer); -} + diff --git a/export/src/pdf.rs b/export/src/pdf.rs index 7de2ec3e..29f11451 100644 --- a/export/src/pdf.rs +++ b/export/src/pdf.rs @@ -1,8 +1,8 @@ //! This is a heavily modified version of the pdfpdf crate by Benjamin Kimock (aka. saethlin) -use pathfinder_geometry::{vector::Vector2F, rect::RectF}; +use pathfinder_geometry::vector::Vector2F; use pathfinder_content::color::ColorU; -use std::io::{self, Write, Cursor, Seek}; +use std::io::{self, Write}; use deflate::Compression; struct Counter { @@ -19,9 +19,6 @@ impl Counter { pub fn pos(&self) -> u64 { self.count } - pub fn into_inner(self) -> T { - self.inner - } } impl Write for Counter { fn write(&mut self, buf: &[u8]) -> io::Result { @@ -101,43 +98,6 @@ impl Pdf { 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) { - 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 #[inline] pub fn set_fill_color(&mut self, color: ColorU) { @@ -178,10 +138,6 @@ impl Pdf { writeln!(self.page_buffer, "f").unwrap(); } - pub fn stroke(&mut self) { - writeln!(self.page_buffer, "s").unwrap(); - } - pub fn close(&mut self) { writeln!(self.page_buffer, "h").unwrap(); } @@ -218,7 +174,7 @@ impl Pdf { /Resources <<\n" .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(); } @@ -258,7 +214,7 @@ impl Pdf { 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) { + 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")?; diff --git a/renderer/src/concurrent/scene_proxy.rs b/renderer/src/concurrent/scene_proxy.rs index 22e29155..9a1f16c5 100644 --- a/renderer/src/concurrent/scene_proxy.rs +++ b/renderer/src/concurrent/scene_proxy.rs @@ -89,12 +89,6 @@ impl SceneProxy { } renderer.end_scene(); } - - pub fn as_svg(&self) -> Vec { - let (sender, receiver) = mpsc::channel(); - self.sender.send(MainToWorkerMsg::GetSVG(sender)).unwrap(); - receiver.recv().unwrap() - } } fn scene_thread(mut scene: Scene, @@ -105,12 +99,7 @@ fn scene_thread(mut scene: Scene, match msg { MainToWorkerMsg::ReplaceScene(new_scene) => scene = new_scene, MainToWorkerMsg::SetViewBox(new_view_box) => scene.set_view_box(new_view_box), - 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(); - } + MainToWorkerMsg::Build(options, listener) => scene.build(options, listener, &executor) } } } @@ -118,8 +107,7 @@ fn scene_thread(mut scene: Scene, enum MainToWorkerMsg { ReplaceScene(Scene), SetViewBox(RectF), - Build(BuildOptions, Box), - GetSVG(Sender>), + Build(BuildOptions, Box) } pub struct RenderCommandStream { diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index 13c2c299..dcd11a16 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -21,7 +21,6 @@ use pathfinder_geometry::rect::RectF; use pathfinder_geometry::transform2d::Transform2DF; use pathfinder_content::color::ColorU; use pathfinder_content::outline::Outline; -use std::io::{self, Write}; #[derive(Clone)] pub struct Scene { @@ -173,101 +172,6 @@ impl Scene { let prepared_options = options.prepare(self.bounds); SceneBuilder::new(self, &prepared_options, listener).build(executor) } - - pub fn write_svg(&self, writer: &mut W) -> io::Result<()> where W: Write { - writeln!( - writer, - "", - 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, " ", - paint.color, path_object.outline - )?; - } - writeln!(writer, "")?; - Ok(()) - } - - pub fn write_ps(&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 { PathIter { @@ -281,10 +185,14 @@ pub struct PathIter<'a> { pos: usize } 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 { 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; item