summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-02 20:50:04 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit5d5f2a25011c751452fb4ff42e28ee84d4bb4fdf (patch)
tree9078fed09c1eb4604d28b1e3ac57e26389e18ac3 /src/pardes.zig
parenta93b796a3389acaf45775d302f559e01c19ddbfc (diff)
downloadpardes-5d5f2a25011c751452fb4ff42e28ee84d4bb4fdf.tar.gz
pardes-5d5f2a25011c751452fb4ff42e28ee84d4bb4fdf.zip
tune PDF raster resolution per native backend
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig101
1 files changed, 90 insertions, 11 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index bc56f966..f32eb93f 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -40,6 +40,39 @@ pub const lsp = @import("lsp/lsp.zig");
pub const Platform = enum { tty, gui, web };
pub const platform: Platform = @field(Platform, @tagName(@import("pardes_config").platform));
+/// Native PDF quality is a shell property, but the core owns MuPDF and the
+/// RGBA cache. Kitty favors wire bandwidth; SDL favors physical-pixel text
+/// quality and asks the renderer to cover either fit axis without upscaling.
+pub const PdfRasterPolicy = struct {
+ dpi: u16,
+ max_dimension: u16,
+ match_viewport: bool,
+};
+
+pub const kitty_pdf_raster_policy: PdfRasterPolicy = .{
+ .dpi = 96,
+ .max_dimension = 1200,
+ .match_viewport = false,
+};
+pub const sdl_pdf_raster_policy: PdfRasterPolicy = .{
+ .dpi = 192,
+ .max_dimension = 4096,
+ .match_viewport = true,
+};
+pub const pdf_raster_policy: PdfRasterPolicy = switch (platform) {
+ .tty => kitty_pdf_raster_policy,
+ .gui => sdl_pdf_raster_policy,
+ .web => kitty_pdf_raster_policy,
+};
+
+test "SDL PDF raster policy is materially denser than Kitty" {
+ try std.testing.expect(sdl_pdf_raster_policy.dpi >= kitty_pdf_raster_policy.dpi * 2);
+ try std.testing.expect(sdl_pdf_raster_policy.max_dimension >
+ kitty_pdf_raster_policy.max_dimension * 3);
+ try std.testing.expect(!kitty_pdf_raster_policy.match_viewport);
+ try std.testing.expect(sdl_pdf_raster_policy.match_viewport);
+}
+
// The capacities and the two heights that are STRUCTURE, not taste: the
// fixed-size pane/column arrays, and the fact that the topbar and a tag are
// one row each (nothing here works at any other value). The layout numbers
@@ -108,6 +141,23 @@ test "MuPDF pane renders, navigates, searches, and round-trips its page" {
const first_place = first.images[0].?;
try std.testing.expect(first_place.rgba.len == first_place.iw * first_place.ih * 4);
try std.testing.expectEqual(image.NativeFit.width, first_place.native.fit);
+ const request = p.pdfRenderRequest(pane) orelse return error.MissingPdfRenderRequest;
+ try std.testing.expectEqual(pdf_raster_policy.dpi, request.dpi);
+ try std.testing.expectEqual(pdf_raster_policy.max_dimension, request.max_dimension);
+ try std.testing.expectEqual(request, pane.pdf.?.render_request);
+ try std.testing.expect(@max(first_place.iw, first_place.ih) <= request.max_dimension);
+ if (pdf_raster_policy.match_viewport) {
+ const viewport = p.pdfViewport(pane) orelse return error.MissingPdfViewport;
+ try std.testing.expectEqual(viewport.pixel_w, request.minimum_width);
+ try std.testing.expectEqual(viewport.pixel_h, request.minimum_height);
+ try std.testing.expect(first_place.iw >= viewport.pixel_w or
+ @max(first_place.iw, first_place.ih) == request.max_dimension);
+ try std.testing.expect(first_place.ih >= viewport.pixel_h or
+ @max(first_place.iw, first_place.ih) == request.max_dimension);
+ } else {
+ try std.testing.expectEqual(@as(u32, 0), request.minimum_width);
+ try std.testing.expectEqual(@as(u32, 0), request.minimum_height);
+ }
// The default reading view moves one visible cell row without replacing
// the page pixels. Fit/pan are placement state, not texture identity.
@@ -426,7 +476,9 @@ test "PDF native mouse selection, Look, and highlights share page geometry" {
try std.testing.expect(searched.images[0].?.native.revision != selected_revision);
// Fit and viewport changes can crop a hit which was already revealed.
- // Both rearm the placement-only reveal without forcing new page pixels.
+ // Fit remains placement-only. SDL's physical-viewport quality request
+ // changes with a resize and therefore replaces pixels; Kitty's fixed,
+ // bandwidth-conscious request retains them.
const revision_before_geometry_change = searched.images[0].?.native.revision;
pv.search_reveal_pending = false;
p.togglePdfFit(pane);
@@ -453,10 +505,14 @@ test "PDF native mouse selection, Look, and highlights share page geometry" {
try std.testing.expect(pv.search_reveal_pending);
_ = frame.reset(.retain_capacity);
const geometry_changed = try p.render(frame.allocator());
- try std.testing.expectEqual(
- revision_before_geometry_change,
- geometry_changed.images[0].?.native.revision,
- );
+ if (pdf_raster_policy.match_viewport)
+ try std.testing.expect(geometry_changed.images[0].?.native.revision !=
+ revision_before_geometry_change)
+ else
+ try std.testing.expectEqual(
+ revision_before_geometry_change,
+ geometry_changed.images[0].?.native.revision,
+ );
p.update(.{ .resize = .{
.cols = p.screen_w,
.rows = p.screen_h,
@@ -1756,6 +1812,11 @@ const PdfView = if (pdf_enabled) struct {
iw: usize = 0,
ih: usize = 0,
render_tried: bool = false,
+ /// Last quality request attempted, successful or not. A viewport change
+ /// gets one transactional retry; a capped/failed request does not spin on
+ /// every frame. Fit and pan are intentionally absent from this key.
+ render_request: pdf_impl.RenderRequest = .{},
+ render_request_valid: bool = false,
render_revision: u32 = 0,
text: []u8 = &.{},
text_tried: bool = false,
@@ -7644,6 +7705,7 @@ pub const Pardes = struct {
if (pv.rgba.len > 0) p.gpa.free(pv.rgba);
pv.rgba = &.{};
pv.render_tried = false;
+ pv.render_request_valid = false;
}
fn dropPdfSearchResults(p: *Pardes, pv: *PdfView) void {
@@ -7860,6 +7922,17 @@ pub const Pardes = struct {
};
}
+ fn pdfRenderRequest(p: *const Pardes, pane: *const Pane) ?pdf_impl.RenderRequest {
+ if (comptime !pdf_enabled) return null;
+ const viewport = p.pdfViewport(pane) orelse return null;
+ return .{
+ .dpi = pdf_raster_policy.dpi,
+ .minimum_width = if (pdf_raster_policy.match_viewport) viewport.pixel_w else 0,
+ .minimum_height = if (pdf_raster_policy.match_viewport) viewport.pixel_h else 0,
+ .max_dimension = pdf_raster_policy.max_dimension,
+ };
+ }
+
fn pdfGeometry(p: *const Pardes, pane: *const Pane) ?image.NativeGeometry {
if (comptime !pdf_enabled) return null;
const pv = pane.pdf orelse return null;
@@ -9569,21 +9642,27 @@ pub const Pardes = struct {
}
}
- /// Render the current PDF page once and attach its stable RGBA until page
- /// navigation invalidates it. MuPDF rendering is bounded to a 1600-pixel
- /// longest side in pdf.zig, independent of malicious page dimensions.
+ /// Render the current PDF page once per backend-quality request and attach
+ /// its stable RGBA until pixels or physical viewport quality change.
+ /// Fit/pan stay placement-only, while every request retains its backend's
+ /// hard longest-side allocation ceiling.
fn drawPdf(p: *Pardes, pane: *Pane, r: Rect, tx: u16, tw: u16) bool {
if (comptime !pdf_enabled) return false;
const pv = &(pane.pdf orelse return false);
- if (!pv.render_tried) {
+ const request = p.pdfRenderRequest(pane) orelse return false;
+ if (!pv.render_tried or !pv.render_request_valid or
+ !pv.render_request.eql(request))
+ {
pv.render_tried = true;
+ pv.render_request = request;
+ pv.render_request_valid = true;
p.resolvePdfSearch(pv);
const rendered = render: {
const highlights = p.pdfHighlights(pv) catch break :render null;
const fresh = if (highlights.len > 0)
- pv.document.renderWithHighlights(p.gpa, pv.page, highlights) catch break :render null
+ pv.document.renderWithHighlightsAt(p.gpa, pv.page, request, highlights) catch break :render null
else
- pv.document.render(p.gpa, pv.page) catch break :render null;
+ pv.document.renderAt(p.gpa, pv.page, request) catch break :render null;
break :render fresh;
};
if (rendered) |fresh| {