#![deny(missing_docs)]
extern crate deflate;
use std::fs::File;
use std::io;
pub mod graphicsstate;
use graphicsstate::{Color, Matrix};
struct PdfObject {
offset: usize,
id: usize,
is_page: bool,
}
pub struct Pdf {
buffer: Vec<u8>,
page_buffer: Vec<u8>,
objects: Vec<PdfObject>,
width: f32,
height: f32,
}
impl Pdf {
pub fn new() -> Self {
let mut this = Pdf {
buffer: Vec::new(),
page_buffer: Vec::new(),
objects: vec![
PdfObject {
offset: 0,
id: 1,
is_page: false,
},
PdfObject {
offset: 0,
id: 2,
is_page: false,
},
],
width: 400.0,
height: 400.0,
};
this.buffer.extend_from_slice(
b"%PDF-1.7\n%\xB5\xED\xAE\xFB\n",
);
this
}
fn move_to(&mut self, x: f32, y: f32) -> &mut Self {
self.page_buffer.extend(
format!("{:.2} {:.2} m ", x, y).bytes(),
);
self
}
fn line_to(&mut self, x: f32, y: f32) -> &mut Self {
self.page_buffer.extend(
format!("{:.2} {:.2} l ", x, y).bytes(),
);
self
}
fn curve_to(&mut self, (x1, y1): (f32, f32), (x2, y2): (f32, f32), (x3, y3): (f32, f32))
-> &mut Self {
self.page_buffer.extend(
format!(
"{:.2} {:.2} {:.2} {:.2} {:.2} {:.2} c\n",
x1,
y1,
x2,
y2,
x3,
y3
).bytes(),
);
self
}
pub fn set_line_width(&mut self, width: f32) -> &mut Self {
self.page_buffer.extend(format!("{:.2} w\n", width).bytes());
self
}
pub fn set_stroke_color(&mut self, color: Color) -> &mut Self {
let norm = |color| color as f32 / 255.0;
match color {
Color::RGB { red, green, blue } => {
self.page_buffer.extend(
format!(
"{:.2} {:.2} {:.2} SC\n",
norm(red),
norm(green),
norm(blue)
).bytes(),
)
}
Color::Gray { gray } => {
self.page_buffer.extend(
format!("{:.2} G\n", norm(gray)).bytes(),
)
}
};
self
}
pub fn transform(&mut self, m: Matrix) -> &mut Self {
self.page_buffer.extend(format!("{} cm\n", m).bytes());
self
}
pub fn draw_circle(&mut self, x: f32, y: f32, radius: f32) -> &mut Self {
let top = y - radius;
let bottom = y + radius;
let left = x - radius;
let right = x + radius;
let c = 0.551915024494;
let leftp = x - (radius * c);
let rightp = x + (radius * c);
let topp = y - (radius * c);
let bottomp = y + (radius * c);
self.move_to(x, top);
self.curve_to((leftp, top), (left, topp), (left, y));
self.curve_to((left, bottomp), (leftp, bottom), (x, bottom));
self.curve_to((rightp, bottom), (right, bottomp), (right, y));
self.curve_to((right, topp), (rightp, top), (x, top));
self.page_buffer.extend_from_slice(b"S\n");
self
}
pub fn draw_line<I>(&mut self, mut points: I) -> &mut Self
where
I: Iterator<Item = (f32, f32)>,
{
if let Some((x, y)) = points.next() {
self.move_to(x, y);
for (x, y) in points {
self.line_to(x, y);
}
}
self.page_buffer.extend_from_slice(b"S\n");
self
}
fn flush_page(&mut self) {
let obj_id = self.objects.iter().map(|o| o.id).max().unwrap() + 1;
self.objects.push(PdfObject {
offset: self.buffer.len(),
id: obj_id,
is_page: false,
});
let compressed = deflate::deflate_bytes_zlib(self.page_buffer.as_slice());
self.buffer.extend(format!("{} 0 obj\n", obj_id).bytes());
self.buffer.extend(
format!(
"<</Length {}\n/Filter /FlateDecode>>\nstream\n",
compressed.len()
).bytes(),
);
self.buffer.extend(compressed.iter());
self.buffer.extend("\nendstream\nendobj\n".bytes());
self.page_buffer.clear();
let obj_id = self.objects.iter().map(|o| o.id).max().unwrap() + 1;
self.objects.push(PdfObject {
offset: self.buffer.len(),
id: obj_id,
is_page: true,
});
self.buffer.extend(format!("{} 0 obj\n", obj_id).bytes());
self.buffer.extend_from_slice(b"<</Type /Page\n");
self.buffer.extend_from_slice(b"/Parent 2 0 R\n");
self.buffer.extend(
format!("/MediaBox [0 0 {} {}]\n", self.width, self.height).bytes(),
);
self.buffer.extend(
format!("/Contents {} 0 R", obj_id - 1).bytes(),
);
self.buffer.extend_from_slice(b">>\nendobj\n");
}
pub fn add_page(&mut self, width: f32, height: f32) -> &mut Self {
if !self.page_buffer.is_empty() {
self.flush_page();
}
self.page_buffer.extend(
"/DeviceRGB cs /DeviceRGB CS\n".bytes(),
);
self.width = width;
self.height = height;
self
}
pub fn write_to(&mut self, filename: &str) -> io::Result<()> {
use std::io::Write;
if !self.page_buffer.is_empty() {
self.flush_page();
}
self.objects[1].offset = self.buffer.len();
self.buffer.extend_from_slice(b"2 0 obj\n");
self.buffer.extend_from_slice(b"<</Type /Pages\n");
self.buffer.extend(
format!(
"/Count {}\n",
self.objects.iter().filter(|o| o.is_page).count()
).bytes(),
);
self.buffer.extend_from_slice(b"/Kids [");
for obj in self.objects.iter().filter(|obj| obj.is_page) {
self.buffer.extend(format!("{} 0 R ", obj.id).bytes());
}
self.buffer.pop();
self.buffer.extend_from_slice(b"]>>\nendobj\n");
self.objects[0].offset = self.buffer.len();
self.buffer.extend_from_slice(
b"1 0 obj\n<</Type /Catalog\n/Pages 2 0 R>>\nendobj\n",
);
let startxref = self.buffer.len();
self.buffer.extend_from_slice(b"xref\n");
self.buffer.extend(
format!("0 {}\n", self.objects.len() + 1).bytes(),
);
self.buffer.extend_from_slice(b"0000000000 65535 f \n");
self.objects.sort_by(|a, b| a.id.cmp(&b.id));
for obj in &self.objects {
self.buffer.extend(
format!("{:010} 00000 f \n", obj.offset).bytes(),
);
}
self.buffer.extend_from_slice(b"trailer\n");
self.buffer.extend(
format!("<</Size {}\n", self.objects.len())
.bytes(),
);
self.buffer.extend_from_slice(b"/Root 1 0 R>>\n");
self.buffer.extend(
format!("startxref\n{}\n", startxref).bytes(),
);
self.buffer.extend_from_slice(b"%%EOF");
File::create(filename)?.write_all(self.buffer.as_slice())
}
}