Merge pull request #205 from s3bk/ps_export
Unify exports into the export crate.
This commit is contained in:
commit
ff777f7995
|
@ -18,7 +18,7 @@ members = [
|
|||
"gl",
|
||||
"gpu",
|
||||
"lottie",
|
||||
"pdf",
|
||||
"export",
|
||||
"metal",
|
||||
"renderer",
|
||||
"simd",
|
||||
|
@ -29,5 +29,5 @@ members = [
|
|||
"utils/area-lut",
|
||||
"utils/gamma-lut",
|
||||
"utils/svg-to-skia",
|
||||
"utils/svg2pdf",
|
||||
"utils/convert",
|
||||
]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "pathfinder_pdf"
|
||||
name = "pathfinder_export"
|
||||
version = "0.1.0"
|
||||
authors = ["Sebastian Köln <sebk@rynx.org>"]
|
||||
edition = "2018"
|
|
@ -0,0 +1,173 @@
|
|||
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 enum FileFormat {
|
||||
/// Scalable Vector Graphics
|
||||
SVG,
|
||||
|
||||
/// Portable Document Format
|
||||
PDF,
|
||||
|
||||
/// PostScript
|
||||
PS,
|
||||
}
|
||||
|
||||
pub trait Export {
|
||||
fn export<W: Write>(&self, writer: &mut W, format: FileFormat) -> io::Result<()>;
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn export_svg<W: Write>(scene: &Scene, writer: &mut W) -> io::Result<()> {
|
||||
let view_box = scene.view_box();
|
||||
writeln!(
|
||||
writer,
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"{} {} {} {}\">",
|
||||
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, " <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()));
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
if contour.is_closed() {
|
||||
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")?;
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
//! 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 std::io::{self, Write, Cursor, Seek};
|
||||
use std::io::{self, Write};
|
||||
use deflate::Compression;
|
||||
|
||||
struct Counter<T> {
|
||||
|
@ -19,9 +19,6 @@ impl<T> Counter<T> {
|
|||
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> {
|
||||
|
@ -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<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
|
||||
#[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")?;
|
|
@ -1,73 +0,0 @@
|
|||
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;
|
||||
|
||||
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);
|
||||
}
|
|
@ -89,12 +89,6 @@ impl SceneProxy {
|
|||
}
|
||||
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,
|
||||
|
@ -105,12 +99,7 @@ fn scene_thread<E>(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<E>(mut scene: Scene,
|
|||
enum MainToWorkerMsg {
|
||||
ReplaceScene(Scene),
|
||||
SetViewBox(RectF),
|
||||
Build(BuildOptions, Box<dyn RenderCommandListener>),
|
||||
GetSVG(Sender<Vec<u8>>),
|
||||
Build(BuildOptions, Box<dyn RenderCommandListener>)
|
||||
}
|
||||
|
||||
pub struct RenderCommandStream {
|
||||
|
|
|
@ -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 {
|
||||
|
@ -174,31 +173,6 @@ impl Scene {
|
|||
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 paths<'a>(&'a self) -> PathIter {
|
||||
PathIter {
|
||||
scene: self,
|
||||
|
@ -211,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<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.scene.paints.get(path_object.paint.0 as usize).unwrap(),
|
||||
&path_object.outline,
|
||||
&*path_object.name
|
||||
)
|
||||
});
|
||||
self.pos += 1;
|
||||
item
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "svg2pdf"
|
||||
name = "convert"
|
||||
version = "0.1.0"
|
||||
authors = ["Sebastian Köln <sebk@rynx.org>"]
|
||||
edition = "2018"
|
||||
|
@ -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 = "*"
|
|
@ -0,0 +1,27 @@
|
|||
use std::fs::File;
|
||||
use std::io::{Read, BufWriter};
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
use pathfinder_svg::BuiltSVG;
|
||||
use pathfinder_export::{Export, FileFormat};
|
||||
use usvg::{Tree, Options};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut args = std::env::args_os().skip(1);
|
||||
let input = PathBuf::from(args.next().expect("no input given"));
|
||||
let output = PathBuf::from(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());
|
||||
|
||||
let scene = &svg.scene;
|
||||
let mut writer = BufWriter::new(File::create(&output)?);
|
||||
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(())
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
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<dyn 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(())
|
||||
}
|
Loading…
Reference in New Issue