#[cfg(feature = "compress")]
use flate2::{write::ZlibEncoder, Compression};
use once_cell::sync::Lazy;
use std::error::Error;
#[cfg(not(target_arch = "wasm32"))]
use std::fs::File;
#[cfg(target_arch = "wasm32")]
use std::io;
use std::io::Write;
use std::path;
use std::sync::Mutex;
pub use crate::shapes::*;
pub use crate::units::*;
const N_OBJ_RESERVED: usize = 2;
static DEFAULT_PAGE_WIDTH: Lazy<Mutex<f64>> = Lazy::new(|| Inch(8.5).to_points().into());
static DEFAULT_PAGE_HEIGHT: Lazy<Mutex<f64>> = Lazy::new(|| Inch(11.0).to_points().into());
#[derive(Debug)]
pub struct Generator {
#[cfg(not(target_arch = "wasm32"))]
file_path: path::PathBuf,
#[cfg(target_arch = "wasm32")]
_file_path: path::PathBuf,
pdf: Vec<u8>, pdf_pre: Vec<u8>, offsets: Vec<usize>, pre_offset: usize, content_stream: Vec<u8>, pages: Vec<usize>, finished: bool, }
impl Generator {
pub fn new(file_path: path::PathBuf) -> Self {
Self {
#[cfg(not(target_arch = "wasm32"))]
file_path,
#[cfg(target_arch = "wasm32")]
_file_path: file_path,
pdf: Vec::new(),
pdf_pre: Vec::new(),
offsets: vec![0; N_OBJ_RESERVED], pre_offset: 0,
content_stream: Vec::new(),
pages: Vec::new(),
finished: false,
}
}
fn ensure_finalized(&mut self) {
if self.finished {
return;
}
self.initialize_pdf();
self.finalize_pdf();
self.finished = true;
}
fn collect_pdf_bytes(&mut self) -> Vec<u8> {
self.ensure_finalized();
let mut bytes = Vec::with_capacity(self.pdf_pre.len() + self.pdf.len());
bytes.extend_from_slice(&self.pdf_pre);
bytes.extend_from_slice(&self.pdf);
bytes
}
pub fn to_pdf_bytes(&mut self) -> Vec<u8> {
self.collect_pdf_bytes()
}
#[cfg(not(target_arch = "wasm32"))]
pub fn write_pdf(&mut self) -> Result<(), Box<dyn Error>> {
let bytes = self.collect_pdf_bytes();
if let Some(dir) = self.file_path.parent() {
std::fs::create_dir_all(dir)?;
}
let mut file = File::create(&self.file_path)?;
file.write_all(&bytes)?;
Ok(())
}
#[cfg(target_arch = "wasm32")]
pub fn write_pdf(&mut self) -> Result<(), Box<dyn Error>> {
Err(io::Error::new(
io::ErrorKind::Other,
"write_pdf is not supported on wasm targets",
)
.into())
}
fn initialize_pdf(&mut self) {
self.add_content();
self.pdf_pre.extend(b"%PDF-1.5\n");
self.add_pre_object(b"<< /Type /Catalog /Pages 2 0 R >>", 0);
let pages_kids: String = self
.pages
.iter()
.map(|page| format!("{} 0 R ", page))
.collect::<String>()
.trim()
.to_string();
self.add_pre_object(
&format!(
"<< /Type /Pages /Kids [{}] /Count {} >>",
pages_kids,
self.pages.len()
)
.as_bytes(),
1,
);
}
fn finalize_pdf(&mut self) {
let xref_start = self.pdf_pre.len() + self.pdf.len();
self.pdf.extend(b"xref\n");
self.pdf
.extend(format!("0 {}\n0000000000 65535 f \n", self.offsets.len() + 1).as_bytes());
for (i, offset) in self.offsets.iter().enumerate() {
self.pdf.extend(
format!(
"{:010} 00000 {} \n",
offset
+ if i >= N_OBJ_RESERVED && (*offset > 0 || i == N_OBJ_RESERVED) {
self.pre_offset
} else {
0
},
if *offset == 0 && i > N_OBJ_RESERVED {
'f'
} else {
'n'
}
)
.as_bytes(),
);
}
self.pdf.extend(b"trailer\n");
self.pdf.extend(
format!(
"<< /Root 1 0 R /Size {} >>\nstartxref\n{}\n%%EOF\n",
self.offsets.len() + 1,
xref_start
)
.as_bytes(),
);
}
fn add_object(&mut self, content: &[u8]) {
self.offsets.push(self.pdf.len()); self.pdf
.extend_from_slice(format!("{} 0 obj\n", self.offsets.len()).as_bytes());
self.pdf.extend_from_slice(content);
self.pdf.extend_from_slice(b"\nendobj\n");
}
fn add_pre_object(&mut self, content: &[u8], n_obj: usize) {
self.offsets[n_obj] = self.pdf_pre.len(); self.pdf_pre
.extend_from_slice(format!("{} 0 obj\n", n_obj + 1).as_bytes());
self.pdf_pre.extend_from_slice(content);
self.pdf_pre.extend_from_slice(b"\nendobj\n");
self.pre_offset = self.pdf_pre.len();
}
pub fn add_page(&mut self) {
self.add_page_with_size(
Pt(*DEFAULT_PAGE_WIDTH.lock().unwrap()),
Pt(*DEFAULT_PAGE_HEIGHT.lock().unwrap()),
);
}
pub fn add_page_a4(&mut self) {
self.add_page_with_size(Mm(210.0), Mm(297.0));
}
pub fn add_page_a4_landscape(&mut self) {
self.add_page_with_size(Mm(297.0), Mm(210.0));
}
pub fn add_page_letter(&mut self) {
self.add_page_with_size(Inch(8.5), Inch(11.0));
}
pub fn add_page_letter_landscape(&mut self) {
self.add_page_with_size(Inch(11.0), Inch(8.5));
}
pub fn add_page_with_size<L: Length>(&mut self, width: L, height: L) {
if self.pages.len() > 0 {
self.add_content(); }
self.pages.push(self.offsets.len() + 1);
self.add_object(
&format!(
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {} {}] /Contents {} 0 R >>",
width.to_points(),
height.to_points(),
self.offsets.len() + 2
)
.as_bytes(),
);
}
#[cfg(feature = "compress")]
fn compress_stream(stream: &mut Vec<u8>) -> bool {
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(stream).unwrap();
let compressed = encoder.finish().unwrap();
if compressed.len() + 21 < stream.len() {
stream.clear();
stream.extend_from_slice(&compressed);
return true;
} else {
return false;
}
}
fn add_content(&mut self) {
#[cfg(feature = "compress")]
let flate_decode = Self::compress_stream(&mut self.content_stream);
#[cfg(not(feature = "compress"))]
let flate_decode = false;
let len = self.content_stream.len();
let mut content: Vec<u8> = format!(
"<< /Length {} {}>>\nstream\n",
len,
if flate_decode {
"/Filter /FlateDecode "
} else {
""
}
)
.as_bytes()
.to_vec();
content.extend_from_slice(&self.content_stream);
content.extend_from_slice(b"\nendstream\n");
self.add_object(&content);
self.content_stream.clear();
}
pub fn line(
&mut self,
x1: impl Length,
y1: impl Length,
x2: impl Length,
y2: impl Length,
) -> Shape<'_> {
Shape {
content_stream: Some(&mut self.content_stream),
enum_type: ShapeType::Line,
x: vec![x1.to_points(), x2.to_points()],
y: vec![y1.to_points(), y2.to_points()],
..Default::default()
}
}
pub fn circle(&mut self, x: impl Length, y: impl Length, radius: impl Length) -> Shape<'_> {
Shape {
content_stream: Some(&mut self.content_stream),
enum_type: ShapeType::Circle,
x: vec![x.to_points()],
y: vec![y.to_points()],
radius: Some(radius.to_points()),
..Default::default()
}
}
pub fn rectangle(
&mut self,
x: impl Length,
y: impl Length,
width: impl Length,
height: impl Length,
) -> Shape<'_> {
Shape {
content_stream: Some(&mut self.content_stream),
enum_type: ShapeType::Rectangle,
x: vec![x.to_points(), width.to_points()],
y: vec![y.to_points(), height.to_points()],
..Default::default()
}
}
pub fn get_default_page_size() -> (Pt, Pt) {
(
Pt(*DEFAULT_PAGE_WIDTH.lock().unwrap()),
Pt(*DEFAULT_PAGE_HEIGHT.lock().unwrap()),
)
}
pub fn set_default_page_size(width: impl Length, height: impl Length) {
*DEFAULT_PAGE_WIDTH.lock().unwrap() = width.to_points();
*DEFAULT_PAGE_HEIGHT.lock().unwrap() = height.to_points();
}
pub fn get_default_width() -> Pt {
Shape::get_default_width()
}
pub fn set_default_width(width: impl Length) {
Shape::set_default_width(width);
}
pub fn set_default_cap_type(cap_type: CapType) {
Shape::set_default_cap_type(cap_type);
}
pub fn set_default_color(color: impl Color) {
Shape::set_default_color(color);
}
pub fn set_default_angle(angle: impl Angle) {
Shape::set_default_angle(angle);
}
}