1use crate::error::FormsError;
9use lopdf::{Document, Object, ObjectId, StringFormat};
10use std::collections::HashMap;
11
12pub const F8949_PDF_2025: &[u8] = include_bytes!("../forms/2025/f8949.pdf");
14pub const SCHEDULE_D_PDF_2025: &[u8] = include_bytes!("../forms/2025/schedule_d.pdf");
16pub const SCHEDULE_SE_PDF_2025: &[u8] = include_bytes!("../forms/2025/schedule_se.pdf");
18pub const F8283_PDF_2025: &[u8] = include_bytes!("../forms/2025/f8283.pdf");
20pub const F1040_PDF_2025: &[u8] = include_bytes!("../forms/2025/f1040.pdf");
22
23pub const F8949_PDF_2024: &[u8] = include_bytes!("../forms/2024/f8949.pdf");
25pub const SCHEDULE_D_PDF_2024: &[u8] = include_bytes!("../forms/2024/schedule_d.pdf");
27pub const SCHEDULE_SE_PDF_2024: &[u8] = include_bytes!("../forms/2024/schedule_se.pdf");
29pub const F8283_PDF_2024: &[u8] = include_bytes!("../forms/2024/f8283.pdf");
31pub const F8275_PDF_2024: &[u8] = include_bytes!("../forms/2024/f8275.pdf");
35pub const F1040_PDF_2024: &[u8] = include_bytes!("../forms/2024/f1040.pdf");
37pub const F8959_PDF_2024: &[u8] = include_bytes!("../forms/2024/f8959.pdf");
39pub const F8960_PDF_2024: &[u8] = include_bytes!("../forms/2024/f8960.pdf");
41pub const F8995_PDF_2024: &[u8] = include_bytes!("../forms/2024/f8995.pdf");
43pub const SCHEDULE_2_PDF_2024: &[u8] = include_bytes!("../forms/2024/f1040s2.pdf");
45pub const SCHEDULE_3_PDF_2024: &[u8] = include_bytes!("../forms/2024/f1040s3.pdf");
47pub const SCHEDULE_A_PDF_2024: &[u8] = include_bytes!("../forms/2024/f1040sa.pdf");
49pub const SCHEDULE_1_PDF_2024: &[u8] = include_bytes!("../forms/2024/f1040s1.pdf");
51pub const SCHEDULE_C_PDF_2024: &[u8] = include_bytes!("../forms/2024/f1040sc.pdf");
53pub const SCHEDULE_B_PDF_2024: &[u8] = include_bytes!("../forms/2024/f1040sb.pdf");
55
56pub const F8949_PDF_2017: &[u8] = include_bytes!("../forms/2017/f8949.pdf");
58pub const SCHEDULE_D_PDF_2017: &[u8] = include_bytes!("../forms/2017/schedule_d.pdf");
60pub const SCHEDULE_SE_PDF_2017: &[u8] = include_bytes!("../forms/2017/schedule_se.pdf");
62pub const F8283_PDF_2017: &[u8] = include_bytes!("../forms/2017/f8283.pdf");
64pub const F1040_PDF_2017: &[u8] = include_bytes!("../forms/2017/f1040.pdf");
66
67pub fn f8949_pdf(year: i32) -> Result<&'static [u8], FormsError> {
69 match year {
70 2017 => Ok(F8949_PDF_2017),
71 2024 => Ok(F8949_PDF_2024),
72 2025 => Ok(F8949_PDF_2025),
73 _ => Err(FormsError::UnsupportedYear(year)),
74 }
75}
76
77pub fn schedule_d_pdf(year: i32) -> Result<&'static [u8], FormsError> {
79 match year {
80 2017 => Ok(SCHEDULE_D_PDF_2017),
81 2024 => Ok(SCHEDULE_D_PDF_2024),
82 2025 => Ok(SCHEDULE_D_PDF_2025),
83 _ => Err(FormsError::UnsupportedYear(year)),
84 }
85}
86
87pub fn schedule_se_pdf(year: i32) -> Result<&'static [u8], FormsError> {
89 match year {
90 2017 => Ok(SCHEDULE_SE_PDF_2017),
91 2024 => Ok(SCHEDULE_SE_PDF_2024),
92 2025 => Ok(SCHEDULE_SE_PDF_2025),
93 _ => Err(FormsError::UnsupportedYear(year)),
94 }
95}
96
97pub fn f8959_pdf(year: i32) -> Result<&'static [u8], FormsError> {
99 match year {
100 2024 => Ok(F8959_PDF_2024),
101 _ => Err(FormsError::UnsupportedYear(year)),
102 }
103}
104
105pub fn f8960_pdf(year: i32) -> Result<&'static [u8], FormsError> {
107 match year {
108 2024 => Ok(F8960_PDF_2024),
109 _ => Err(FormsError::UnsupportedYear(year)),
110 }
111}
112
113pub fn f8995_pdf(year: i32) -> Result<&'static [u8], FormsError> {
115 match year {
116 2024 => Ok(F8995_PDF_2024),
117 _ => Err(FormsError::UnsupportedYear(year)),
118 }
119}
120
121pub fn schedule_2_pdf(year: i32) -> Result<&'static [u8], FormsError> {
123 match year {
124 2024 => Ok(SCHEDULE_2_PDF_2024),
125 _ => Err(FormsError::UnsupportedYear(year)),
126 }
127}
128
129pub fn schedule_3_pdf(year: i32) -> Result<&'static [u8], FormsError> {
131 match year {
132 2024 => Ok(SCHEDULE_3_PDF_2024),
133 _ => Err(FormsError::UnsupportedYear(year)),
134 }
135}
136
137pub fn schedule_b_pdf(year: i32) -> Result<&'static [u8], FormsError> {
139 match year {
140 2024 => Ok(SCHEDULE_B_PDF_2024),
141 _ => Err(FormsError::UnsupportedYear(year)),
142 }
143}
144
145pub fn schedule_c_pdf(year: i32) -> Result<&'static [u8], FormsError> {
147 match year {
148 2024 => Ok(SCHEDULE_C_PDF_2024),
149 _ => Err(FormsError::UnsupportedYear(year)),
150 }
151}
152
153pub fn schedule_1_pdf(year: i32) -> Result<&'static [u8], FormsError> {
155 match year {
156 2024 => Ok(SCHEDULE_1_PDF_2024),
157 _ => Err(FormsError::UnsupportedYear(year)),
158 }
159}
160
161pub fn schedule_a_pdf(year: i32) -> Result<&'static [u8], FormsError> {
163 match year {
164 2024 => Ok(SCHEDULE_A_PDF_2024),
165 _ => Err(FormsError::UnsupportedYear(year)),
166 }
167}
168
169pub fn f8283_pdf(year: i32) -> Result<&'static [u8], FormsError> {
171 match year {
172 2017 => Ok(F8283_PDF_2017),
173 2024 => Ok(F8283_PDF_2024),
174 2025 => Ok(F8283_PDF_2025),
175 _ => Err(FormsError::UnsupportedYear(year)),
176 }
177}
178
179pub fn f8275_pdf(year: i32) -> Result<&'static [u8], FormsError> {
185 match year {
186 2017 | 2024 | 2025 => Ok(F8275_PDF_2024),
187 _ => Err(FormsError::UnsupportedYear(year)),
188 }
189}
190
191pub fn f1040_pdf(year: i32) -> Result<&'static [u8], FormsError> {
193 match year {
194 2017 => Ok(F1040_PDF_2017),
195 2024 => Ok(F1040_PDF_2024),
196 2025 => Ok(F1040_PDF_2025),
197 _ => Err(FormsError::UnsupportedYear(year)),
198 }
199}
200
201#[derive(Debug, Clone)]
204pub struct Field {
205 pub id: ObjectId,
207 pub fqn: String,
209 pub rect: Option<[f32; 4]>,
211 pub is_button: bool,
213 pub max_len: Option<usize>,
221}
222
223impl Field {
224 pub fn cx(&self) -> Option<f32> {
226 self.rect.map(|r| (r[0] + r[2]) / 2.0)
227 }
228 pub fn cy(&self) -> Option<f32> {
230 self.rect.map(|r| (r[1] + r[3]) / 2.0)
231 }
232}
233
234#[derive(Debug, Clone)]
236pub enum FieldValue {
237 Text(String),
239 Check {
241 on: String,
243 },
244}
245
246pub fn load(bytes: &[u8]) -> Result<Document, FormsError> {
248 Ok(Document::load_mem(bytes)?)
249}
250
251fn number(o: &Object) -> Option<f32> {
252 match o {
253 Object::Integer(i) => Some(*i as f32),
254 Object::Real(r) => Some(*r),
255 _ => None,
256 }
257}
258
259fn rect_of(dict: &lopdf::Dictionary) -> Option<[f32; 4]> {
260 let arr = dict.get(b"Rect").ok()?.as_array().ok()?;
261 if arr.len() != 4 {
262 return None;
263 }
264 Some([
265 number(&arr[0])?,
266 number(&arr[1])?,
267 number(&arr[2])?,
268 number(&arr[3])?,
269 ])
270}
271
272fn acroform_id(doc: &Document) -> Result<ObjectId, FormsError> {
274 match doc.catalog()?.get(b"AcroForm") {
275 Ok(Object::Reference(id)) => Ok(*id),
276 Ok(_) => Err(FormsError::Structure(
277 "AcroForm is not an indirect reference".into(),
278 )),
279 Err(_) => Err(FormsError::Structure("catalog has no AcroForm".into())),
280 }
281}
282
283pub fn drop_xfa_and_set_needappearances(doc: &mut Document) -> Result<(), FormsError> {
286 let id = acroform_id(doc)?;
287 let acro = doc.get_dictionary_mut(id)?;
288 acro.remove(b"XFA");
289 acro.set("NeedAppearances", Object::Boolean(true));
290 Ok(())
291}
292
293pub fn collect_fields(doc: &Document) -> Result<Vec<Field>, FormsError> {
295 let acro = doc.get_dictionary(acroform_id(doc)?)?;
296 let mut out = Vec::new();
297 let fields = acro
298 .get(b"Fields")
299 .and_then(|o| o.as_array())
300 .map_err(|_| FormsError::Structure("AcroForm has no /Fields array".into()))?;
301 for f in fields {
302 if let Ok(id) = f.as_reference() {
303 walk(doc, id, "", None, None, &mut out)?;
304 }
305 }
306 Ok(out)
307}
308
309pub(crate) fn decode_pdf_text(b: &[u8]) -> String {
312 if b.len() >= 2 && b[0] == 0xFE && b[1] == 0xFF {
313 let units: Vec<u16> = b[2..]
314 .chunks(2)
315 .map(|c| ((c[0] as u16) << 8) | *c.get(1).unwrap_or(&0) as u16)
316 .collect();
317 String::from_utf16_lossy(&units)
318 } else {
319 b.iter().map(|&c| c as char).collect()
320 }
321}
322
323fn field_component_name(dict: &lopdf::Dictionary) -> Option<String> {
324 dict.get(b"T")
325 .ok()
326 .and_then(|o| o.as_str().ok())
327 .map(decode_pdf_text)
328}
329
330fn walk(
331 doc: &Document,
332 id: ObjectId,
333 parent_fqn: &str,
334 inherited_ft: Option<String>,
335 inherited_max_len: Option<usize>,
336 out: &mut Vec<Field>,
337) -> Result<(), FormsError> {
338 let dict = match doc.get_dictionary(id) {
339 Ok(d) => d,
340 Err(_) => return Ok(()), };
342 let name = field_component_name(dict);
343 let fqn = match &name {
344 Some(t) if parent_fqn.is_empty() => t.clone(),
345 Some(t) => format!("{parent_fqn}.{t}"),
346 None => parent_fqn.to_string(),
347 };
348 let ft = dict
349 .get(b"FT")
350 .ok()
351 .and_then(|o| o.as_name().ok())
352 .map(|b| String::from_utf8_lossy(b).into_owned())
353 .or(inherited_ft);
354
355 let max_len = dict
357 .get(b"MaxLen")
358 .ok()
359 .and_then(|o| o.as_i64().ok())
360 .and_then(|n| usize::try_from(n).ok())
361 .or(inherited_max_len);
362
363 let kids: Option<Vec<ObjectId>> = dict
365 .get(b"Kids")
366 .ok()
367 .and_then(|o| o.as_array().ok())
368 .map(|arr| arr.iter().filter_map(|k| k.as_reference().ok()).collect());
369 match kids {
370 Some(kids) if !kids.is_empty() => {
371 for k in kids {
372 walk(doc, k, &fqn, ft.clone(), max_len, out)?;
373 }
374 }
375 _ => {
376 out.push(Field {
377 id,
378 fqn,
379 rect: rect_of(dict),
380 is_button: ft.as_deref() == Some("Btn"),
381 max_len,
382 });
383 }
384 }
385 Ok(())
386}
387
388pub fn index(fields: &[Field]) -> HashMap<String, Field> {
390 fields.iter().map(|f| (f.fqn.clone(), f.clone())).collect()
391}
392
393fn encode_pdf_text(s: &str) -> Vec<u8> {
402 if s.is_ascii() {
403 return s.as_bytes().to_vec();
404 }
405 let mut out = Vec::with_capacity(2 + s.len() * 2);
406 out.extend_from_slice(&[0xFE, 0xFF]);
407 for unit in s.encode_utf16() {
408 out.push((unit >> 8) as u8);
409 out.push((unit & 0xFF) as u8);
410 }
411 out
412}
413
414pub fn apply_writes(
416 doc: &mut Document,
417 index: &HashMap<String, Field>,
418 writes: &[(String, FieldValue)],
419) -> Result<(), FormsError> {
420 for (fqn, value) in writes {
421 let field = index
422 .get(fqn)
423 .ok_or_else(|| FormsError::MapFieldMissing(fqn.clone()))?;
424 let dict = doc.get_dictionary_mut(field.id)?;
425 match value {
426 FieldValue::Text(s) => {
427 dict.set(
428 "V",
429 Object::String(encode_pdf_text(s), StringFormat::Literal),
430 );
431 }
432 FieldValue::Check { on } => {
433 dict.set("V", Object::Name(on.clone().into_bytes()));
434 dict.set("AS", Object::Name(on.clone().into_bytes()));
435 }
436 }
437 }
438 Ok(())
439}
440
441pub fn strip_nondeterminism(doc: &mut Document) {
445 if let Ok(info) = doc.trailer.get(b"Info").and_then(|o| o.as_reference()) {
446 if let Ok(d) = doc.get_dictionary_mut(info) {
447 d.remove(b"CreationDate");
448 d.remove(b"ModDate");
449 }
450 }
451 doc.trailer.remove(b"ID");
452}
453
454pub fn save(doc: &mut Document) -> Result<Vec<u8>, FormsError> {
456 let mut buf = Vec::new();
457 doc.save_to(&mut buf)?;
458 Ok(buf)
459}
460
461pub fn text_value(doc: &Document, id: ObjectId) -> Option<String> {
463 let v = doc.get_dictionary(id).ok()?.get(b"V").ok()?;
464 match v {
465 Object::String(b, _) => Some(decode_pdf_text(b)),
466 _ => None,
467 }
468}
469
470pub fn checkbox_on(doc: &Document, id: ObjectId) -> Option<String> {
472 let as_ = doc.get_dictionary(id).ok()?.get(b"AS").ok()?;
473 match as_ {
474 Object::Name(b) if b != b"Off" => Some(String::from_utf8_lossy(b).into_owned()),
475 _ => None,
476 }
477}
478
479pub fn button_on_states(doc: &Document, id: ObjectId) -> Vec<String> {
484 let mut out = Vec::new();
485 let Ok(dict) = doc.get_dictionary(id) else {
486 return out;
487 };
488 let ap = match dict.get(b"AP") {
489 Ok(Object::Reference(r)) => doc.get_dictionary(*r).ok(),
490 Ok(Object::Dictionary(d)) => Some(d),
491 _ => None,
492 };
493 let n = ap.and_then(|ap| match ap.get(b"N") {
494 Ok(Object::Reference(r)) => doc.get_dictionary(*r).ok(),
495 Ok(Object::Dictionary(d)) => Some(d),
496 _ => None,
497 });
498 if let Some(n) = n {
499 for (k, _) in n.iter() {
500 if k != b"Off" {
501 out.push(String::from_utf8_lossy(k).into_owned());
502 }
503 }
504 }
505 out.sort();
506 out
507}