oxihuman_export/
pdf_stub_export.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct PdfObject {
11 pub number: u32,
12 pub content: Vec<u8>,
13}
14
15#[allow(dead_code)]
17pub struct PdfStub {
18 pub title: String,
19 pub author: String,
20 pub objects: Vec<PdfObject>,
21 pub page_width_pt: f32,
22 pub page_height_pt: f32,
23}
24
25#[allow(dead_code)]
27pub fn new_pdf_stub(title: &str, author: &str) -> PdfStub {
28 PdfStub {
29 title: title.to_string(),
30 author: author.to_string(),
31 objects: Vec::new(),
32 page_width_pt: 595.0,
33 page_height_pt: 842.0,
34 }
35}
36
37#[allow(dead_code)]
39pub fn add_content_stream(stub: &mut PdfStub, content: &str) -> u32 {
40 let number = (stub.objects.len() + 1) as u32;
41 stub.objects.push(PdfObject {
42 number,
43 content: content.as_bytes().to_vec(),
44 });
45 number
46}
47
48#[allow(dead_code)]
50pub fn export_pdf_stub(stub: &PdfStub) -> Vec<u8> {
51 let mut out: Vec<u8> = Vec::new();
52 out.extend_from_slice(b"%PDF-1.4\n");
53 let mut offsets: Vec<usize> = Vec::new();
54 for obj in &stub.objects {
55 offsets.push(out.len());
56 let header = format!(
57 "{} 0 obj\n<< /Length {} >>\nstream\n",
58 obj.number,
59 obj.content.len()
60 );
61 out.extend_from_slice(header.as_bytes());
62 out.extend_from_slice(&obj.content);
63 out.extend_from_slice(b"\nendstream\nendobj\n");
64 }
65 let xref_offset = out.len();
66 out.extend_from_slice(b"xref\n");
67 let total = stub.objects.len() + 1;
68 out.extend_from_slice(format!("0 {}\n", total).as_bytes());
69 out.extend_from_slice(b"0000000000 65535 f \n");
70 for &off in &offsets {
71 out.extend_from_slice(format!("{:010} 00000 n \n", off).as_bytes());
72 }
73 let trailer = format!(
74 "trailer\n<< /Size {} /Root 1 0 R /Info << /Title ({}) /Author ({}) >> >>\nstartxref\n{}\n%%EOF\n",
75 total, stub.title, stub.author, xref_offset
76 );
77 out.extend_from_slice(trailer.as_bytes());
78 out
79}
80
81#[allow(dead_code)]
83pub fn pdf_object_count(stub: &PdfStub) -> usize {
84 stub.objects.len()
85}
86
87#[allow(dead_code)]
89pub fn pdf_estimated_size(stub: &PdfStub) -> usize {
90 export_pdf_stub(stub).len()
91}
92
93#[allow(dead_code)]
95pub fn add_text_stream(stub: &mut PdfStub, text: &str, x: f32, y: f32) -> u32 {
96 let stream = format!("BT /F1 12 Tf {:.2} {:.2} Td ({}) Tj ET", x, y, text);
97 add_content_stream(stub, &stream)
98}
99
100#[allow(dead_code)]
102pub fn is_valid_pdf_header(bytes: &[u8]) -> bool {
103 bytes.starts_with(b"%PDF-")
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn pdf_starts_with_magic() {
112 let stub = new_pdf_stub("Test", "Author");
113 let bytes = export_pdf_stub(&stub);
114 assert!(is_valid_pdf_header(&bytes));
115 }
116
117 #[test]
118 fn pdf_ends_with_eof() {
119 let stub = new_pdf_stub("Test", "Author");
120 let bytes = export_pdf_stub(&stub);
121 let s = String::from_utf8_lossy(&bytes);
122 assert!(s.contains("%%EOF"));
123 }
124
125 #[test]
126 fn add_content_increases_count() {
127 let mut stub = new_pdf_stub("Test", "Author");
128 add_content_stream(&mut stub, "q Q");
129 assert_eq!(pdf_object_count(&stub), 1);
130 }
131
132 #[test]
133 fn object_numbers_sequential() {
134 let mut stub = new_pdf_stub("Test", "Author");
135 let n1 = add_content_stream(&mut stub, "q Q");
136 let n2 = add_content_stream(&mut stub, "q Q");
137 assert_eq!(n2, n1 + 1);
138 }
139
140 #[test]
141 fn pdf_contains_xref() {
142 let stub = new_pdf_stub("Test", "Author");
143 let bytes = export_pdf_stub(&stub);
144 let s = String::from_utf8_lossy(&bytes);
145 assert!(s.contains("xref"));
146 }
147
148 #[test]
149 fn pdf_contains_title() {
150 let stub = new_pdf_stub("MyTitle", "Author");
151 let bytes = export_pdf_stub(&stub);
152 let s = String::from_utf8_lossy(&bytes);
153 assert!(s.contains("MyTitle"));
154 }
155
156 #[test]
157 fn pdf_estimated_size_positive() {
158 let stub = new_pdf_stub("Test", "Author");
159 assert!(pdf_estimated_size(&stub) > 0);
160 }
161
162 #[test]
163 fn add_text_stream_creates_object() {
164 let mut stub = new_pdf_stub("Test", "Author");
165 add_text_stream(&mut stub, "Hello", 100.0, 700.0);
166 assert_eq!(pdf_object_count(&stub), 1);
167 }
168
169 #[test]
170 fn stream_content_in_output() {
171 let mut stub = new_pdf_stub("Test", "Author");
172 add_content_stream(&mut stub, "q Q");
173 let bytes = export_pdf_stub(&stub);
174 let s = String::from_utf8_lossy(&bytes);
175 assert!(s.contains("q Q"));
176 }
177
178 #[test]
179 fn empty_stub_valid_header() {
180 let stub = new_pdf_stub("Empty", "Nobody");
181 let bytes = export_pdf_stub(&stub);
182 assert!(is_valid_pdf_header(&bytes));
183 }
184}