Since iOS/iPadOS 27.0, saving a PDF with PDFDocument.dataRepresentation() produces a corrupt file when a page contains a PDFAnnotation subclass that overrides draw(with:in:) and draws a 16-bits-per-component image. The same code works correctly on iOS 26.4.
Filed as FBxxxxxxxx.
What happens:
- The annotation is written with /AAPL:AKExtras, and its appearance (/AP) references an image XObject declared as /BitsPerComponent 16 (RGB).
- The image stream contains only half of the required data (8-bpc worth). Example: 1429 x 2021 image. Required: 17,328,054 bytes. Actual: 8,664,027 bytes.
- Adobe Acrobat reports "Insufficient data for an image".
- Other viewers read the 8-bpc data as 16-bpc, so the drawing is squeezed into the upper half of the page and the lower half is empty. In PDFKit, colors may turn black.
- The problem is in the writing step: a PDF written on iOS 27.0 is broken everywhere, while a PDF written on iOS 26.4 opens correctly on iOS 27.0.
How to reproduce:
- Create a 16-bpc image with UIGraphicsImageRenderer (preferredRange = .extended, or the default format on a wide-color device).
- Add a PDFAnnotation subclass whose draw(with:in:) draws that image with context.draw(cgImage, in: bounds).
- Call dataRepresentation() and reopen the result.
Current workaround: Create the image with an 8-bpc standard-range format before drawing it in the annotation:
let format = UIGraphicsImageRendererFormat.preferred()
format.preferredRange = .standard // 8 bpc, sRGB
let renderer = UIGraphicsImageRenderer(size: size, format: format)
With this, the declared bit depth and the data length match, and the PDF is valid on iOS 27.0. However, this gives up 16-bpc / wide-color output, so I would prefer a solution that keeps the original image quality.
Possibly related: FB24882188 / FB24843022 (iOS 27 regression in PDFAnnotation subclasses that override draw(with:in:)).
Questions:
- Does anyone know a way to avoid this issue without downgrading the image to 8 bpc? For example, a different way to create or attach the annotation appearance so that PDFKit on iOS 27 writes the 16-bpc data correctly.
- Has anyone else seen this, or confirmed whether it is fixed in a later iOS 27 release?
Thanks for the precise report. The byte counts made this easy to confirm. I reproduced it on iPhone 18 Pro Max with iOS 27.0, with the same counts: 8,664,027 of 17,328,054 bytes for a 1429 x 2021 image. The output also contains /AAPL:AKExtras. The same build on iPhone 16 with iOS 26.6.2 wrote the full 16-bpc data.
In my tests, the truncation happens only when a stamp annotation is on a page that belongs to a document opened from existing PDF data. That means a document from PDFDocument(url:) or PDFDocument(data:). Saving with write(to:) gives the same result. The same annotation writes correctly on a page in a document created with PDFDocument(), including a page moved there from an opened file.
Moving the pages into a new document before adding annotations keeps the 16-bpc data. This function does that:
func documentForEditing(contentsOf url: URL) -> PDFDocument? {
guard let source = PDFDocument(url: url) else { return nil }
let document = PDFDocument()
for index in 0..<source.pageCount {
if let page = source.page(at: index)?.copy() as? PDFPage {
document.insert(page, at: document.pageCount)
}
}
document.documentAttributes = source.documentAttributes
document.outlineRoot = source.outlineRoot
return document
}
On iOS 27.0, a document made this way wrote the full 17,328,054 bytes, without AKExtras. The saved file rendered correctly in both halves of the annotation. The new document kept the page order, a form widget, and links. Moving pages drops the documentAttributes dictionary, which holds the title and author, so the function copies it. I didn't verify that the outlineRoot assignment preserves an outline.
I can't comment on future releases. Your 27.2 beta Simulator result is the most current information about whether a later release changes this. Thanks for filing FB25195006. I've added my results to it.