use crate::units::Length;
fn pdf_escape(value: &str) -> String {
value
.replace('\\', "\\\\")
.replace('(', "\\(")
.replace(')', "\\)")
}
pub fn write_text_pdf(lines: &[String], page_width: Length, page_height: Length) -> Vec<u8> {
let width_pt = page_width.as_pt();
let height_pt = page_height.as_pt();
let mut content = String::new();
content.push_str("BT\n/F1 12 Tf\n14 TL\n50 ");
content.push_str(&format!("{:.2}", height_pt - 50.0));
content.push_str(" Td\n");
for (index, line) in lines.iter().enumerate() {
if index > 0 {
content.push_str("T*\n");
}
content.push('(');
content.push_str(&pdf_escape(line));
content.push_str(") Tj\n");
}
content.push_str("ET\n");
let objects = vec![
"<< /Type /Catalog /Pages 2 0 R >>".to_string(),
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_string(),
format!(
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {width_pt:.2} {height_pt:.2}] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>"
),
format!(
"<< /Length {} >>\nstream\n{}endstream",
content.len(),
content
),
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>".to_string(),
];
pdf_from_objects(&objects)
}
pub fn pdf_from_objects(objects: &[String]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(b"%PDF-1.4\n");
let mut offsets = Vec::with_capacity(objects.len() + 1);
offsets.push(0usize);
for (index, object) in objects.iter().enumerate() {
offsets.push(out.len());
out.extend_from_slice(format!("{} 0 obj\n{}\nendobj\n", index + 1, object).as_bytes());
}
let xref_pos = out.len();
out.extend_from_slice(format!("xref\n0 {}\n", objects.len() + 1).as_bytes());
out.extend_from_slice(b"0000000000 65535 f \n");
for offset in offsets.iter().skip(1) {
out.extend_from_slice(format!("{:010} 00000 n \n", offset).as_bytes());
}
out.extend_from_slice(
format!(
"trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{}\n%%EOF\n",
objects.len() + 1,
xref_pos
)
.as_bytes(),
);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn writes_pdf_header_and_eof() {
let pdf = write_text_pdf(
&["Hello, world".to_string(), "Second line".to_string()],
Length::pt(595.0),
Length::pt(842.0),
);
assert!(pdf.starts_with(b"%PDF-1.4"));
assert!(pdf.ends_with(b"%%EOF\n"));
assert!(pdf.windows(4).any(|w| w == b"xref"));
}
#[test]
fn escapes_pdf_text_special_chars() {
let pdf = write_text_pdf(
&["a (b) \\c".to_string()],
Length::pt(100.0),
Length::pt(100.0),
);
let s = String::from_utf8_lossy(&pdf);
assert!(s.contains("a \\(b\\) \\\\c"));
}
}