<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrew Kwak</title>
    <description>The latest articles on DEV Community by Andrew Kwak (@andrewbuilds).</description>
    <link>https://dev.to/andrewbuilds</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4034673%2Fd4b31bae-fde6-49c6-b06c-61c0719e44a8.jpg</url>
      <title>DEV Community: Andrew Kwak</title>
      <link>https://dev.to/andrewbuilds</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andrewbuilds"/>
    <language>en</language>
    <item>
      <title>Why Google Docs looked blurry in my WKWebView (and the two-part fix)</title>
      <dc:creator>Andrew Kwak</dc:creator>
      <pubDate>Sat, 18 Jul 2026 08:55:52 +0000</pubDate>
      <link>https://dev.to/andrewbuilds/why-google-docs-looked-blurry-in-my-wkwebview-and-the-two-part-fix-4d7</link>
      <guid>https://dev.to/andrewbuilds/why-google-docs-looked-blurry-in-my-wkwebview-and-the-two-part-fix-4d7</guid>
      <description>&lt;p&gt;&lt;em&gt;Building Orbit for Mac, a native multi-account Gmail client, I hit a bug that only showed up in Google Docs and Sheets, never in Gmail. Here's what was actually going on.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A user emailed me on launch day: "Docs and Sheets look blurry, but Gmail is crisp." Same app, same window, same account. Gmail sharp, Docs fuzzy. That "only some pages" detail is the whole clue, so let me walk through why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Orbit shows the real Google web apps inside &lt;code&gt;WKWebView&lt;/code&gt;, one per account, each with its own &lt;code&gt;WKWebsiteDataStore&lt;/code&gt; for session isolation. Gmail, Calendar, Drive, Docs, Sheets, all the same web content in the same kind of view. So a rendering bug that hits Docs but not Gmail can't be "WKWebView is blurry." Something is different about how Docs &lt;em&gt;draws&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The clue: how each app renders text
&lt;/h2&gt;

&lt;p&gt;Here's the thing I didn't appreciate until I dug in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gmail draws text as DOM text.&lt;/strong&gt; WebKit rasterizes real text on demand, so at any zoom level it re-renders crisply. Scale it, and it stays sharp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs and Sheets draw text onto a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;.&lt;/strong&gt; The document is painted as a bitmap. And a bitmap has a fixed resolution, the moment you display it at any non-integer scale, it gets resampled, and resampled bitmaps look soft.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So "blurry Docs" wasn't a text-rendering bug. It was a &lt;strong&gt;bitmap being drawn at a fractional zoom.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the fractional zoom came from
&lt;/h2&gt;

&lt;p&gt;Two independent sources, which is why it was annoying to pin down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A stray trackpad pinch.&lt;/strong&gt; &lt;code&gt;WKWebView&lt;/code&gt; has &lt;code&gt;allowsMagnification&lt;/code&gt;. One accidental two-finger pinch sets &lt;code&gt;magnification&lt;/code&gt; to something like &lt;code&gt;1.03&lt;/code&gt;, and now the canvas bitmap is being resampled forever. Invisible in Gmail (DOM text re-rasterizes), obvious in Docs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inherited zoom state from the pool.&lt;/strong&gt; Orbit keeps a pool of webviews alive per account so switching accounts is instant (no reload, no re-login). But a pooled, reused view can carry over stale &lt;code&gt;pageZoom&lt;/code&gt; from a previous session. Again: harmless for Gmail, visible in Docs.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix for this half was boring but necessary, pin both to 1.0 on creation so no canvas app ever inherits a fractional scale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;wv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pageZoom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;wv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;magnification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(And &lt;code&gt;⌘0&lt;/code&gt; / &lt;code&gt;resetZoom()&lt;/code&gt; as the manual escape hatch when someone &lt;em&gt;does&lt;/em&gt; pinch.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The other half: the User-Agent
&lt;/h2&gt;

&lt;p&gt;Fixing zoom helped, but Docs still wasn't as sharp as Safari. The second lever was the &lt;strong&gt;User-Agent string.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google serves different rendering paths depending on what browser it thinks it's talking to. For the Docs family specifically, presenting a proper Safari User-Agent gets you the rendering path Google actually tuned for Apple's WebKit, including how it handles the canvas on Retina displays, instead of a more generic fallback.&lt;/p&gt;

&lt;p&gt;So Orbit sends a Docs-specific Safari UA only for the Docs/Sheets/Slides family, and a normal one everywhere else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;GoogleService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isDocsFamily&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;docsSafariUserAgent&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;safeSafariUserAgent&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two services, two rendering realities: the same canvas that looked soft under the generic path renders crisp under the Safari path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;If you're embedding third-party web apps in &lt;code&gt;WKWebView&lt;/code&gt; and &lt;em&gt;some&lt;/em&gt; of them look blurry while others are fine, don't reach for a global "make it sharp" setting. Ask which pages render to &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; versus DOM. Canvas pages are bitmap pages, and bitmaps hate two things: fractional zoom and the wrong rendering path. Pin the zoom, match the User-Agent the site expects, and the fuzz goes away.&lt;/p&gt;

&lt;p&gt;It took a user's one-line report ("Gmail is crisp, Docs isn't") to make the pattern obvious. That's been the theme of building &lt;a href="https://orbitformac.com" rel="noopener noreferrer"&gt;Orbit&lt;/a&gt; in public: the bug reports are better than any test suite.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>webkit</category>
      <category>macos</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
