Skip to content

feat(android, ios, web): implement support for pdf.#118

Open
rhusted1982 wants to merge 1 commit into
5-stones:mainfrom
rhusted1982:feat/pdf-support
Open

feat(android, ios, web): implement support for pdf.#118
rhusted1982 wants to merge 1 commit into
5-stones:mainfrom
rhusted1982:feat/pdf-support

Conversation

@rhusted1982

@rhusted1982 rhusted1982 commented Jun 15, 2026

Copy link
Copy Markdown

Overview

The intent of this PR is to add PDF support on web and mobile.

Web

Web takes inspiration from the current EPUB functionality and follows that architecture to render the PDF inside the eReader for a custom layout.
Web leverages a custom layout or iframe to render the pdf in the eReader
Web will try to render using the custom layout, if this fails (and it can, CORS being one example), it will fall back on an iframe to render the pdf.
PDFs on web are rendered and scrolled vertically, basically the default.
Reader Settings from EPUB are not shown for PDF as they are not applicable.
Back and Next buttons will not be displayed since vertical scrolling.
Table of Contents is available for the custom page only, iframe cannot support this.

Mobile

Mobile takes inspiration from the current EPUB functionality and follows that architecture to render the PDF inside the eReader.
PDFs do not leverage the forward and back buttons, instead preferring vertical scrolling.
Reader Settings from EPUB are not shown for PDF as they are not applicable.
Back and Next buttons will not be displayed since vertical scrolling.
Table of Contents is supported.

Testing

Testing was done using a single and multiple column PDF.
Testing was done using the Example NextJS web app for web.
Testing was done using the Example Native app for iOS and Android. Testing involved Android Emulator for a mobile device, iOS Simulator for a mobile and tablet device.

Multiple Column PDF

iOS ios portrait ios landscape
Android android portrait android landscape
Web (rendered via iframe) web

Single Column PDF

iOS ios portrait ios landscape
Android android portrait android landscape
Web (rendered via iframe) web

Table of Contents

iOS ios portrait
Android android portrait
Web (rendered via custom layout) web

@jspizziri jspizziri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rhusted1982 I'm going to focus on reviewing one platform first (iOS) then once we have that down you can port the feedback to the rest. Then I'll review the other platforms as well.

Comment thread ios/Reader/PDF/PDFHTTPServer.swift Outdated
import ReadiumAdapterGCDWebServer

/// Provides a shared HTTP server backed by GCDWebServer for PDF resources.
enum PDFHTTPServer {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems identical to the EPUBHTTPServer.swift.

Wouldn't it be better to share a single HTTP service across both EPUB and PDF?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create HTTPServer in the common directory, use for both PDF and EPUB.

let urlHash = abs(cleanURLString.hashValue)
let localURL = cacheDir.appendingPathComponent("\(urlHash).pdf")
if fileManager.fileExists(atPath: localURL.path) {
self.runOpenPipeline(url: localURL, bookId: bookId, locator: locator, selectionActions: selectionActions, sender: sender, completion: completion)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why all of this is needed? Is there some sort of performance problem with streaming PDFs? The swift-toolkit doesn't seem to download streamed files like this.

Additionally, I have concerns about if .hashValue is actually consistent over time, as if I understand correctly, in swift > 4.2 .hashValue has a random seed (across processes)so the resulting hash of the same value will not be stable across them.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change, to cache the PDF before rendering it, was needed to avoid a performance problem that exists in readium 3.5.0. In 3.5.0, if it streamed the PDF over the HTTP server, it noticeably slower (as it chunked it 2K at a time) though it would ultimately work. Loading the PDF to the cache directory and opening it from there avoided the HTTP streaming and loaded much faster.

In readium 3.9.0, they did introduce changes that makes this caching moot as it moves away from the HTTP server altogether.

For the .hashValue, it would not be stable, per your comment I added a new hashURL private method to hash the url not leveraging .hashValue so it should now be stable.


class PDFViewController: ReaderViewController {

private var isPDFViewConfigured = false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we simplify this whole configureNativePDFView / findNativePDFView / viewDidLayoutSubviews dance? It looks like the navigator already does most of this for us:

  • Doesn't the PDFPreferences we pass in PDFModule (scroll = true, scrollAxis = .vertical) already make the navigator set displaysAsBook, .singlePageContinuous, and the scroll view up in its own apply(settings:)?
  • For the scale/autoScales bits we're actually overriding, can't we use the official hook: PDFNavigatorDelegate.navigator(_:setupPDFView:)? Don't we already conform to that protocol + are set as the delegate. That'd also let us drop the recursive findNativePDFView, since pdfNavigator.pdfView is exposed publicly?

My worry with the current approach is that it reaches into the navigator's view tree and re-applies settings on a layout-timing one-shot, so it quietly fights the navigator's own apply() (e.g. autoScales/scaleFactor) and would break if we ever add a preferences UI or on rotation. The swift-toolkit TestApp's PDFViewController doesn't do any of this — it just sets preferences and the delegate.

Can we try leaning on PDFPreferences for the layout, and moving anything that's left into setupPDFView? If there was a specific reason the navigator defaults looked wrong (fit-to-width?).

Open to being wrong about this.

self?.log(.error, "Failed to open publication: \(error)")
}
},
receiveCompletion: { _ in },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did we remove error logging?

also strong vs weak capture?

self.url(path: url)
.flatMap { self.openPublication(at: $0, allowUserInteraction: true, sender: sender ) }
if let remoteURL = URL(string: url), remoteURL.scheme != nil, remoteURL.scheme != "file",
remoteURL.pathExtension.lowercased() == "pdf" || url.contains(".pdf") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems like we should rework PDF detection a bit. However, this is only relevant if we need to keep this downloading of streamed files workflow.

bookId: String,
selectionActions: [SelectionActionData]?
) throws -> ReaderViewController {
var preferences = PDFPreferences()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to interact a bit with my other note about what we're doing in the PDFViewController

Comment thread web/hooks/usePdfNavigator.ts Outdated
}, [goToPage, container]);

return { pageNumber, pageCount, goForward, goBackward, goToLocator, isReady };
}; No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newlines at end of file

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment thread apps/example-native/src/App.tsx Outdated
id: 'mi-may-newsletter',
title: 'MI May Newsletter',
author: 'Militia Immaculatae',
epubUrl: 'https://militiaoftheimmaculata.com/wp-content/uploads/2020/06/2026-MAY-E-Pub-final-web.pdf',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a more conventional test PDFs?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated from newsletters to Jane Austen's Sense and Sensibility.

Comment thread apps/example-native/src/App.tsx Outdated
id: 'mi-june-newsletter',
title: 'MI June Newsletter',
author: 'Militia Immaculatae',
epubUrl: 'https://militiaoftheimmaculata.com/wp-content/uploads/2020/06/2026-JUNE-E-Pub-final-pages-web.pdf',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

epubUrl as a key name no longer makes much sense.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a new key, pdfUrl and a new hook usePdfFile to load those files. If epubPath or epubUrl are set, uses existing useEpubFile, if pdfUrl is set, uses new usePdfFile.

Decoration,
SelectionAction,
PublicationReadyEvent,
ReadiumFile,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the demo reader of an PDF, there's a large white "gap" at the top of the PDF viewer between the control bar and the first page of the PDF. Where is this coming from?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we need to carefully think about how "zoom" is supported on PDFs. I know there's at least a double-tap zoom on iOS but pinch to zoom seems like it might be necessary?

@rhusted1982 rhusted1982 force-pushed the feat/pdf-support branch 4 times, most recently from b5c74e1 to 71c98b8 Compare June 25, 2026 03:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants