Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ pub fn build(b: *std.Build) void {
});
exe.headerpad_max_install_names = true;

if (target.result.os.tag == .macos) exe.linkFramework("CoreGraphics");

const deps = .{
.vaxis = b.dependency("vaxis", .{ .target = target, .optimize = optimize }),
.fzwatch = b.dependency("fzwatch", .{ .target = target, .optimize = optimize }),
Expand Down
4 changes: 3 additions & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Because fancy-cat provides sensible defaults, you only need to specify the optio
"scroll_step": 100.0,
"retry_delay": 0.2,
"timeout": 5.0,
"detect_dpi": true,
"dpi": 96.0,
"history": 1000
},
Expand Down Expand Up @@ -208,7 +209,8 @@ The `General` section includes various display and timing settings.
| `zoom_step` | Float | Zoom multiplier per keystroke |
| `zoom_min` | Float | Minimum zoom level allowed |
| `scroll_step` | Float (pixels) | Distance the viewport moves per scroll keystroke |
| `dpi` | Float | Resolution used for 100% zoom calculation |
| `detect_dpi` | Boolean | Enables pixel-density detection so that 100% zoom = actual size |
| `dpi` | Float | Pixel density to use if `detect_dpi` is false, or fallback if detection fails |
| `retry_delay` | Float (seconds) | Delay before retrying to load a document or render a page |
| `timeout` | Float (seconds) | Maximum time to keep retrying before giving up on loading a document or rendering a page |
| `history` | Integer | Maximum number of entries in command history |
Expand Down
2 changes: 2 additions & 0 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub const General = struct {
retry_delay: f32 = 0.2,
timeout: f32 = 5.0,
// resolution
detect_dpi: bool = true,
dpi: f32 = 96.0,
// whole number (possibly 0)
history: u32 = 1000,
Expand Down Expand Up @@ -119,6 +120,7 @@ pub const General = struct {
general.scroll_step = parseType(f32, val.object, "scroll_step", allocator, general.scroll_step);
general.retry_delay = parseType(f32, val.object, "retry_delay", allocator, general.retry_delay);
general.timeout = parseType(f32, val.object, "timeout", allocator, general.timeout);
general.detect_dpi = parseType(bool, val.object, "detect_dpi", allocator, general.detect_dpi);
general.dpi = parseType(f32, val.object, "dpi", allocator, general.dpi);
general.history = parseType(u32, val.object, "history", allocator, general.history);

Expand Down
4 changes: 2 additions & 2 deletions src/handlers/DocumentHandler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ pub fn zoomOut(self: *Self) void {
self.pdf_handler.zoomOut();
}

pub fn setZoom(self: *Self, zoom_factor: f32) void {
self.pdf_handler.active_zoom = @max(zoom_factor, self.pdf_handler.config.general.zoom_min);
pub fn setZoom(self: *Self, percent: f32) void {
self.pdf_handler.setZoom(percent);
}

pub fn toggleColor(self: *Self) void {
Expand Down
9 changes: 9 additions & 0 deletions src/handlers/PdfHandler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const fastb64z = @import("fastb64z");
const vaxis = @import("vaxis");
const Config = @import("../config/Config.zig");
const types = @import("./types.zig");
const Utilities = @import("../utilities/Utilities.zig");

const c = @cImport({
@cInclude("fitz-z.h");
@cInclude("mupdf/fitz.h");
Expand Down Expand Up @@ -226,6 +228,13 @@ pub fn zoomOut(self: *Self) void {
self.active_zoom /= self.config.general.zoom_step;
}

pub fn setZoom(self: *Self, percent: f32) void {
var dpi = self.config.general.dpi;
if (self.config.general.detect_dpi) dpi = Utilities.getDPI() orelse dpi;

self.active_zoom = @max(percent * dpi / 7200.0, self.config.general.zoom_min);
}

pub fn toggleColor(self: *Self) void {
self.config.general.colorize = !self.config.general.colorize;
}
Expand Down
5 changes: 1 addition & 4 deletions src/modes/CommandMode.zig
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,7 @@ fn handleZoom(self: *Self, cmd: []const u8) bool {

const number_str = cmd[0 .. cmd.len - 1];
if (std.fmt.parseFloat(f32, number_str)) |percent| {
// TODO detect DPI
const dpi = self.context.document_handler.pdf_handler.config.general.dpi;
const zoom_factor = (percent * dpi) / 7200.0;
self.context.document_handler.setZoom(zoom_factor);
self.context.document_handler.setZoom(percent);
self.context.resetCurrentPage();
return true;
} else |_| {
Expand Down
11 changes: 11 additions & 0 deletions src/utilities/Utilities.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const builtin = @import("builtin");
const utilities = struct {
pub const macos = @import("./macos.zig");
};

pub fn getDPI() ?f32 {
return switch (builtin.os.tag) {
.macos => utilities.macos.getDPI(),
else => null,
};
}
37 changes: 37 additions & 0 deletions src/utilities/macos.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const std = @import("std");
pub const c = @cImport({
@cInclude("CoreGraphics/CoreGraphics.h");
});

pub fn getDPI() ?f32 {
const display = getDisplay();

if (c.CGDisplayCopyDisplayMode(display)) |mode| {
defer c.CGDisplayModeRelease(mode);
const width_px = @as(f32, @floatFromInt(c.CGDisplayModeGetPixelWidth(mode)));
const width_mm = @as(f32, @floatCast(c.CGDisplayScreenSize(display).width));
if (width_mm != 0) return std.math.round(width_px / width_mm * 25.4);
}
return null;
}

fn getDisplay() c.CGDirectDisplayID {
const main_display = c.CGMainDisplayID();

var disp_count: u32 = 0;
if (c.CGGetActiveDisplayList(0, null, &disp_count) != c.kCGErrorSuccess or disp_count <= 1) return main_display;

const win_list = c.CGWindowListCopyWindowInfo(c.kCGWindowListOptionOnScreenOnly | c.kCGWindowListExcludeDesktopElements, c.kCGNullWindowID) orelse return main_display;
defer c.CFRelease(win_list);
if (c.CFArrayGetCount(win_list) == 0) return main_display;

const win = @as(c.CFDictionaryRef, @ptrCast(c.CFArrayGetValueAtIndex(win_list, 0)));
const win_bounds = c.CFDictionaryGetValue(win, c.kCGWindowBounds) orelse return main_display;
var win_rect: c.CGRect = undefined;
if (!c.CGRectMakeWithDictionaryRepresentation(@as(c.CFDictionaryRef, @ptrCast(win_bounds)), &win_rect)) return main_display;

var display: c.CGDirectDisplayID = 0;
if (c.CGGetDisplaysWithRect(win_rect, 1, &display, &disp_count) == c.kCGErrorSuccess and disp_count > 0) return display;

return main_display;
}