mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-30 13:08:48 -06:00
* Build each crate's integration tests as one binary Every tests/*.rs file is its own executable that links the whole crate graph. Move each crate's test files into tests/it/ as modules of one tests/it/main.rs, for apps/pdfcraft, apps/pdfcraft-cli, automation, cos, crypt, engine, render, sign and ui-egui. ui-egui's ui_group binary and the support modules move into tests/it/ too. Crates with a single test file keep it. The test executables go from 150 to 52, and every crate runs the same number of tests as before. ui-egui perf keeps its own binary (own process): its frame-time budgets would charge each measurement for the other tests running beside it. What the move needs: - include_str!/include_bytes! and #[path] paths gain one "../". - ui-egui: one GPU lock in tests/it/main.rs replaces the five per-file ones (content, document_links, editing, fill_sign, view), so one process creates one wgpu device at a time. The drop-line test in ui.rs and the screenshot blocks behind PDFCRAFT_*_SHOT(S) and CRAFT_UI_DOCKING_DIR, which render without holding it, take it too. - pdfcraft winit_wayland_scroll: the vendored winit source it compiles names crate::event, so that shim lives in main.rs. - parity evidence names the new paths. * Name the one-binary test paths in docs and comments - READMEs, docs, ATTRIBUTION and source comments name tests/it/<file>.rs. - The oracle README's example filters with --test it encryption::qpdf_agrees. - pdfcraft's GPU lock comment points at the UI tests' shared lock in crates/ui-egui/tests/it/main.rs.
149 lines
6.4 KiB
Rust
149 lines
6.4 KiB
Rust
//! Issue #740: column select, as in Acrobat. Alt-drag (Option-drag on macOS) with the Select tool,
|
||
//! or a plain drag with Edit ▸ Column select, selects only the text inside the rectangle drawn.
|
||
|
||
use egui::{Event, Modifiers, PointerButton, Pos2, vec2};
|
||
use egui_kittest::Harness;
|
||
use pdfcraft_ui_egui::PdfCraftApp;
|
||
|
||
/// One 300×200 page with a three-row table, "Name Qty / Apple 12 / Pear 7". Its columns sit far
|
||
/// enough apart that reading order takes one column, then the other: glyphs 0–3 "Name", 4–8
|
||
/// "Apple", 9–12 "Pear", 13–15 "Qty", 16–17 "12", 18 "7".
|
||
fn table() -> Vec<u8> {
|
||
let content =
|
||
"BT /F1 14 Tf 20 150 Td (Name) Tj 130 0 Td (Qty) Tj -130 -24 Td (Apple) Tj 130 0 Td (12) Tj -130 -24 Td (Pear) Tj 130 0 Td (7) Tj ET";
|
||
format!(
|
||
"%PDF-1.7
|
||
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
|
||
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
|
||
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 300 200] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> endobj
|
||
4 0 obj << /Length {} >> stream
|
||
{content}
|
||
endstream endobj
|
||
5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj
|
||
trailer << /Root 1 0 R >>
|
||
%%EOF",
|
||
content.len()
|
||
)
|
||
.into_bytes()
|
||
}
|
||
|
||
fn harness(setup: impl FnOnce(&mut PdfCraftApp) + 'static) -> Harness<'static, PdfCraftApp> {
|
||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).with_step_dt(1.0 / 60.0).build_eframe(move |_cc| {
|
||
let mut app = PdfCraftApp::new();
|
||
app.set_option("language", "en").unwrap();
|
||
app.open_bytes("table.pdf", None, table()).expect("opens");
|
||
app.set_option("left", "closed").unwrap();
|
||
app.set_option("author", "Tester").unwrap();
|
||
setup(&mut app);
|
||
app
|
||
});
|
||
// Wait for the text layer itself (#1277): under load the first render takes longer than a
|
||
// fixed second, and nothing may be pending yet before the text layer is asked for.
|
||
crate::support::wait_until(&mut h, "the page's text layer", |app| app.views[0].glyph_screen_pos(0, 0).is_some() && !app.render_pending());
|
||
h
|
||
}
|
||
|
||
fn glyph(h: &Harness<'static, PdfCraftApp>, g: usize) -> Pos2 {
|
||
h.state().views[0].glyph_screen_pos(0, g).expect("glyph on screen")
|
||
}
|
||
|
||
fn text(h: &Harness<'static, PdfCraftApp>) -> Option<String> {
|
||
h.state().views[0].selected_text()
|
||
}
|
||
|
||
/// Press at `from`, move to `to` and release there, with `modifiers` held throughout.
|
||
fn drag(h: &mut Harness<'static, PdfCraftApp>, from: Pos2, to: Pos2, modifiers: Modifiers) {
|
||
h.event(Event::ModifiersChanged(modifiers));
|
||
h.hover_at(from);
|
||
h.run_steps(1);
|
||
h.event(Event::PointerButton { pos: from, button: PointerButton::Primary, pressed: true, modifiers });
|
||
h.run_steps(1);
|
||
for k in 1..=4 {
|
||
h.hover_at(from + (to - from) * (k as f32 / 4.0));
|
||
h.run_steps(1);
|
||
}
|
||
h.event(Event::PointerButton { pos: to, button: PointerButton::Primary, pressed: false, modifiers });
|
||
h.run_steps(1);
|
||
h.event(Event::ModifiersChanged(Modifiers::NONE));
|
||
h.run_steps(1);
|
||
}
|
||
|
||
/// Below-right of the top two rows: past the "y" of "Qty" (the wider cell), level with "12".
|
||
fn below_right_of_row_two(h: &Harness<'static, PdfCraftApp>) -> Pos2 {
|
||
egui::pos2(glyph(h, 15).x.max(glyph(h, 17).x) + 12.0, glyph(h, 17).y + 8.0)
|
||
}
|
||
|
||
/// From above-left of "Name" to below-right of "12": the top two rows of both columns.
|
||
fn top_two_rows(h: &Harness<'static, PdfCraftApp>) -> (Pos2, Pos2) {
|
||
(glyph(h, 0) - vec2(12.0, 12.0), below_right_of_row_two(h))
|
||
}
|
||
|
||
#[test]
|
||
fn alt_drag_selects_only_the_text_inside_the_rectangle() {
|
||
let mut h = harness(|_| {});
|
||
let (from, to) = top_two_rows(&h);
|
||
// A plain drag selects in reading order: everything from "Name" to "12".
|
||
drag(&mut h, from, to, Modifiers::NONE);
|
||
assert_eq!(text(&h).as_deref(), Some("Name\nApple\nPear\nQty\n12"));
|
||
h.run_steps(60);
|
||
// Alt (Option on macOS) at the start of the drag: only the rectangle, row by row.
|
||
drag(&mut h, from, to, Modifiers::ALT);
|
||
assert_eq!(text(&h).as_deref(), Some("Name\tQty\nApple\t12"));
|
||
// One column of numbers, which is what the issue asked for.
|
||
h.run_steps(60);
|
||
let (from, to) = (glyph(&h, 13) - vec2(8.0, 12.0), egui::pos2(glyph(&h, 15).x + 12.0, glyph(&h, 18).y + 12.0));
|
||
drag(&mut h, from, to, Modifiers::ALT);
|
||
assert_eq!(text(&h).as_deref(), Some("Qty\n12\n7"));
|
||
}
|
||
|
||
#[test]
|
||
fn the_column_select_tool_needs_no_modifier() {
|
||
let mut h = harness(|app| assert!(app.execute("edit.column_select")));
|
||
assert_eq!(h.state().quick_tool, pdfcraft_ui_egui::QuickTool::ColumnSelect);
|
||
let (from, to) = top_two_rows(&h);
|
||
drag(&mut h, from, to, Modifiers::NONE);
|
||
assert_eq!(text(&h).as_deref(), Some("Name\tQty\nApple\t12"));
|
||
// A rectangle around no text selects nothing.
|
||
h.run_steps(60);
|
||
let below = glyph(&h, 12) + vec2(0.0, 60.0);
|
||
drag(&mut h, below, below + vec2(60.0, 30.0), Modifiers::NONE);
|
||
assert_eq!(text(&h), None);
|
||
}
|
||
|
||
/// ⇧-click extends a reading-order selection; a column selection has no anchor glyph, so a
|
||
/// ⇧-click clears it like a plain click.
|
||
#[test]
|
||
fn shift_click_clears_a_column_selection() {
|
||
let mut h = harness(|_| {});
|
||
let (from, to) = top_two_rows(&h);
|
||
drag(&mut h, from, to, Modifiers::ALT);
|
||
assert!(text(&h).is_some());
|
||
h.run_steps(60);
|
||
let at = glyph(&h, 10);
|
||
h.event(Event::ModifiersChanged(Modifiers::SHIFT));
|
||
h.hover_at(at);
|
||
h.run_steps(1);
|
||
for pressed in [true, false] {
|
||
h.event(Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Modifiers::SHIFT });
|
||
}
|
||
h.run_steps(1);
|
||
h.event(Event::ModifiersChanged(Modifiers::NONE));
|
||
h.run_steps(1);
|
||
assert_eq!(text(&h), None);
|
||
}
|
||
|
||
/// Alt-drag with the highlighter marks only the cells inside the rectangle: one quad per piece
|
||
/// of a line. The drag starts on text, since off text the highlighter draws an area highlight.
|
||
#[test]
|
||
fn alt_drag_with_the_highlighter_marks_only_the_column() {
|
||
let mut h = harness(|app| assert!(app.execute("comment.highlight")));
|
||
// Just above-left of the centre of "N", still on the glyph.
|
||
let (from, to) = (glyph(&h, 0) - vec2(2.0, 2.0), below_right_of_row_two(&h));
|
||
drag(&mut h, from, to, Modifiers::ALT);
|
||
h.run_steps(3);
|
||
let app = h.state();
|
||
let doc = app.session.get(app.views[0].id).unwrap();
|
||
let marks: Vec<(&str, usize)> = doc.info.annotations.iter().map(|a| (a.subtype.as_str(), a.quads.len())).collect();
|
||
assert_eq!(marks, [("Highlight", 4)], "Name, Qty, Apple and 12, each its own quad");
|
||
}
|