WebView: withMinimumDeviceScaleFactor (macOS + Windows) — fix blur on low-DPI displays#35
Closed
jakepenn wants to merge 2 commits into
Closed
WebView: withMinimumDeviceScaleFactor (macOS + Windows) — fix blur on low-DPI displays#35jakepenn wants to merge 2 commits into
jakepenn wants to merge 2 commits into
Conversation
…sity Adds AppleWkWebView::withMinimumDeviceScaleFactor(double). On a standard-DPI (1x) display the WKWebView rasterises at one device pixel per CSS pixel; when the host scales the editor up via an ancestor AffineTransform (which enlarges the WebView's peer bounds without changing its CSS layout) the OS upscales that 1x raster and the content reads as blurry — most visibly text. Raising the effective device scale gives the renderer headroom so the upscale stays sharp. It is a floor: a Retina display already exceeds it, so it's a no-op there. This affects rasterisation density and window.devicePixelRatio only — CSS layout is unchanged, so it cannot shift or reflow the UI (unlike a CSS zoom-based fit). macOS impl uses the private WKWebView SPI _setOverrideDeviceScaleFactor: via a guarded objc_msgSend, re-applied in viewDidChangeBackingProperties so it survives moves between displays of differing backingScaleFactor. respondsToSelector + @Try make it a silent no-op if a future OS drops the SPI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the Windows counterpart to the macOS withMinimumDeviceScaleFactor floor, on WinWebView2 options. Implemented with ICoreWebView2Controller3:: put_RasterizationScale: the floor is max(requested, monitor scale), and ShouldDetectMonitorScaleChanges is turned off so WebView2 doesn't reset it on DPI changes — it's re-applied from the scale-factor notifier with the live monitor scale instead. No-op on WebView2 runtimes without Controller3. Same purpose as the macOS path: on a 100%-scaled monitor the content otherwise rasterises at 1 device px per CSS px, so the host's ancestor-transform scaling leaves the OS upscaling a 1x raster (blurry, most visibly text). Note that raising RasterizationScale also shrinks the CSS viewport (CSS px = bounds / scale), so it's only appropriate for responsively-laid-out content. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The player webview renders blurry on low-DPI displays (1× / 100%-scaled monitors; reported on a 1920×1080 panel). The frontend fits a fixed-size design into the host window; the host editor scales the WebView via an ancestor
AffineTransform, which enlarges the WebView's bounds without changing its CSS layout. At 1 device px per CSS px, the OS upscale of that enlarged surface bilinear-blurs the content — most visibly text. High-DPI (2×) displays hide it because the page already rasterises at 2×.Change
Adds
withMinimumDeviceScaleFactor(double)— a floor on the WebView's rasterisation device scale — to bothAppleWkWebViewandWinWebView2options. Raises rasterisation density (andwindow.devicePixelRatio) sotransform-based fits have headroom and stay crisp. It's a floor, so a high-DPI display is a no-op.macOS: private WKWebView SPI
_setOverrideDeviceScaleFactor:via a guardedobjc_msgSend(an@interfacecategory can't live in thejucenamespace). Re-applied inviewDidChangeBackingPropertiesand floored atmax(min, backingScaleFactor).respondsToSelector+@try→ silent no-op if the SPI is dropped. CSS viewport is unchanged.Windows:
ICoreWebView2Controller3::put_RasterizationScaleset tomax(min, monitorScale), withShouldDetectMonitorScaleChangesdisabled so WebView2 doesn't reset the floor on DPI changes (re-applied from the scale-factor notifier with the live monitor scale). No-op on runtimes withoutController3. Note: raising RasterizationScale also shrinks the CSS viewport (CSS px = bounds / scale), so it's only appropriate for responsively-laid-out content — documented on the option.Consumer opt-in lands separately in
minimal_core(MKUEmbeddedWebView→.withMinimumDeviceScaleFactor(2.0)on both backends).Status / testing
_setOverrideDeviceScaleFactor:must take effect; checkwindow.devicePixelRatioreads 2 on a 1× display.window.devicePixelRatioreads 2 and layout is unchanged-but-sharp.🤖 Generated with Claude Code