Linux: prefer server DPI over panel physical DPI in vendored winit (#1150)

* fix(x11): prefer server DPI over panel physical DPI in winit

Without Xft DPI, use the X server screen dimensions before the output physical density. Xorg can run a dense panel at 96 DPI; EDID sizes alone make only winit apps appear zoomed.

Supersedes #908: preserves Xft.dpi priority, configured HiDPI screens, and WINIT_X11_SCALE_FACTOR override (including randr). Falls back to RandR output density when server dimensions are missing.

Adds default_dpi_factor() with 4 unit tests plus vendored-copy guard test in apps/pdfcraft/tests/winit_x11_dpi.rs.

* vendor/README: record the winit X11 DPI patch as (11)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
gyurix 2026-10-12 01:12:32 +02:00 committed by GitHub
parent 492207ab2c
commit f5452cf126
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 9 deletions

View File

@ -0,0 +1,14 @@
#[test]
fn winit_carries_the_x11_server_dpi_fallback() {
// The behavior tests live beside the policy in winit (util::randr::tests).
// This guards PdfCraft's vendored copy, which workspace tests do not test itself.
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let randr = std::fs::read_to_string(root.join("vendor/winit/src/platform_impl/linux/x11/util/randr.rs")).unwrap();
assert!(randr.contains("let screen = self.default_root();"));
assert!(randr.contains("default_dpi_factor(\n self.get_xft_dpi(),"));
assert!(randr.contains("screen.width_in_millimeters.into()"));
assert!(randr.contains("calc_dpi_factor(screen_pixels, screen_mm)"));
assert!(randr.contains("server_dpi_wins_over_the_panels_physical_density"));
let desktop = std::fs::read_to_string(root.join("packaging/linux/ai.storyteller.pdfcraft.desktop")).unwrap();
assert!(!desktop.contains("WINIT_X11_SCALE_FACTOR"));
}

2
vendor/README.md vendored

File diff suppressed because one or more lines are too long

View File

@ -142,14 +142,14 @@ impl XConnection {
dpi_override
},
EnvVarDPI::NotSet => {
if let Some(dpi) = self.get_xft_dpi() {
dpi / 96.
} else {
calc_dpi_factor(
(crtc.width.into(), crtc.height.into()),
(output_info.mm_width as _, output_info.mm_height as _),
)
}
let screen = self.default_root();
default_dpi_factor(
self.get_xft_dpi(),
(screen.width_in_pixels.into(), screen.height_in_pixels.into()),
(screen.width_in_millimeters.into(), screen.height_in_millimeters.into()),
(crtc.width.into(), crtc.height.into()),
(output_info.mm_width as _, output_info.mm_height as _),
)
},
};
@ -184,3 +184,71 @@ impl XConnection {
Ok(self.xcb_connection().randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?.reply()?.mode)
}
}
// PdfCraft patch: without Xft DPI, use the X server's configured screen DPI before
// the output's physical density. Xorg can deliberately run a dense panel at 96 DPI;
// using its EDID dimensions instead makes only winit applications appear zoomed.
// Keep configured HiDPI screens and the explicit `randr` override working.
fn default_dpi_factor(
xft_dpi: Option<f64>,
screen_pixels: (u32, u32),
screen_mm: (u64, u64),
output_pixels: (u32, u32),
output_mm: (u64, u64),
) -> f64 {
if let Some(dpi) = xft_dpi {
return dpi / 96.;
}
if screen_pixels.0 != 0 && screen_pixels.1 != 0 && screen_mm.0 != 0 && screen_mm.1 != 0 {
calc_dpi_factor(screen_pixels, screen_mm)
} else {
calc_dpi_factor(output_pixels, output_mm)
}
}
#[cfg(test)]
mod tests {
use super::{calc_dpi_factor, default_dpi_factor};
const PIXELS: (u32, u32) = (1920, 1080);
const SCREEN_96: (u64, u64) = (508, 285);
const SCREEN_144: (u64, u64) = (339, 190);
const SCREEN_192: (u64, u64) = (254, 143);
const PANEL: (u64, u64) = (381, 214);
#[test]
fn server_dpi_wins_over_the_panels_physical_density() {
assert_eq!(default_dpi_factor(None, PIXELS, SCREEN_96, PIXELS, PANEL), 1.0);
assert_eq!(default_dpi_factor(None, PIXELS, SCREEN_144, PIXELS, PANEL), 1.5);
assert_eq!(default_dpi_factor(None, PIXELS, SCREEN_192, PIXELS, PANEL), 2.0);
}
#[test]
fn xft_dpi_still_has_priority() {
for dpi in [96.0, 120.0, 144.0, 192.0] {
assert_eq!(
default_dpi_factor(Some(dpi), PIXELS, SCREEN_192, PIXELS, PANEL),
dpi / 96.0,
);
}
}
#[test]
fn missing_screen_dimensions_keep_the_randr_fallback() {
let physical_scale = calc_dpi_factor(PIXELS, PANEL);
assert!(physical_scale > 1.0);
for screen_mm in [(0, 285), (508, 0), (0, 0)] {
assert_eq!(default_dpi_factor(None, PIXELS, screen_mm, PIXELS, PANEL), physical_scale);
}
for screen_pixels in [(0, 1080), (1920, 0), (0, 0)] {
assert_eq!(default_dpi_factor(None, screen_pixels, SCREEN_96, PIXELS, PANEL), physical_scale);
}
assert_eq!(default_dpi_factor(None, PIXELS, (0, 0), PIXELS, (0, 0)), 1.0);
}
#[test]
fn explicit_randr_calculation_still_uses_output_dimensions() {
assert_eq!(calc_dpi_factor(PIXELS, PANEL), 4.0 / 3.0);
assert_eq!(calc_dpi_factor(PIXELS, SCREEN_192), 2.0);
}
}