Add support for the Decorator API in the PDF navigator - #861
Add support for the Decorator API in the PDF navigator#861mickael-menu wants to merge 4 commits into
Conversation
4d2f2ba to
9e5b0dc
Compare
| @@ -0,0 +1,30 @@ | |||
| # 1. Render PDF decorations with overlay views, requiring iOS 16 | |||
There was a problem hiding this comment.
Does this file need to be in the code base?
| let decoration = diffable.decoration | ||
| guard | ||
| isCurrentResource(href: decoration.locator.href), | ||
| self.pageIndex(for: decoration.locator, in: document) == pageIndex |
There was a problem hiding this comment.
This feels inefficient, iterating through each decoration on each page change. It would be better to sort them in a dictionary when the PDF is first opened.
| /// Maximum rasterization scale for `.draw` templates. The backing-store | ||
| /// memory grows quadratically with the scale and PDFKit allows deep zoom, | ||
| /// so drawings get blurry past this cap. | ||
| private static let maxRasterizationScale: CGFloat = 4 |
There was a problem hiding this comment.
Could this be exposed via configuration so developers can judge the tradeoffs?
| case let .update(decoration): | ||
| invalidateResolvedDecorations(for: decoration.locator, document: document) | ||
| // The update may have moved the decoration to another page. | ||
| if let old = source.first(where: { $0.decoration.id == decoration.id }) { |
There was a problem hiding this comment.
These two source.first calls have the same problem with efficiency. Better to build and use a dictionary for this too.
| for change in hrefChanges { | ||
| switch change { | ||
| case let .add(decoration): | ||
| invalidateResolvedDecorations(for: decoration.locator, document: document) | ||
| case let .remove(id): | ||
| if let old = source.first(where: { $0.decoration.id == id }) { | ||
| invalidateResolvedDecorations(for: old.decoration.locator, document: document) | ||
| } else { | ||
| resolvedDecorationsCache.removeAll() | ||
| } | ||
| case let .update(decoration): | ||
| invalidateResolvedDecorations(for: decoration.locator, document: document) | ||
| // The update may have moved the decoration to another page. | ||
| if let old = source.first(where: { $0.decoration.id == decoration.id }) { | ||
| invalidateResolvedDecorations(for: old.decoration.locator, document: document) | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
resolvedDecorationsCache only holds the few pages currently on screen, correct? Calling invalidateResolvedDecorations for every change during bulk runs the locator for pages that aren't in the cache. Couldn't we just call resolvedDecorationsCache.removeAll() when clearing or adding a whole group?
PDFNavigatorViewControllernow implementsDecorableNavigator(proposal 008), so features like annotations and search highlighting can render highlights, underlines and custom UI over PDF publications.Rendering
Decorations are rendered with
PDFPageOverlayViewProvideroverlay views (iOS 16+), never by mutating the shared cachedPDFDocument. The rationale is recorded indocs/adr/0001-pdf-decoration-rendering.md. On iOS 15,supports(decorationStyle:)returnsfalseandapply()is a no-op that logs a warning.Overlay views live in page space: PDFKit applies zoom/rotation itself, so decorations stay glued to the text. Z-order is deterministic: groups stack in the order they were first applied, then array order within a group.
Geometry resolution
Each decoration locator resolves to a page (via the existing
PDFPageNumberResolver), then to line boxes through a priority chain:locations.fragments—highlight=lt,rt,top,btm(one per line of text) orviewrect=x,y,w,h— rendered directly with no text search.text.highlightscoped to the page, with normalization (soft hyphens, collapsed whitespace, ligature folding) that maps matches back to original string offsets before building thePDFSelection.text.before/afterdisambiguate repeated matches.An unresolvable locator (e.g. scanned PDF without a text layer) renders nothing and logs a warning. A failed search never degrades into a full-page highlight.
Selection locators are enriched symmetrically:
selectionDidChangenow emits onehighlight=fragment per selected line plustext.before/aftercontext, so highlights created from a selection round-trip without any text search.Templates
New public
PDFDecorationTemplate(registered viaConfiguration.decorationTemplates), mirroring EPUB'sdecorationTemplates:Layout(boxes/bounds) selects the rect set,Width(wrap/bounds/page) adjusts horizontal extent, per the spec.Renderer.viewvends aUIViewper rect (built-in highlight/underline use this; solid-color views stay sharp at any zoom).Renderer.drawis the CoreGraphics escape hatch, rasterized with a zoom-followingcontentsScalecapped at 4x.onActivatedwith its bounding rect in navigator coordinates.