2020-01-31 03:16:34 -05:00
|
|
|
// pathfinder/export/src/lib.rs
|
|
|
|
//
|
|
|
|
// Copyright © 2020 The Pathfinder Project Developers.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
|
|
|
use pathfinder_content::segment::SegmentKind;
|
2020-02-05 22:59:40 -05:00
|
|
|
use pathfinder_renderer::paint::Paint;
|
2019-06-25 04:26:37 -04:00
|
|
|
use pathfinder_renderer::scene::Scene;
|
|
|
|
use pathfinder_geometry::vector::Vector2F;
|
|
|
|
use std::fmt;
|
2020-02-05 22:59:40 -05:00
|
|
|
use std::io::{self, Write};
|
2019-06-15 10:50:25 -04:00
|
|
|
|
|
|
|
mod pdf;
|
|
|
|
use pdf::Pdf;
|
|
|
|
|
2019-06-25 04:26:37 -04:00
|
|
|
pub enum FileFormat {
|
|
|
|
/// Scalable Vector Graphics
|
|
|
|
SVG,
|
|
|
|
|
|
|
|
/// Portable Document Format
|
|
|
|
PDF,
|
|
|
|
|
|
|
|
/// PostScript
|
|
|
|
PS,
|
2019-06-15 10:50:25 -04:00
|
|
|
}
|
|
|
|
|
2019-06-25 04:26:37 -04:00
|
|
|
pub trait Export {
|
|
|
|
fn export<W: Write>(&self, writer: &mut W, format: FileFormat) -> io::Result<()>;
|
|
|
|
}
|
2019-06-25 14:32:26 -04:00
|
|
|
|
2019-06-25 04:26:37 -04:00
|
|
|
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)
|
2019-06-15 10:50:25 -04:00
|
|
|
}
|
|
|
|
}
|
2019-06-25 04:26:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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)?;
|
|
|
|
}
|
2020-02-05 22:59:40 -05:00
|
|
|
writeln!(writer, " fill=\"{:?}\" d=\"{:?}\" />", paint, outline)?;
|
2019-06-25 04:26:37 -04:00
|
|
|
}
|
|
|
|
writeln!(writer, "</svg>")?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2019-06-15 10:50:25 -04:00
|
|
|
|
2019-06-25 04:26:37 -04:00
|
|
|
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() {
|
2020-02-05 22:59:40 -05:00
|
|
|
match paint {
|
|
|
|
Paint::Color(color) => pdf.set_fill_color(*color),
|
|
|
|
Paint::Gradient(_) => {
|
|
|
|
// TODO(pcwalton): Gradients.
|
|
|
|
}
|
2020-02-14 14:50:34 -05:00
|
|
|
Paint::Pattern(_) => {
|
|
|
|
// TODO(pcwalton): Patterns.
|
|
|
|
}
|
2020-02-05 22:59:40 -05:00
|
|
|
}
|
2019-06-15 10:50:25 -04:00
|
|
|
|
2019-06-25 04:26:37 -04:00
|
|
|
for contour in outline.contours() {
|
|
|
|
for (segment_index, segment) in contour.iter().enumerate() {
|
|
|
|
if segment_index == 0 {
|
|
|
|
pdf.move_to(tr(segment.baseline.from()));
|
|
|
|
}
|
2019-06-15 10:50:25 -04:00
|
|
|
|
2019-06-25 04:26:37 -04:00
|
|
|
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);
|
2019-06-15 10:50:25 -04:00
|
|
|
}
|
2019-06-25 04:26:37 -04:00
|
|
|
SegmentKind::Cubic => pdf.cubic_to(tr(segment.ctrl.from()), tr(segment.ctrl.to()), tr(segment.baseline.to()))
|
2019-06-15 10:50:25 -04:00
|
|
|
}
|
2019-06-25 04:26:37 -04:00
|
|
|
}
|
2019-06-15 10:50:25 -04:00
|
|
|
|
2019-06-25 04:26:37 -04:00
|
|
|
if contour.is_closed() {
|
|
|
|
pdf.close();
|
2019-06-15 10:50:25 -04:00
|
|
|
}
|
2019-06-25 04:26:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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())
|
2019-06-15 10:50:25 -04:00
|
|
|
}
|
|
|
|
}
|
2019-06-25 04:26:37 -04:00
|
|
|
|
|
|
|
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")?;
|
2019-06-15 10:50:25 -04:00
|
|
|
|
2019-06-25 04:26:37 -04:00
|
|
|
for (paint, outline, name) in scene.paths() {
|
|
|
|
if !name.is_empty() {
|
|
|
|
writeln!(writer, "newpath % {}", name)?;
|
|
|
|
} else {
|
|
|
|
writeln!(writer, "newpath")?;
|
|
|
|
}
|
2020-02-05 22:59:40 -05:00
|
|
|
|
2019-06-25 04:26:37 -04:00
|
|
|
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")?;
|
|
|
|
}
|
|
|
|
}
|
2020-02-05 22:59:40 -05:00
|
|
|
|
|
|
|
match paint {
|
|
|
|
Paint::Color(color) => {
|
|
|
|
writeln!(writer, "{} {} {} setrgbcolor", color.r, color.g, color.b)?;
|
|
|
|
}
|
|
|
|
Paint::Gradient(_) => {
|
|
|
|
// TODO(pcwalton): Gradients.
|
|
|
|
}
|
2020-02-14 14:50:34 -05:00
|
|
|
Paint::Pattern(_) => {
|
|
|
|
// TODO(pcwalton): Patterns.
|
|
|
|
}
|
2020-02-05 22:59:40 -05:00
|
|
|
}
|
|
|
|
|
2019-06-25 04:26:37 -04:00
|
|
|
writeln!(writer, "fill")?;
|
2019-06-15 10:50:25 -04:00
|
|
|
}
|
2019-06-25 04:26:37 -04:00
|
|
|
writeln!(writer, "showpage")?;
|
|
|
|
Ok(())
|
2019-06-15 10:50:25 -04:00
|
|
|
}
|
|
|
|
|
2019-06-25 04:26:37 -04:00
|
|
|
|