mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 21:11:52 +00:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
75 lines
3.7 KiB
Rust
75 lines
3.7 KiB
Rust
//! A contributor-original synthetic page: a check box widget listed before a filled square
|
||
//! comment that covers it. Form fields' widgets are drawn after the page's other annotations
|
||
//! (hayro-interpret patch (86)), so the check box shows; the comment covered it before.
|
||
|
||
use pdfcraft_render::{PageRenderer, RenderConfig, RenderRequest};
|
||
use std::sync::Arc;
|
||
|
||
/// A 200 × 200 pt page whose `/Annots` lists `order` of: `W`, a check box drawing a green box,
|
||
/// `S`, a square comment drawing a black one over it, and `R`, a larger red square comment.
|
||
fn pdf(order: &str) -> Vec<u8> {
|
||
let annot = |c: char| match c {
|
||
'W' => "6 0 R",
|
||
'S' => "7 0 R",
|
||
_ => "8 0 R",
|
||
};
|
||
let annots: Vec<&str> = order.chars().map(annot).collect();
|
||
let form = |rgb: &str| {
|
||
let content = format!("{rgb} rg 0 0 100 100 re f");
|
||
format!("<< /Type /XObject /Subtype /Form /BBox [0 0 100 100] /Length {} >>\nstream\n{content}\nendstream", content.len())
|
||
};
|
||
let objects = [
|
||
"<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [6 0 R] >> >>".to_string(),
|
||
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_string(),
|
||
format!("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Annots [{}] >>", annots.join(" ")),
|
||
form("0 1 0"),
|
||
form("0 0 0"),
|
||
"<< /Type /Annot /Subtype /Widget /FT /Btn /T (box) /F 4 /Rect [50 50 150 150] /AS /Off /AP << /N << /Off 4 0 R /Yes 4 0 R >> >> >>"
|
||
.to_string(),
|
||
"<< /Type /Annot /Subtype /Square /F 4 /C [0 0 0] /Rect [40 40 160 160] /AP << /N 5 0 R >> >>".to_string(),
|
||
"<< /Type /Annot /Subtype /Square /F 4 /C [1 0 0] /Rect [30 30 170 170] /AP << /N 9 0 R >> >>".to_string(),
|
||
form("1 0 0"),
|
||
];
|
||
let mut bytes = b"%PDF-1.7\n".to_vec();
|
||
let mut offsets = Vec::new();
|
||
for (i, object) in objects.iter().enumerate() {
|
||
offsets.push(bytes.len());
|
||
bytes.extend_from_slice(format!("{} 0 obj\n{object}\nendobj\n", i + 1).as_bytes());
|
||
}
|
||
let xref = bytes.len();
|
||
bytes.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objects.len() + 1).as_bytes());
|
||
for offset in offsets {
|
||
bytes.extend_from_slice(format!("{offset:010} 00000 n \n").as_bytes());
|
||
}
|
||
bytes.extend_from_slice(format!("trailer << /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objects.len() + 1).as_bytes());
|
||
bytes
|
||
}
|
||
|
||
/// The colour at the page's centre.
|
||
fn centre(order: &str) -> [u8; 3] {
|
||
at(order, 100, 100)
|
||
}
|
||
|
||
/// The colour at (`x`, `y`) px from the top left.
|
||
fn at(order: &str, x: usize, y: usize) -> [u8; 3] {
|
||
let mut renderer = PageRenderer::new(Arc::new(pdf(order)), RenderConfig::default());
|
||
let out = renderer.render(RenderRequest { page: 0, scale: 1.0, ..Default::default() });
|
||
assert!(out.error.is_none(), "{:?}", out.error);
|
||
let at = (y * out.width as usize + x) * 4;
|
||
let px = out.rgba.get(at..at + 3).expect("the centre lies on the page");
|
||
[px[0], px[1], px[2]]
|
||
}
|
||
|
||
#[test]
|
||
fn a_widget_is_drawn_above_the_comments_on_its_page() {
|
||
assert_eq!(centre("WS"), [0, 255, 0], "a check box listed before a comment over it");
|
||
assert_eq!(centre("SW"), [0, 255, 0], "a check box listed after it");
|
||
// Comments keep their `/Annots` order among themselves.
|
||
assert_eq!(centre("RS"), [0, 0, 0], "the black square listed last is on top");
|
||
assert_eq!(centre("SR"), [255, 0, 0], "the red square listed last is on top");
|
||
// And with a widget among them: (45, 45) is inside both squares, outside the check box.
|
||
assert_eq!(at("WRS", 45, 45), [0, 0, 0], "a widget first: the black square listed last is on top");
|
||
assert_eq!(at("WSR", 45, 45), [255, 0, 0], "a widget first: the red square listed last is on top");
|
||
assert_eq!(centre("WSR"), [0, 255, 0], "and the check box above both");
|
||
}
|