Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ once the version tag exists.

### Changed

- A document wider than the screen opens fitted to it rather than running off
the edge, which is what it has always done on Android.
- The way out of a document is a back chevron, not the words "Back to
documents".
- Documents that can be edited offer a pencil next to the search button.
Expand Down
12 changes: 10 additions & 2 deletions OpenDocumentReader/CoreWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,23 @@ private func isCsv(_ file: DecodedFile) -> Bool { file.fileType == .commaSeparat
throw coreWrapperError(.unsupportedFileType, "not a document file")
}

// the same answers OpenDocument.droid gives odrcore, so a document is
// the same document on both — the viewport meta each page carries is
// decided from these
let config = HtmlConfig()
config.editable = editable
// resource paths are resolved relative to an output directory, and in
// server mode there is none — odrcore rejects the combination
config.relativeResourcePaths = false
// the side margins of a printed page, which is what it was written to look like
// the side margins of a printed page, which is what it was written to
// look like, and what makes odrcore call a text document paged: its
// pages are then fitted to the screen rather than shown at full size
config.textDocumentMargin = true
// served with the pages rather than inlined as base64, as in OpenDocument.droid
// served with the pages rather than inlined as base64
config.embedImages = false
// odrcore's own css and js go into the page: there is no output
// directory to put them beside
config.embedShippedResources = true

let documentType: DocumentType
let openedDocument: OdrCoreObjC.Document?
Expand Down
57 changes: 57 additions & 0 deletions OpenDocumentReader/DocumentViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,61 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel
private lazy var toolBarItems: [UIBarButtonItem] = toolBar.items ?? []
private lazy var toolBarItemsWithoutEdit: [UIBarButtonItem] = toolBarItems.filter { $0 !== editButton }

/// What OpenDocument.droid gets from `loadWithOverviewMode`, which iOS has
/// no setting for: a page wider than the screen is zoomed out until it fits
/// instead of running off the edge.
///
/// odrcore asks for that by leaving the initial scale out of the viewport
/// meta - `width=device-width` alone, which every browser but a web view in
/// overview mode reads as "lay out at screen width and let the rest
/// overflow". A page that names its scale (a spreadsheet, a csv) means it,
/// and is left alone.
///
/// Only for what odrcore served, which is why `origin` is checked here as
/// well as before the script is installed: the same web view shows the
/// formats odrcore does not handle, and follows links out of a document.
/// Their viewport is their author's to write, and rewriting it would throw
/// away what it says - `user-scalable=no`, a maximum scale, a `viewport-fit`.
private static func fitToWidthScript(servedFrom origin: String) -> String {
"""
(function () {
if (location.origin !== '\(origin)') {
return;
}

var meta = document.querySelector('meta[name="viewport"]');
if (!meta || (meta.content || '').indexOf('initial-scale') !== -1) {
return;
}

var width = document.documentElement.scrollWidth;
if (width > window.innerWidth) {
meta.setAttribute('content', 'width=' + width + ',user-scalable=yes');
}
})();
"""
}

/// Arms ``fitToWidthScript(servedFrom:)`` for a page that came off our own
/// server, and disarms it for anything else. At document end rather than on
/// `didFinish`, so the page is fitted before it is first drawn instead of
/// jumping once the images are in.
private func installFitToWidth(for url: URL) {
let scripts = webview.configuration.userContentController
scripts.removeAllUserScripts()

guard CoreWrapper.isServedURL(url),
let scheme = url.scheme, let host = url.host, let port = url.port
else {
return
}

scripts.addUserScript(
WKUserScript(
source: Self.fitToWidthScript(servedFrom: "\(scheme)://\(host):\(port)"),
injectionTime: .atDocumentEnd, forMainFrameOnly: true))
}

/// Fills the banner slot when no ad does. Sits on top of `bannerSlot` rather than in the
/// layout chain, so the slot keeps its height and nothing below it moves.
private let houseAdView = HouseAdView()
Expand Down Expand Up @@ -572,6 +627,8 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel
return
}

installFitToWidth(for: url)

// pages come off the loopback server; a file URL needs read access
// granted along with it
if url.isFileURL {
Expand Down