Some checks failed
repo hygiene / hygiene (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
traffic / gates (push) Has been cancelled
traffic / nigig-traffic (push) Has been cancelled
traffic / supply-chain (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
nigig-map / test (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
p2p-intel / engine (push) Has been cancelled
p2p-intel / notifications (push) Has been cancelled
p2p-intel / coverage (push) Has been cancelled
p2p-intel / makepad-app (push) Has been cancelled
p2p-intel / exchange-tab (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
Whole-tree sync: cad-core/cad-ui split sources, nigig-build construction_frame migration, pdf port progress, mpesa/pay/uikit/doc updates, workspace members/profiles/lock, CI workflows and reviews. See individual file history for details.
170 lines
6.2 KiB
Rust
170 lines
6.2 KiB
Rust
//! 1:1 port of `dart-pdf/packages/pdf_graphics/test/render_command_codec_test.dart`
|
|
//! (tranche 1: synthetic round-trip, overprint tag, corpus round-trip, and
|
|
//! the f32 path precision contract).
|
|
//!
|
|
//! Codec fidelity is compared at the command level (`format_commands`):
|
|
//! a buffer that survives the wire byte-identical replays identically.
|
|
//! Replay itself is covered by `render_command_port.rs`.
|
|
//!
|
|
//! Not ported: worker state-scope compaction, image offload/placeholders
|
|
//! (nigig's wire always carries image bytes — no decline model), glyph
|
|
//! outline deduplication, decode regions, resolution caps, page budgets,
|
|
//! and raster-pixel helpers. Those are the background-worker render
|
|
//! pipeline, which nigig does not have.
|
|
|
|
use nigig_pdf_document::PdfDocument;
|
|
use nigig_pdf_graphics::content::parse_content_stream;
|
|
use nigig_pdf_graphics::path::PdfPathBuilder;
|
|
use nigig_pdf_graphics::recording::{format_commands, RecordingDevice, RenderCommand};
|
|
use nigig_pdf_graphics::wire::{decode, encode};
|
|
|
|
fn roundtrip(cmds: &[RenderCommand]) -> Vec<RenderCommand> {
|
|
let bytes = encode(cmds);
|
|
decode(&bytes).expect("decodes")
|
|
}
|
|
|
|
fn record(content: &str) -> Vec<RenderCommand> {
|
|
let ops = parse_content_stream(content.as_bytes()).expect("content parses");
|
|
RecordingDevice::from_ops(&ops)
|
|
}
|
|
|
|
#[test]
|
|
fn synthetic_buffers_round_trip() {
|
|
let cases = [
|
|
"q 2 0 0 2 10 10 cm 0 0 1 rg 5 5 20 30 re f 1 0 0 RG 4 w 0 0 10 10 re S Q",
|
|
"[3 2] 1.5 d 1 w 10 10 m 90 90 l S",
|
|
"0 0 5 5 re W n 0 0 1 rg 0 0 10 10 re f",
|
|
"BT /F1 24 Tf 72 720 Td (Hello, world!) Tj ET",
|
|
// Word spacing (Tw) with char spacing exercises kern-adjusted runs.
|
|
"BT /F1 10 Tf 5 Tw 0.2 Tc 72 700 Td ( ab ) Tj ET",
|
|
"q q 0 0 1 1 re f Q q 1 1 2 2 re f Q Q",
|
|
"10 10 m 20 30 40 30 50 10 c f",
|
|
"0 0 10 10 re 2 2 6 6 re f*",
|
|
];
|
|
for content in cases {
|
|
let original = record(content);
|
|
assert!(!original.is_empty(), "fixture should paint something");
|
|
let restored = roundtrip(&original);
|
|
assert_eq!(
|
|
format_commands(&restored),
|
|
format_commands(&original),
|
|
"round-trip diverged for {content:?}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn byte_output_is_stable_across_two_serializations() {
|
|
let original =
|
|
record("q 0 0 1 rg 5 5 20 30 re f Q BT /F1 12 Tf 10 10 Td (hi) Tj ET");
|
|
let a = encode(&original);
|
|
let b = encode(&original);
|
|
assert_eq!(a, b);
|
|
}
|
|
|
|
#[test]
|
|
fn overprint_state_round_trips_fill_stroke_mode() {
|
|
let commands = vec![
|
|
RenderCommand::SetOverprint { fill: true, stroke: false, mode: 1 },
|
|
RenderCommand::SetOverprint { fill: false, stroke: true, mode: 0 },
|
|
];
|
|
let restored = roundtrip(&commands);
|
|
assert_eq!(restored.len(), 2);
|
|
assert!(
|
|
matches!(
|
|
&restored[0],
|
|
RenderCommand::SetOverprint { fill: true, stroke: false, mode: 1 }
|
|
),
|
|
"got {:?}",
|
|
restored[0]
|
|
);
|
|
assert!(
|
|
matches!(
|
|
&restored[1],
|
|
RenderCommand::SetOverprint { fill: false, stroke: true, mode: 0 }
|
|
),
|
|
"got {:?}",
|
|
restored[1]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn path_coordinates_survive_at_f32_precision() {
|
|
// Geometry rides the wire as f32 (dart's Float32List exactness): the
|
|
// codec promises the f32 image, not the f64 original.
|
|
let mut b = PdfPathBuilder::new();
|
|
b.move_to(0.1, 0.2);
|
|
b.line_to(0.3, 1.0 / 3.0);
|
|
let path = b.take_path();
|
|
let restored = roundtrip(&[RenderCommand::Path(path)]);
|
|
assert_eq!(restored.len(), 1);
|
|
let RenderCommand::Path(back) = &restored[0] else {
|
|
panic!("expected a Path");
|
|
};
|
|
// Decoded coords equal the f32 truncation of the inputs, exactly.
|
|
let coords: Vec<f64> = back
|
|
.segments()
|
|
.iter()
|
|
.flat_map(|s| match *s {
|
|
nigig_pdf_graphics::path::PdfPathSegment::MoveTo { x, y }
|
|
| nigig_pdf_graphics::path::PdfPathSegment::LineTo { x, y } => vec![x, y],
|
|
nigig_pdf_graphics::path::PdfPathSegment::CubicTo { x1, y1, x2, y2, x3, y3 } => {
|
|
vec![x1, y1, x2, y2, x3, y3]
|
|
}
|
|
nigig_pdf_graphics::path::PdfPathSegment::Close => Vec::new(),
|
|
})
|
|
.collect();
|
|
for (got, want) in coords.iter().zip([0.1, 0.2, 0.3, 1.0 / 3.0]) {
|
|
assert_eq!(*got as f32, want as f32, "f32 image must match");
|
|
assert!(
|
|
(*got - want).abs() < 1e-6,
|
|
"f32 rounding only, got {got} want {want}"
|
|
);
|
|
}
|
|
}
|
|
|
|
// Real pages exercise the fragile callbacks. Nigig's wire always carries
|
|
// image bytes (no decline/placeholder model — a documented deviation from
|
|
// dart's cos-gated serialization), so every parseable page must round-trip.
|
|
const GHENT: &str = "/Users/aok/Projects/rustdev/CratesCode/dart-pdf/test_corpora/ghent/1-CMYK";
|
|
|
|
#[test]
|
|
fn corpus_buffers_round_trip() {
|
|
let files = [
|
|
"GWG168_Softmasks_Vector_part1_X4.pdf",
|
|
"GWG1610_Softmasks_Text_part1_X4.pdf",
|
|
"GWG160_Transp_Basic_BM_DeviceCMYK_Non-knockout_X4.pdf",
|
|
"GWG161_Transp_Basic_BM_DeviceCMYK_Knockout_X4.pdf",
|
|
"GWG060_Shading_x1a.pdf",
|
|
"GWG061_Shading_x1a.pdf",
|
|
];
|
|
let mut ran = 0;
|
|
for name in files {
|
|
let path = format!("{GHENT}/{name}");
|
|
let Ok(bytes) = std::fs::read(&path) else {
|
|
eprintln!("SKIP (missing corpus file): {path}");
|
|
continue;
|
|
};
|
|
let Ok(mut doc) = PdfDocument::parse(&bytes) else {
|
|
eprintln!("SKIP (unparseable — likely compressed xref): {path}");
|
|
continue;
|
|
};
|
|
for i in 0..doc.page_count() {
|
|
let Ok(page) = doc.page(i) else { continue };
|
|
let Ok(ops) = parse_content_stream(&page.content_data) else { continue };
|
|
let original = RecordingDevice::from_ops(&ops);
|
|
if original.is_empty() {
|
|
continue;
|
|
}
|
|
let restored = roundtrip(&original);
|
|
ran += 1;
|
|
assert_eq!(
|
|
format_commands(&restored),
|
|
format_commands(&original),
|
|
"{name} page {i} diverged after round-trip"
|
|
);
|
|
}
|
|
}
|
|
eprintln!("corpus_buffers_round_trip compared {ran} pages");
|
|
assert!(ran > 0, "no corpus pages ran — checkout missing?");
|
|
}
|