summaryrefslogtreecommitdiff
path: root/test/image_harness.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-03 10:02:00 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit7452ff1caf51942ba9561acf2aa284d50435b8c3 (patch)
tree5be0d58e16b3ee9810377131f1d83c06f10b69bf /test/image_harness.zig
parentcd4a5b991104a2dc96a4bb7390451fb384376a9a (diff)
downloadpardes-7452ff1caf51942ba9561acf2aa284d50435b8c3.tar.gz
pardes-7452ff1caf51942ba9561acf2aa284d50435b8c3.zip
optimize and harness PDF sections navigation
Diffstat (limited to 'test/image_harness.zig')
-rw-r--r--test/image_harness.zig93
1 files changed, 85 insertions, 8 deletions
diff --git a/test/image_harness.zig b/test/image_harness.zig
index 4b1c527c..73142459 100644
--- a/test/image_harness.zig
+++ b/test/image_harness.zig
@@ -126,11 +126,11 @@ fn writePdf(arena: std.mem.Allocator, path: [:0]const u8) !void {
;
var out: std.ArrayList(u8) = .empty;
- var offsets: [12]usize = @splat(0);
+ var offsets: [16]usize = @splat(0);
try out.appendSlice(arena, "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n");
offsets[1] = out.items.len;
- try out.appendSlice(arena, "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
+ try out.appendSlice(arena, "1 0 obj\n<< /Type /Catalog /Pages 2 0 R /Outlines 12 0 R /PageMode /UseOutlines >>\nendobj\n");
offsets[2] = out.items.len;
try out.appendSlice(arena, "2 0 obj\n<< /Type /Pages /Count 4 /Kids [3 0 R 5 0 R 7 0 R 9 0 R] >>\nendobj\n");
offsets[3] = out.items.len;
@@ -151,12 +151,20 @@ fn writePdf(arena: std.mem.Allocator, path: [:0]const u8) !void {
try appendFmt(arena, &out, "10 0 obj\n<< /Length {d} >>\nstream\n{s}endstream\nendobj\n", .{ fourth_stream.len, fourth_stream });
offsets[11] = out.items.len;
try out.appendSlice(arena, "11 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n");
+ offsets[12] = out.items.len;
+ try out.appendSlice(arena, "12 0 obj\n<< /Type /Outlines /First 13 0 R /Last 15 0 R /Count 3 >>\nendobj\n");
+ offsets[13] = out.items.len;
+ try out.appendSlice(arena, "13 0 obj\n<< /Title (Upper) /Parent 12 0 R /First 14 0 R /Last 14 0 R /Next 15 0 R /Count 1 /Dest [3 0 R /XYZ 4 8 null] >>\nendobj\n");
+ offsets[14] = out.items.len;
+ try out.appendSlice(arena, "14 0 obj\n<< /Title (Lower) /Parent 13 0 R /Dest [3 0 R /XYZ 4 28 null] >>\nendobj\n");
+ offsets[15] = out.items.len;
+ try out.appendSlice(arena, "15 0 obj\n<< /Title (Page two) /Parent 12 0 R /Prev 13 0 R /Dest [5 0 R /XYZ 4 38 null] >>\nendobj\n");
const xref = out.items.len;
- try out.appendSlice(arena, "xref\n0 12\n0000000000 65535 f \n");
+ try out.appendSlice(arena, "xref\n0 16\n0000000000 65535 f \n");
for (offsets[1..]) |offset|
try appendFmt(arena, &out, "{d:0>10} 00000 n \n", .{offset});
- try appendFmt(arena, &out, "trailer\n<< /Size 12 /Root 1 0 R >>\nstartxref\n{d}\n%%EOF\n", .{xref});
+ try appendFmt(arena, &out, "trailer\n<< /Size 16 /Root 1 0 R >>\nstartxref\n{d}\n%%EOF\n", .{xref});
try eh.writeFile(path, out.items);
}
@@ -213,8 +221,12 @@ fn runKitty(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !vo
fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !void {
_ = unsetenv("PARDES_TEST");
_ = unsetenv("PARDES_TEST_CAPTURE_DIR");
- const pdf_path = try std.fmt.allocPrintSentinel(arena, "{s}/pages.pdf", .{base}, 0);
+ _ = base;
+ // Keep the generated location token short enough that the 38-column PDF
+ // pane can visibly include each outline title after `path:page:ordinal`.
+ const pdf_path = try std.fmt.allocPrintSentinel(arena, "/tmp/p{d}.pdf", .{libc.getpid()}, 0);
try writePdf(arena, pdf_path);
+ defer _ = libc.unlink(pdf_path);
var h = try eh.Harness.initArgs(gpa, exe, 16, 80, pdf_path);
defer h.deinit();
@@ -308,7 +320,8 @@ fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8)
try h.send("\x02");
try h.expectWaitFor("pdf 1/4 width PdfFit", 5000, "continuous page-up did not return to PDF page one");
try h.send("/SECOND\r");
- try h.expectWaitFor("pages.pdf:2:1 SECOND", 5000, "PDF search did not preserve the exact page-two query occurrence");
+ const search_row = try std.fmt.allocPrint(arena, "{s}:2:1 SECOND", .{std.fs.path.basename(pdf_path)});
+ try h.expectWaitFor(search_row, 5000, "PDF search did not preserve the exact page-two query occurrence");
const before_look = std.mem.count(u8, h.raw.items, transmit);
try h.send("n");
const look_deadline = eh.nowMs() + 5000;
@@ -337,7 +350,47 @@ fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8)
if (std.mem.count(u8, h.raw.items, transmit) != before_fit)
return error.KittyPdfFitRetransmitted;
- std.debug.print("PDF harness ok: Kitty continuous boundary + exact cache + search/fit\n", .{});
+ // The generated PDF carries a real nested outline. PdfSections must show
+ // all three DFS rows in its output pane while leaving n/N ownership on
+ // the PDF. The first two destinations share page one but have distinct
+ // page-space y coordinates; their placement APCs must differ while the
+ // second reveal reuses exactly the rasters made visible by the first.
+ try h.send(" ts");
+ try h.expectWaitFor("Upper", 5000, "PdfSections did not expose the root outline row");
+ try h.expectContains("Lower", "PdfSections did not expose the nested outline row");
+ try h.expectContains("Page two", "PdfSections did not expose the other-page outline row");
+
+ const upper_start = h.raw.items.len;
+ try h.send("n");
+ try h.expectWaitFor("pdf 1/4 height PdfFit", 5000, "first PdfSections n did not reveal page one");
+ const upper_deadline = eh.nowMs() + 3000;
+ while (eh.nowMs() < upper_deadline and kittyPlacementApc(h.raw.items, upper_start) == null)
+ _ = try h.pumpOnce(50);
+ const upper_apc = kittyPlacementApc(h.raw.items, upper_start) orelse
+ return error.KittyPdfSectionsUpperPlacementMissing;
+ const upper_copy = try arena.dupe(u8, upper_apc);
+ try h.pump(150);
+ const same_page_transmits = std.mem.count(u8, h.raw.items, "\x1b_Gf=32,");
+
+ const lower_start = h.raw.items.len;
+ try h.send("n");
+ try h.expectWaitFor("pdf 1/4 height PdfFit", 5000, "nested PdfSections n left page one");
+ const lower_deadline = eh.nowMs() + 3000;
+ while (eh.nowMs() < lower_deadline and kittyPlacementApc(h.raw.items, lower_start) == null)
+ _ = try h.pumpOnce(50);
+ const lower_apc = kittyPlacementApc(h.raw.items, lower_start) orelse
+ return error.KittyPdfSectionsLowerPlacementMissing;
+ if (std.mem.eql(u8, upper_copy, lower_apc))
+ return error.KittyPdfSectionsSamePagePlacementUnchanged;
+ if (std.mem.count(u8, h.raw.items, "\x1b_Gf=32,") != same_page_transmits)
+ return error.KittyPdfSectionsSamePageRetransmitted;
+
+ try h.send("n");
+ try h.expectWaitFor("pdf 2/4 height PdfFit", 5000, "third PdfSections n did not reveal page two");
+ try h.send("N");
+ try h.expectWaitFor("pdf 1/4 height PdfFit", 5000, "PdfSections N did not return to the nested page-one row");
+
+ std.debug.print("PDF harness ok: Kitty continuous cache + search/fit + nested PdfSections n/N\n", .{});
}
fn kittyPlacementApc(raw: []const u8, start: usize) ?[]const u8 {
@@ -465,7 +518,31 @@ fn runGuiPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !v
defer gpa.free(final_capture);
const final_pixels = ppmPixels(final_capture) orelse return error.BadCapture;
if (countColor(final_pixels, pdf_red) != 0) return error.SdlPdfOffscreenPageStillDrawn;
- std.debug.print("PDF harness ok: SDL continuous strip + theme gap + red->green ({d}->{d} pixels)\n", .{ fit_width.count, green.count });
+
+ // Exercise the actual PdfSections output + Look stepping path in SDL too.
+ // The first two nested outline rows stay on red page one at different y
+ // positions, the third moves to green page two, and N returns exactly to
+ // the nested page-one destination.
+ _ = libc.unlink(capture_path);
+ try h.send("gg");
+ _ = try waitForCaptureBounds(&h, capture_path, pdf_red, 500, 5000);
+ _ = libc.unlink(capture_path);
+ try h.send(" ts");
+ _ = try waitForCaptureBounds(&h, capture_path, pdf_red, 500, 5000);
+ _ = libc.unlink(capture_path);
+ try h.send("n");
+ _ = try waitForCaptureBounds(&h, capture_path, pdf_red, 200, 5000);
+ _ = libc.unlink(capture_path);
+ try h.send("n");
+ _ = try waitForCaptureBounds(&h, capture_path, pdf_red, 100, 5000);
+ _ = libc.unlink(capture_path);
+ try h.send("n");
+ _ = try waitForCaptureBounds(&h, capture_path, pdf_green, 200, 5000);
+ _ = libc.unlink(capture_path);
+ try h.send("N");
+ _ = try waitForCaptureBounds(&h, capture_path, pdf_red, 100, 5000);
+
+ std.debug.print("PDF harness ok: SDL continuous strip + nested PdfSections red->green->red ({d}->{d} pixels)\n", .{ fit_width.count, green.count });
}
const ColorBounds = struct {