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
18 changes: 12 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,18 @@ once the version tag exists.

## [1.41]

### Added

- Documents follow your light or dark appearance. PDFs stay light.

### Changed

- The engine is odrcore 6.7.1, up from 6.6.0. Pdfs, text files and archive
listings can be searched too, and a pdf's text sits where the file puts it.
- PDFs are rendered by odrcore instead of being handed to the web view, and a
password protected one takes the prompt the other formats use. They can be
searched like a document.
- PDFs, text files and archives can be searched, and a PDF's text sits where the
file puts it.
- PDFs show what was filled into their forms, a scanned page is no longer blank,
Word documents keep the space between their paragraphs, and a large text file
opens at once.
- A password protected PDF asks for its password the way the other formats do.
- The search button leaves the tool bar when the page cannot be searched,
rather than greying out - the same as the edit button.
- The pencil turns into a save button while editing, as on Android. Saving from
Expand All @@ -40,14 +45,15 @@ once the version tag exists.

- Flat XML documents (`.fodt`, `.fodp`, `.fods`, `.fodg`), `.otm`, `.xlt` and
`.xlm` can be picked in the document browser instead of being greyed out.
odrcore rendered them already; the app claimed no type that reached them.
The app could read them already; it claimed no type that reached them.
- The app is translated again. Every language but English was showing English
for the privacy screen and the banner, and Danish, Catalan, Turkish and Czech
showed it for most of the rest.
- Tapping a document sets the cursor, so an edit can be typed. The keyboard
never came up before.
- A document opens at the top of its first page. On a wide screen it opened far
enough down to cut off the heading.
- A text file with commas in it is shown as text, not as a table.

### Removed

Expand Down
2 changes: 1 addition & 1 deletion OpenDocumentReader.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,7 @@
repositoryURL = "https://github.com/opendocument-app/OpenDocument.core.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 6.7.1;
minimumVersion = 6.9.0;
};
};
AD584FCD41577C8CDEE974AA /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */ = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions OpenDocumentReader/CoreWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ private func isCsv(_ file: DecodedFile) -> Bool { file.fileType == .commaSeparat
// 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
// the reader's own appearance: odrcore writes a dark sheet behind a
// `prefers-color-scheme: dark`, which the web view answers from the
// system setting. A pdf has no dark view and stays light.
config.colorScheme = .system
// 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
Expand Down
18 changes: 13 additions & 5 deletions OpenDocumentReader/DocumentViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -760,11 +760,19 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel
printController.present(animated: true, completionHandler: nil)
}

/// A page of ours rather than a document: the word "loading", or the error.
/// The colour scheme is named because a web view paints a page that claims
/// none white, whatever the reader has the device set to.
private func loadMessage(_ body: String) {
webview.loadHTMLString(
"<html><head><meta name=\"color-scheme\" content=\"light dark\"></head><body>\(body)</body></html>",
baseURL: nil)
}

func documentUpdateContent(_ doc: Document) {
guard let url = doc.result else {
documentNavigation = nil
self.webview.loadHTMLString(
"<html><h1>\(NSLocalizedString("loading", comment: ""))</h1></html>", baseURL: nil)
loadMessage("<h1>\(NSLocalizedString("loading", comment: ""))</h1>")

return
}
Expand Down Expand Up @@ -838,9 +846,9 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel
}

documentNavigation = nil
self.webview.loadHTMLString(
"<html><h1>\(NSLocalizedString("error", comment: ""))</h1>\(NSLocalizedString("toast_error_generic", comment: ""))</html>",
baseURL: nil)
loadMessage(
"<h1>\(NSLocalizedString("error", comment: ""))</h1>\(NSLocalizedString("toast_error_generic", comment: ""))"
)

AnalyticsManager.shared.report(
"load_error",
Expand Down
17 changes: 16 additions & 1 deletion OpenDocumentReaderTests/OpenDocumentReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,20 @@ class OpenDocumentReaderTests: XCTestCase {
}
}

/// The page carries both sheets and picks between them itself, so turning
/// the device dark needs no second translation.
func testAPageFollowsTheReadersAppearance() throws {
let wrapper = CoreWrapper()

try wrapper.translate(
documentURL.path, cache: temporaryDirectory, into: temporaryDirectory, with: nil, editable: false)

let (data, _) = try fetch(try XCTUnwrap(wrapper.pageURLs.first))
let html = try XCTUnwrap(String(data: data, encoding: .utf8))

XCTAssertTrue(html.contains("(prefers-color-scheme: dark)"), html)
}

