PDF Widget Annotations appear Pixelated/Rasterized

The Problem

On opening a PDF document in the Preview app, PDF widget annotations appear pixelated/rasterized. This problem exists with button, text, and choice widget subtypes. The pixelation becomes more apparent when zoomed in.

Expected Results

PDF widgets should appear sharp and smooth, without pixelation. In previous versions of the Preview app, widgets appear vector-based as expected.

Impact on User Experience

PDF widgets appear pixelated and inconsistent with text content in the same PDF document. Widgets do not look like elements of an interactive form but, instead, resemble low-quality embedded images.

Affected Apps/OSs:

Preview 11.0 (1147), as well as other apps that use PDFKit, on macOS Golden Gate 27.0 (26A428).

A similar problem appears to affect PDFKit on iOS 27.0 and iPadOS 27.0 as well.

Feedback/bug report: FB24843022

Answered by DTS Engineer in 906279022

Thanks for these reports, and for the reproducer project. FB24843022 and FB24882188 are both on file.

If you are hitting this, a report of your own helps even where it looks like a duplicate. Yours carries what the existing ones cannot: the PDF itself, which annotation subtypes it uses, the OS versions and hardware you see it on, and whether the same file rendered correctly on an earlier release. A small project that reproduces the problem is the most useful form a report can take. Feedback Assistant is at https://developer.apple.com/feedback-assistant/. If you file a bug report, please post its number to this thread so I can follow up and make sure it is routed to the right place.

The widgets disappearing after toggling radio buttons and checkboxes, then saving and reopening, is worth a separate report. That is a different problem from the rasterization, and it will be tracked better on its own.

For annotations that do not need to stay interactive, writing the document with burnInAnnotationsOption (https://developer.apple.com/documentation/pdfkit/pdfdocumentwriteoption/burninannotationsoption) and displaying the result draws them as vector:

let options = [PDFDocumentWriteOption.burnInAnnotationsOption: true]
pdfView.document = document.dataRepresentation(options: options).flatMap(PDFDocument.init(data:))

That is no help for the form widgets this thread is about, since flattening a form removes the fields along with the pixelation, but it does apply to ink, stamps, and free text, which are affected too.

I’m dealing with the same problem. Is Apple planning to address it soon?

Also related to problems with PDFKit rendering widget annotations: when a user toggles radio buttons or checkboxes in a PDF using Preview, the widgets disappear following subsequent interactions after the file is saved and reopened.

PDF Widget Annotations Disappear After Saving in PDFKit (including with Preview)

We hit the same regression and traced it. Filed as FB24882188, with a small repro project.

What is happening: on iOS/iPadOS/macOS 27, PDFKit draws annotations into a private layer, PDFPageLayerAnnotationEffect, and configures it with contentsScale 1.0 and magnificationFilter "nearest". The PDFPageLayerTile layers that draw the page right beside it get the real on-screen scale (3.86 in our repro) and "linear". So annotations are rasterized at 1 pixel per point and then stretched with nearest-neighbour sampling, which is the pixelation. On iOS 26.5 the same layer gets the tile scale and "linear", which is why the same build is crisp there.

It is not caused by custom draw(with:in:) code: standard ink and free-text annotations show it too, and widgets as well (the OP's FB24843022, same defect).

Setting those two values on the live layer fixes it immediately, and PDFKit's next draw then uses the new scale. PDFKit resets the layer on every redraw, though, so the correction has to run right before each one. We do that by replacing -display on that one class (never on CALayer itself):

import UIKit

// iOS/iPadOS/macOS 27 workaround for pixelated PDFKit annotations (FB24882188, FB24843022).
// Call once at launch, before any PDFView draws. No-op if the private class is absent.
func installAnnotationLayerFix() {
    guard #available(iOS 27.0, *),
          let cls = NSClassFromString("PDFPageLayerAnnotationEffect"),
          let method = class_getInstanceMethod(cls, #selector(CALayer.display)) else { return }
    typealias Display = @convention(c) (CALayer, Selector) -> Void
    let original = unsafeBitCast(method_getImplementation(method), to: Display.self)
    let block: @convention(block) (CALayer) -> Void = { layer in
        if Thread.isMainThread {
            MainActor.assumeIsolated { correctScale(of: layer) }
        }
        original(layer, #selector(CALayer.display))
    }
    // Adds an override on the subclass only; CALayer itself is untouched.
    class_replaceMethod(cls, #selector(CALayer.display),
                        imp_implementationWithBlock(block), method_getTypeEncoding(method))
}

/// Gives the layer its real on-screen density (screen scale x its magnification in the window)
/// and linear filtering, which is what PDFKit gives the page tiles beside it.
@MainActor
private func correctScale(of layer: CALayer) {
    guard let window = hostView(of: layer)?.window else { return }
    let bounds = layer.bounds
    guard bounds.width > 0, bounds.height > 0 else { return }
    let inWindow = layer.convert(bounds, to: window.layer)
    let magnification = sqrt((inWindow.width * inWindow.height) / (bounds.width * bounds.height))
    let scale = magnification * window.traitCollection.displayScale
    guard scale.isFinite, scale > 0 else { return }
    layer.contentsScale = scale
    layer.magnificationFilter = .linear
}

@MainActor
private func hostView(of layer: CALayer) -> UIView? {
    var current = layer.superlayer
    while let l = current {
        if let view = l.delegate as? UIView { return view }
        current = l.superlayer
    }
    return nil
}

It depends on a private class name, so treat it as a stopgap until Apple fixes the regression; if the class is renamed or removed, it silently does nothing. In our app it fixed everything on iPadOS 27 and Mac Catalyst (macOS 27): new annotations, annotations loaded from saved files, zooming, page changes, rotation. It compiles under both Swift 5 and Swift 6 language modes.

If you're affected, please file your own report and mention FB24882188 and FB24843022 in it, so they get linked on Apple's side.

Thanks for these reports, and for the reproducer project. FB24843022 and FB24882188 are both on file.

If you are hitting this, a report of your own helps even where it looks like a duplicate. Yours carries what the existing ones cannot: the PDF itself, which annotation subtypes it uses, the OS versions and hardware you see it on, and whether the same file rendered correctly on an earlier release. A small project that reproduces the problem is the most useful form a report can take. Feedback Assistant is at https://developer.apple.com/feedback-assistant/. If you file a bug report, please post its number to this thread so I can follow up and make sure it is routed to the right place.

The widgets disappearing after toggling radio buttons and checkboxes, then saving and reopening, is worth a separate report. That is a different problem from the rasterization, and it will be tracked better on its own.

For annotations that do not need to stay interactive, writing the document with burnInAnnotationsOption (https://developer.apple.com/documentation/pdfkit/pdfdocumentwriteoption/burninannotationsoption) and displaying the result draws them as vector:

let options = [PDFDocumentWriteOption.burnInAnnotationsOption: true]
pdfView.document = document.dataRepresentation(options: options).flatMap(PDFDocument.init(data:))

That is no help for the form widgets this thread is about, since flattening a form removes the fields along with the pixelation, but it does apply to ink, stamps, and free text, which are affected too.

PDF Widget Annotations appear Pixelated/Rasterized
 
 
Q