diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-03 12:22:52 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-10 09:17:07 -0300 |
| commit | 79d5b445085ec8c7bdbe430a88f9386249e7e867 (patch) | |
| tree | 7d6820a7b46483636c9db0c0ce286447bd835565 | |
| parent | 7452ff1caf51942ba9561acf2aa284d50435b8c3 (diff) | |
| download | pardes-79d5b445085ec8c7bdbe430a88f9386249e7e867.tar.gz pardes-79d5b445085ec8c7bdbe430a88f9386249e7e867.zip | |
make filtered PDF tint the default
| -rw-r--r-- | src/pardes.zig | 58 | ||||
| -rw-r--r-- | test/image_harness.zig | 60 |
2 files changed, 64 insertions, 54 deletions
diff --git a/src/pardes.zig b/src/pardes.zig index d490c6f3..324a9603 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -378,11 +378,17 @@ test "MuPDF pane renders, navigates, searches, and round-trips its page" { p.setPdfPage(pane, next_page); try std.testing.expectEqualStrings(long_query, pane.pdf.?.search_query); + // PDF dumps intentionally retain the existing image-compatible schema: + // page/path are restored, while pane-local tint starts from the fresh-PDF + // default rather than being serialized in the image record. + p.togglePdfTint(pane); + try std.testing.expectEqual(PdfTintMode.full, pane.pdf.?.tint); try p.dumpState(); const restored = try Pardes.initFromDump(gpa, .{}, p.dump_out.?); defer restored.deinit(); try std.testing.expect(hasPdf(restored.panes[0].?)); try std.testing.expectEqual(pane.pdf.?.page, restored.panes[0].?.pdf.?.page); + try std.testing.expectEqual(PdfTintMode.filtered, restored.panes[0].?.pdf.?.tint); } test "PDF normal adapter consumes unsupported actions and navigates page fallback" { @@ -534,29 +540,31 @@ test "PdfTint cycles pane-local state and exposes it in the live PDF tag" { const tint_builtin = std.meta.stringToEnum(Builtin, "PdfTint") orelse return error.MissingPdfTintBuiltin; - try std.testing.expectEqual(PdfTintMode.disabled, pv.tint); + try std.testing.expectEqual(PdfTintMode.filtered, pv.tint); const initial_tag = try p.tagText(p.scratch.allocator(), pane); try std.testing.expect(std.mem.indexOf( u8, initial_tag, - "width PdfFit disabled PdfTint", + "width PdfFit filtered PdfTint", ) != null); p.runBuiltin(tint_builtin, 0, "", null); - try std.testing.expectEqual(PdfTintMode.filtered, pv.tint); - const filtered_tag = try p.tagText(p.scratch.allocator(), pane); - try std.testing.expect(std.mem.indexOf(u8, filtered_tag, "filtered PdfTint") != null); + try std.testing.expectEqual(PdfTintMode.full, pv.tint); + const full_tag = try p.tagText(p.scratch.allocator(), pane); + try std.testing.expect(std.mem.indexOf(u8, full_tag, "full PdfTint") != null); // The launch companion is a terminal. Running the same pane-scoped word // there cannot mutate the PDF next to it. try std.testing.expect(!hasPdf(p.panes[1].?)); p.runBuiltin(tint_builtin, 1, "", null); - try std.testing.expectEqual(PdfTintMode.filtered, pv.tint); - - p.runBuiltin(tint_builtin, 0, "", null); try std.testing.expectEqual(PdfTintMode.full, pv.tint); + p.runBuiltin(tint_builtin, 0, "", null); try std.testing.expectEqual(PdfTintMode.disabled, pv.tint); + const disabled_tag = try p.tagText(p.scratch.allocator(), pane); + try std.testing.expect(std.mem.indexOf(u8, disabled_tag, "disabled PdfTint") != null); + p.runBuiltin(tint_builtin, 0, "", null); + try std.testing.expectEqual(PdfTintMode.filtered, pv.tint); try std.testing.expectEqual(PdfFitMode.width, pv.fit); // `dark` intentionally leaves page bg/fg null. PDF tint resolves those @@ -590,8 +598,9 @@ test "PDF tint and tinted theme changes rebuild every visible raster only" { const viewport = p.pdfViewport(pane).?; pv.document_scroll_y = @floatFromInt(pv.page_starts[1] -| viewport.pixel_h / 2); _ = frame.reset(.retain_capacity); - const plain_surface = try p.render(frame.allocator()); - try std.testing.expectEqual(@as(usize, 2), plain_surface.nimages); + const default_surface = try p.render(frame.allocator()); + try std.testing.expectEqual(PdfTintMode.filtered, pv.tint); + try std.testing.expectEqual(@as(usize, 2), default_surface.nimages); try std.testing.expectEqual(@as(usize, 2), pv.rasters.items.len); const Snapshot = struct { @@ -632,20 +641,9 @@ test "PDF tint and tinted theme changes rebuild every visible raster only" { } } }; - const plain = try Capture.get(plain_surface); - - p.togglePdfTint(pane); - try std.testing.expectEqual(PdfTintMode.filtered, pv.tint); - for (pv.rasters.items) |raster| try std.testing.expect(!raster.tried); - _ = frame.reset(.retain_capacity); - const filtered_surface = try p.render(frame.allocator()); - const filtered = try Capture.get(filtered_surface); - try Capture.expectGeometry(plain, filtered); - try std.testing.expectEqual(@as(usize, 2), pv.rasters.items.len); - for (plain, filtered) |old, new| { - try std.testing.expect(new.revision > old.revision); - try std.testing.expect(new.checksum != old.checksum); - } + const default_filtered = try Capture.get(default_surface); + for (pv.rasters.items) |raster| + try std.testing.expectEqual(PdfTintMode.filtered, raster.tint_key.?.mode); const dark_index = for (themes, 0..) |th, i| { if (std.mem.eql(u8, th.name, "dark")) break i; @@ -655,8 +653,8 @@ test "PDF tint and tinted theme changes rebuild every visible raster only" { _ = frame.reset(.retain_capacity); const themed_surface = try p.render(frame.allocator()); const themed = try Capture.get(themed_surface); - try Capture.expectGeometry(filtered, themed); - for (filtered, themed) |old, new| { + try Capture.expectGeometry(default_filtered, themed); + for (default_filtered, themed) |old, new| { try std.testing.expect(new.revision > old.revision); try std.testing.expect(new.checksum != old.checksum); } @@ -695,9 +693,9 @@ test "PDF tint and tinted theme changes rebuild every visible raster only" { const disabled_surface = try p.render(frame.allocator()); const disabled = try Capture.get(disabled_surface); try Capture.expectGeometry(full, disabled); - for (plain, disabled) |original, restored| { - try std.testing.expect(restored.revision > original.revision); - try std.testing.expectEqual(original.checksum, restored.checksum); + for (full, disabled) |old, source| { + try std.testing.expect(source.revision > old.revision); + try std.testing.expect(source.checksum != old.checksum); } const acme_index = for (themes, 0..) |th, i| { @@ -3132,7 +3130,7 @@ const PdfView = if (pdf_enabled) struct { layout_anchor_fraction: f64 = 0, next_raster_revision: u32 = 0, fit: PdfFitMode = .width, - tint: PdfTintMode = .disabled, + tint: PdfTintMode = .filtered, /// Normalized 0..65535 pan positions. NativeGeometry maps the extrema /// exactly onto the source overflow, independent of page or cell size. pan_x: u16 = 0, diff --git a/test/image_harness.zig b/test/image_harness.zig index 73142459..d4cb8955 100644 --- a/test/image_harness.zig +++ b/test/image_harness.zig @@ -22,8 +22,11 @@ extern "c" fn unsetenv(name: [*:0]const u8) c_int; const gpa = std.heap.page_allocator; const fixture_rgb = [3]u8{ 13, 77, 231 }; -const pdf_red = [3]u8{ 255, 0, 0 }; -const pdf_green = [3]u8{ 0, 255, 0 }; +// The harness isolates configuration, so fresh PDFs use the boot `helix` +// theme (#080808 paper, #bdbdbd ink). These are the exact fixed-point +// filtered-tint results for the fixture's saturated red and green pages. +const pdf_filtered_red = [3]u8{ 255, 77, 77 }; +const pdf_filtered_green = [3]u8{ 0, 162, 0 }; pub fn main(init: std.process.Init) !void { var arena_state: std.heap.ArenaAllocator = .init(gpa); @@ -240,11 +243,14 @@ fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) _ = try h.pumpOnce(50); const sent = std.mem.indexOf(u8, h.raw.items, transmit) != null; const placed = std.mem.indexOf(u8, h.raw.items, "\x1b_Ga=p,i=") != null; - if (sent and placed and h.contains("pdf 1/4 width PdfFit")) break; + if (sent and placed and h.contains("pdf 1/4 width PdfFit filtered PdfTint")) break; } try h.expectRawContains(transmit, "first PDF page was not transmitted as the 96x64 Kitty raster"); try h.expectRawContains("\x1b_Ga=p,i=", "first PDF page was not placed in the pane body"); - try h.expectContains("pdf 1/4 width PdfFit", "PDF pane did not expose its initial width fit"); + try h.expectContains( + "pdf 1/4 width PdfFit filtered PdfTint", + "fresh PDF pane did not expose filtered tint as its default", + ); // Fit-width is the initial PDF policy. The short fixture pages make the // first boundary visible immediately, so both page rasters must coexist. @@ -472,27 +478,27 @@ fn runGuiPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !v var h = try eh.Harness.initArgs(gpa, exe, 10, 20, pdf_path); defer h.deinit(); - const fit_width = try waitForCaptureBounds(&h, capture_path, pdf_red, 500, 5000); - const initial_green = try waitForCaptureBounds(&h, capture_path, pdf_green, 500, 5000); - try expectThemeGap(capture_path, fit_width, initial_green); + const fit_width = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 500, 5000); + const initial_green = try waitForCaptureBounds(&h, capture_path, pdf_filtered_green, 500, 5000); + try expectThemeGap(capture_path, fit_width, initial_green, pdf_filtered_red, pdf_filtered_green); // One row moves both native quads by the exact document distance. They // remain together on screen, separated by the same theme-colored gap; a // page-at-a-time implementation would replace red with green here. _ = libc.unlink(capture_path); try h.send("j"); - const moved_red = try waitForCaptureBounds(&h, capture_path, pdf_red, 500, 5000); - const moved_green = try waitForCaptureBounds(&h, capture_path, pdf_green, 500, 5000); + const moved_red = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 500, 5000); + const moved_green = try waitForCaptureBounds(&h, capture_path, pdf_filtered_green, 500, 5000); if (moved_red.max_y >= fit_width.max_y or moved_green.min_y >= initial_green.min_y) return error.SdlPdfContinuousStepDidNotMove; - try expectThemeGap(capture_path, moved_red, moved_green); + try expectThemeGap(capture_path, moved_red, moved_green, pdf_filtered_red, pdf_filtered_green); // Fit-height fills/crops the full body height. These are GPU readback // extents, so this exercises SDL's UV crop and destination rectangle // rather than merely observing the page color. _ = libc.unlink(capture_path); try h.send(" tz"); - const fit_height = try waitForCaptureBounds(&h, capture_path, pdf_red, 500, 5000); + const fit_height = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 500, 5000); const width_delta = if (fit_width.width() > fit_height.width()) fit_width.width() - fit_height.width() else @@ -507,17 +513,17 @@ fn runGuiPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !v // leave page two at the top, proving continuous traversal in GPU output. _ = libc.unlink(capture_path); try h.send("j"); - const height_red = try waitForCaptureBounds(&h, capture_path, pdf_red, 500, 5000); - const height_green = try waitForCaptureBounds(&h, capture_path, pdf_green, 200, 5000); - try expectThemeGap(capture_path, height_red, height_green); + const height_red = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 500, 5000); + const height_green = try waitForCaptureBounds(&h, capture_path, pdf_filtered_green, 200, 5000); + try expectThemeGap(capture_path, height_red, height_green, pdf_filtered_red, pdf_filtered_green); _ = libc.unlink(capture_path); try h.send("jjjjjjjj"); - const green = try waitForCaptureBounds(&h, capture_path, pdf_green, 500, 5000); + const green = try waitForCaptureBounds(&h, capture_path, pdf_filtered_green, 500, 5000); const final_capture = try readFile(gpa, capture_path); 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; + if (countColor(final_pixels, pdf_filtered_red) != 0) return error.SdlPdfOffscreenPageStillDrawn; // 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 @@ -525,22 +531,22 @@ fn runGuiPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !v // the nested page-one destination. _ = libc.unlink(capture_path); try h.send("gg"); - _ = try waitForCaptureBounds(&h, capture_path, pdf_red, 500, 5000); + _ = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 500, 5000); _ = libc.unlink(capture_path); try h.send(" ts"); - _ = try waitForCaptureBounds(&h, capture_path, pdf_red, 500, 5000); + _ = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 500, 5000); _ = libc.unlink(capture_path); try h.send("n"); - _ = try waitForCaptureBounds(&h, capture_path, pdf_red, 200, 5000); + _ = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 200, 5000); _ = libc.unlink(capture_path); try h.send("n"); - _ = try waitForCaptureBounds(&h, capture_path, pdf_red, 100, 5000); + _ = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 100, 5000); _ = libc.unlink(capture_path); try h.send("n"); - _ = try waitForCaptureBounds(&h, capture_path, pdf_green, 200, 5000); + _ = try waitForCaptureBounds(&h, capture_path, pdf_filtered_green, 200, 5000); _ = libc.unlink(capture_path); try h.send("N"); - _ = try waitForCaptureBounds(&h, capture_path, pdf_red, 100, 5000); + _ = try waitForCaptureBounds(&h, capture_path, pdf_filtered_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 }); } @@ -586,7 +592,13 @@ fn waitForCaptureBounds( return error.PdfPageColorMissing; } -fn expectThemeGap(path: [:0]const u8, upper: ColorBounds, lower: ColorBounds) !void { +fn expectThemeGap( + path: [:0]const u8, + upper: ColorBounds, + lower: ColorBounds, + upper_color: [3]u8, + lower_color: [3]u8, +) !void { if (lower.min_y <= upper.max_y + 1) return error.SdlPdfGapMissing; const gap = lower.min_y - upper.max_y - 1; if (gap < 7 or gap > 9) return error.SdlPdfGapWrongSize; @@ -598,7 +610,7 @@ fn expectThemeGap(path: [:0]const u8, upper: ColorBounds, lower: ColorBounds) !v const x1 = @min(upper.max_x, lower.max_x) -| 2; if (x0 > x1 or y >= ppm.height) return error.SdlPdfGapOutOfBounds; const first = ppm.pixels[(y * ppm.width + x0) * 3 ..][0..3]; - if (std.mem.eql(u8, first, &pdf_red) or std.mem.eql(u8, first, &pdf_green)) + if (std.mem.eql(u8, first, &upper_color) or std.mem.eql(u8, first, &lower_color)) return error.SdlPdfGapCovered; var x = x0 + 1; while (x <= x1) : (x += 1) { |