/// The same URL would come back out of the web view's cache holding the
/// pages the document had before the password or the edit.
func testRetranslatingMovesThePagesToNewAddresses() throws {
Expand Down Expand Up @@ -380,7 +394,8 @@ class OpenDocumentReaderTests: XCTestCase {
}

/// Fulfills its expectation once, whichever way the navigation ends.
private class NavigationRecorder: NSObject, WKNavigationDelegate {
/// Also used by `PrintAppearanceTests`.
class NavigationRecorder: NSObject, WKNavigationDelegate {
let finished: XCTestExpectation
private(set) var error: Error?
private var isDone = false
Expand Down
116 changes: 116 additions & 0 deletions OpenDocumentReaderTests/PrintAppearanceTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import UIKit
import WebKit
import XCTest

@testable import OpenDocumentReader

/// Print takes the page the web view is showing, and since odrcore renders a
/// document in the reader's own appearance that page can be dark. Paper is not:
/// a dark page printed as it stands is pale ink on white, which is nothing.
class PrintAppearanceTests: XCTestCase {

/// The web view prints in light whatever the device is set to, so the menu
/// needs nothing of its own. This says so, and would say if that changed.
func testPrintingADarkPageComesOutOnWhitePaper() throws {
let webview = WKWebView(frame: CGRect(x: 0, y: 0, width: 390, height: 700))

// in a window, because that is where a view is told what appearance it
// is in: a web view on its own stays light whatever is set on it
let window = UIWindow(frame: webview.frame)
window.overrideUserInterfaceStyle = .dark
window.rootViewController = UIViewController()
window.rootViewController?.view.addSubview(webview)
window.makeKeyAndVisible()

let recorder = NavigationRecorder(finished: expectation(description: "loaded"))
webview.navigationDelegate = recorder

// the shape odrcore emits: a dark sheet gated on the reader's preference
webview.loadHTMLString(
"""
<html><head><meta name="color-scheme" content="light dark">
<style media="(prefers-color-scheme: dark)">
:root{color-scheme:dark}
body{background:#0d1117;color:#e6edf3}
</style></head><body><p>Hello</p></body></html>
""", baseURL: nil)

wait(for: [recorder.finished], timeout: 30)
XCTAssertNil(recorder.error)

// or the rest of this passes without ever having been dark
XCTAssertEqual(try evaluate("matchMedia('(prefers-color-scheme: dark)').matches", on: webview) as? Bool, true)

let page = try printFirstPage(of: webview)
let (mean, darkest) = try brightness(of: page)

XCTAssertGreaterThan(mean, 0.9, "the paper came out dark")
XCTAssertLessThan(darkest, 0.3, "nothing dark was printed: the text went white on white")
}

private func evaluate(_ script: String, on webview: WKWebView) throws -> Any? {
var result: Any?
let done = expectation(description: script)

webview.evaluateJavaScript(script) { value, error in
result = value
XCTAssertNil(error)
done.fulfill()
}

wait(for: [done], timeout: 30)

return result
}

/// One page of what `printDocument` hands the print controller, drawn onto
/// white the way paper is.
private func printFirstPage(of webview: WKWebView) throws -> UIImage {
// US Letter at 72dpi, which is what UIPrintPageRenderer measures in
let paper = CGRect(x: 0, y: 0, width: 612, height: 792)

let renderer = UIPrintPageRenderer()
renderer.addPrintFormatter(webview.viewPrintFormatter(), startingAtPageAt: 0)
// the two rects have no setters of their own
renderer.setValue(paper, forKey: "paperRect")
renderer.setValue(paper.insetBy(dx: 36, dy: 36), forKey: "printableRect")

UIGraphicsBeginImageContextWithOptions(paper.size, true, 1)
defer { UIGraphicsEndImageContext() }

UIColor.white.setFill()
UIRectFill(paper)
renderer.prepare(forDrawingPages: NSRange(location: 0, length: 1))
renderer.drawPage(at: 0, in: paper)

return try XCTUnwrap(UIGraphicsGetImageFromCurrentImageContext())
}

private func brightness(of image: UIImage) throws -> (mean: Double, darkest: Double) {
let cgImage = try XCTUnwrap(image.cgImage)
let width = cgImage.width
let height = cgImage.height

var pixels = [UInt8](repeating: 0, count: width * height * 4)
let context = try XCTUnwrap(
CGContext(
data: &pixels, width: width, height: height, bitsPerComponent: 8,
bytesPerRow: width * 4, space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue))
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

var total = 0.0
var darkest = 1.0

for pixel in stride(from: 0, to: pixels.count, by: 4) {
let value =
(0.299 * Double(pixels[pixel]) + 0.587 * Double(pixels[pixel + 1])
+ 0.114 * Double(pixels[pixel + 2])) / 255

total += value
darkest = min(darkest, value)
}

return (total / Double(width * height), darkest)
}
}
2 changes: 2 additions & 0 deletions fastlane/metadata/en-US/changelogs/1.41.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
- Documents follow your light or dark appearance
- PDFs can be searched, and password protected ones ask for the password like other documents
- PDFs show what was filled into their forms, and scanned pages are no longer blank
- Tapping a document while editing puts the cursor there, and saving is now on the bar rather than in the menu
- LibreOffice's flat XML documents and Excel templates open from the document browser instead of being greyed out
- The three welcome pages are gone, so the app opens straight on your documents