High-DPI & scaling

Short version: you usually don't have to do anything. Size text in points and lay out with pack / grid / Flex / Grid, and your app renders at a sensible physical size on a 1080p laptop, a 4K monitor, and a Retina MacBook alike. This page explains why, and what to do in the one case that needs care — absolute pixel positioning.

How each platform handles density

Screen density ("DPI") is resolved in surprisingly different places on each OS. Measured on one 27″ 2560×1440 panel (a true ~109 DPI) plus a Retina MacBook:

Platform What the app sees Who does the scaling
macOS logical points, never physical pixels — e.g. 2048×1152, or 1408×881 on Retina — at a flat 96 DPI the OS composites points → pixels (Retina too)
Windows physical pixels + the real DPI (96 at 100 %, 144 at 150 %) — Tk 9 is per-monitor-DPI-aware the app, guided by the reported DPI
Linux / X11 the DPI your desktop set via Xft.dpi (≈107 here), or a fabricated 96 on a bare window manager the app

Two takeaways:

tk scaling is the one number that's right everywhere: device pixels per point, and Tk uses it to size fonts and any geometry you express in points.

The convention: think in logical pixels

ScriptWeaver's unit of choice is the logical pixel — a CSS-style reference pixel at 96 DPI. To convert logical pixels to device pixels, multiply by

S = tk scaling × 0.75

(0.75 because a point is 1/72″ while a logical pixel is 1/96″.) Measured S on the hardware above:

Platform / scale tk scaling S
Linux, Xft.dpi 107 1.485 1.11
macOS (external + Retina) 1.333 1.00
Windows @ 100 % 1.333 1.00
Windows @ 150 % 1.998 1.50

S is 1.0 exactly where the OS already owns the scaling (macOS, Windows at 100 %) and rises only where the app must do the work — so you can multiply by S unconditionally and never double-scale.

When you actually need it

const S = parseFloat(__native_tcl_eval('tk scaling')) * 0.75;
const dp = (n) => Math.round(n * S);

// a 200×120 logical-pixel panel, 16 logical px in from the top-left:
panel.place.configure({ x: dp(16), y: dp(16), width: dp(200), height: dp(120) });

// stretch-with-margins that survives both resize and DPI:
field.place.configure({ x: dp(8), relwidth: 1, width: dp(-16), height: dp(28) });

Gotchas

Planned

A Player-level convenience (app.scale and app.dp(n)) is planned so you won't read tk scaling by hand. Until it lands, the two-line helper above is the whole story.

Next