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

- A zip file opens and lists what is inside. Tapping an entry shows it.
- Photos and text files the reader used to refuse now open.
- Rich text files open.
- Markdown files are shown as prose: a heading is a heading, and the hashes and
stars are gone.

### Changed

- The engine is odrcore 6.11.0, up from 6.10.1.
- A file that will not open says so, and offers to write to us when something
went wrong on our side.
- The app no longer offers itself for music and films.
- A link out of a document opens in the browser, rather than leading nowhere.
- Slides show their colours: text, highlights and the fill behind a shape.
- Text in a PDF no longer runs over the page in giant type, and can be selected,
searched and copied where it came out as Chinese, Japanese or Korean.
- A PDF opens faster and zooms without stalling.
- A tall spreadsheet keeps many more rows before it stops; a very wide one keeps
fewer.
- A single-file OpenDocument shows the document, not its source.

### Fixed

- A file saved by the reader opens in LibreOffice again.
- A presentation exported from Google Slides opens.
- A Photoshop or JPEG 2000 file says it will not open, instead of showing a
blank page.

## [1.42]

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.10.1;
minimumVersion = 6.11.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.

43 changes: 36 additions & 7 deletions OpenDocumentReader/CoreWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,37 @@ private func selectViews(_ views: [HtmlView], _ documentType: DocumentType) -> [
private var document: OdrCoreObjC.Document?
private let lock = NSRecursiveLock()

/// The largest sheet region translated, as on OpenDocument.droid. The
/// encoding is written out because Swift has no `@encode`.
private static let spreadsheetLimit: NSValue = withUnsafeBytes(
of: TableDimensions(rows: 100_000, columns: 500)
) { NSValue(bytes: $0.baseAddress!, objCType: "{ODRTableDimensions=II}") }

/// Bounds the rows by the sheet's width: the wider, the fewer it keeps.
private static let spreadsheetCellLimit: UInt64 = 500_000

/// As odrcore reads the bytes, or as the name says where that reading is
/// text. Markdown has no signature, so only the name can name it.
private func openFile(_ inputPath: String) throws -> DecodedFile {
let detected = try DecodedFile.decode(path: inputPath)
let declared = Odr.fileType(extension: URL(fileURLWithPath: inputPath).pathExtension)

guard declared != .unknown, declared != detected.fileType, detected.isTextFile,
nameOutranksText(declared)
else {
return detected
}

return (try? DecodedFile.decode(path: inputPath, as: declared)) ?? detected
}

/// A document, or a format odrcore cannot detect from its bytes. Csv is
/// neither: odrcore reads that out of the text itself.
private func nameOutranksText(_ declared: FileType) -> Bool {
Odr.fileCategory(fileType: declared) == .document
|| !Odr.capabilities(fileType: declared).detectByContent
}

@objc func translate(
_ inputPath: String,
cache cachePath: String,
Expand All @@ -111,7 +142,7 @@ private func selectViews(_ views: [HtmlView], _ documentType: DocumentType) -> [
throw coreWrapperError(.unsupportedFileType, "odrcore does not recognise this file type")
}

var file = try DecodedFile.decode(path: inputPath)
var file = try openFile(inputPath)
if file.isPasswordEncrypted {
do {
file = try file.decrypt(withPassword: password ?? "")
Expand All @@ -130,12 +161,6 @@ private func selectViews(_ views: [HtmlView], _ documentType: DocumentType) -> [
throw coreWrapperError(.unsupportedFileType, "odrcore does not render this file type")
}

// odrcore calls a file it recognises as nothing else text, so an unnamed
// charset means the bytes are not text at all
if file.isTextFile, (try? file.asTextFile())?.charset == nil {
throw coreWrapperError(.unsupportedFileType, "odrcore could not name a charset")
}

// 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
Expand All @@ -157,6 +182,10 @@ private func selectViews(_ views: [HtmlView], _ documentType: DocumentType) -> [
// odrcore's own css and js go into the page: there is no output
// directory to put them beside
config.embedShippedResources = true
// stated rather than inherited: a sheet past the limit is cut off silently
config.spreadsheetLimit = Self.spreadsheetLimit
config.spreadsheetCellLimit = NSNumber(value: Self.spreadsheetCellLimit)
config.spreadsheetLimitByContent = true

let documentType: DocumentType
let openedDocument: OdrCoreObjC.Document?
Expand Down
60 changes: 37 additions & 23 deletions OpenDocumentReader/DocumentViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -246,17 +246,20 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel
])
}

/// odrcore writes every link with `target="_blank"`, and this app has no
/// second window: what odrcore serves opens in the web view that asked.
/// Only a link that leaves the page carries `target="_blank"`, and this app
/// has no second window: the web goes to the browser, and what odrcore
/// serves — should any of it arrive here — to the web view that asked.
func webView(
_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,
for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures
) -> WKWebView? {
guard let url = navigationAction.request.url, CoreWrapper.isServedURL(url) else {
return nil
}
guard let url = navigationAction.request.url else { return nil }

webView.load(navigationAction.request)
if CoreWrapper.isServedURL(url) {
webView.load(navigationAction.request)
} else if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}

return nil
}
Expand All @@ -278,12 +281,12 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel
if !navigationResponse.canShowMIMEType {
decisionHandler(.cancel)

// the system could not draw it after all, so fall back to the listing
if let listing = listingInReserve {
listingInReserve = nil
// the system could not draw it after all, so fall back to odrcore's
if let page = corePageInReserve {
corePageInReserve = nil

installFitToWidth(for: listing)
documentNavigation = webview.load(URLRequest(url: listing))
installFitToWidth(for: page)
documentNavigation = webview.load(URLRequest(url: page))

return
}
Expand All @@ -293,7 +296,7 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel
return
}

listingInReserve = nil
corePageInReserve = nil

guard let response = navigationResponse.response as? HTTPURLResponse,
response.statusCode >= 400,
Expand Down Expand Up @@ -875,10 +878,9 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel
return
}

// only a container to odrcore, but a document to the system: it gets
// the first go, with the listing kept in reserve
if doc.isArchive, systemKnowsItAsADocument(doc.fileURL) {
listingInReserve = url
// the system gets the first go, with odrcore's page kept in reserve
if systemDrawsItBetter(doc) {
corePageInReserve = url

canEdit = false
canSearch = false
Expand All @@ -892,16 +894,28 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel
documentNavigation = self.webview.load(URLRequest(url: url))
}

/// Whether the system's type for this file says document rather than
/// container: a `.pages` is composite content, a `.zip` is not.
private func systemKnowsItAsADocument(_ url: URL) -> Bool {
guard let type = UTType(filenameExtension: url.pathExtension) else { return false }
/// Three the system draws better, all falling back to `corePageInReserve`:
/// html, which odrcore has no type for and reads as its own source, iWork,
/// whose styles and pictures it does not read, and a container it knows as a
/// document (`.epub` is composite content, `.zip` is not).
private func systemDrawsItBetter(_ doc: Document) -> Bool {
let ext = doc.fileURL.pathExtension.lowercased()

guard let type = UTType(filenameExtension: ext), !type.isDynamic else { return false }

return !type.isDynamic && type.conforms(to: .compositeContent)
return Self.webPageTypes.contains(where: type.conforms(to:))
|| Self.iWorkExtensions.contains(ext)
|| (doc.isArchive && type.conforms(to: .compositeContent))
}

/// odrcore's listing, held back while the system has the first go.
private var listingInReserve: URL?
/// Both, because `public.xhtml` conforms to xml rather than to html — and
/// xml is what a flat ODF is, which odrcore does render.
private static let webPageTypes: [UTType] = [.html, UTType("public.xhtml")].compactMap { $0 }

private static let iWorkExtensions: Set<String> = ["pages", "numbers", "key"]

/// odrcore's page, held back while the system has the first go.
private var corePageInReserve: URL?

func documentEncrypted(_ doc: Document) {
// the document is opened before this controller is presented, so the
Expand Down
31 changes: 29 additions & 2 deletions OpenDocumentReaderTests/ArchiveDocumentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,7 @@ class ArchiveDocumentTests: XCTestCase {
XCTAssertFalse(shown.contains(NSLocalizedString("toast_error_generic", comment: "")), shown)
}

/// A `.pages` is a zip to odrcore, but a document to the system, which gets
/// the first go.
/// odrcore reads a `.pages`, but only its text: the system gets the first go.
func testADocumentTheSystemKnowsIsLeftToTheSystem() throws {
try present(try copyFixture(ofType: "pages"))

Expand All @@ -112,6 +111,34 @@ class ArchiveDocumentTests: XCTestCase {
XCTAssertNil(controller.presentedViewController, "it said the file would not open")
}

/// odrcore has no html type, so it reads the page's own source as text. The
/// system draws the page, as it did before the reader asked the core.
func testHtmlIsLeftToTheSystem() throws {
for pathExtension in ["html", "htm", "xhtml"] {
try present(try writeHtml(ofType: pathExtension))

let opened = expectation(description: "opened " + pathExtension)
document.open { _ in opened.fulfill() }
wait(for: [opened], timeout: 60)

let shown = try XCTUnwrap(waitForURL { $0.isFileURL }, pathExtension)

XCTAssertEqual(shown.lastPathComponent, "test." + pathExtension, shown.absoluteString)
XCTAssertNil(controller.presentedViewController, "it said the file would not open")
}
}

private func writeHtml(ofType pathExtension: String) throws -> URL {
let documentsURL = try FileManager.default.url(
for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)

let url = documentsURL.appendingPathComponent("test." + pathExtension)
try? FileManager.default.removeItem(at: url)
try "<html><body><h1>Heading</h1></body></html>".write(to: url, atomically: true, encoding: .utf8)

return url
}

/// And when the system cannot draw it after all, the listing takes over
/// rather than a message.
func testAnArchiveTheSystemCannotDrawFallsBackToTheListing() throws {
Expand Down
35 changes: 35 additions & 0 deletions OpenDocumentReaderTests/OpenDocumentReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,41 @@ class OpenDocumentReaderTests: XCTestCase {
XCTAssertEqual(wrapper.pageNames, ["text"])
}

/// A text file comes back as `text`; a markdown one as a document, with the
/// hashes and stars turned into a heading and a bold run.
func testMarkdownIsReadAsProse() throws {
let wrapper = CoreWrapper()

let notes = URL(fileURLWithPath: temporaryDirectory).appendingPathComponent("notes.md")
try "# Heading\n\nSome **bold** prose.\n".write(to: notes, atomically: true, encoding: .utf8)

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

XCTAssertEqual(wrapper.pageNames, ["document"])

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

XCTAssertTrue(html.contains("font-size:2em"), "the heading is not one")
XCTAssertTrue(html.contains("font-weight:bold"), "the bold run is not bold")
XCTAssertFalse(html.contains("# Heading"), "the hashes are still in it")
XCTAssertFalse(html.contains("**bold**"), "the stars are still in it")
}

/// A `.csv` is odrcore's decision from the text; the name must not take it.
func testCsvIsStillOdrcoresDecision() throws {
let wrapper = CoreWrapper()

let rows = URL(fileURLWithPath: temporaryDirectory).appendingPathComponent("rows.csv")
try "a,b\n1,2\n".write(to: rows, atomically: true, encoding: .utf8)

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

XCTAssertFalse(wrapper.pageNames.isEmpty)
}

func testTranslatePerformance() throws {
let wrapper = CoreWrapper()
let path = documentURL.path
Expand Down
7 changes: 2 additions & 5 deletions fastlane/metadata/de-DE/description.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,10 @@ Darüber hinaus unterstützt der Dokumentenbetrachter viele weitere Dateiformate
- Bilder: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG, etc
- Videos: MP4, WEBM, etc
- Audio: MP3, OGG, etc
- Textdateien: CSV, TXT, HTML, RTF
- Textdateien: CSV, TXT, HTML, MD, RTF
- Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX)
- Apple iWork: Pages, Numbers, Keynote
- Libre Office und Open Office ODF (ODT, ODS, ODP, ODG)
- PostScript (EPS)
- AutoCAD (DXF)
- Photoshop (PSD)
- Libre Office und Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG)

Diese App ist Open Source. Wir stehen in keiner Verbindung zu OpenOffice, LibreOffice oder ähnlichen Projekten. Made in Austria. ${ads} Über Rückmeldungen jeder Art per E-Mail freuen wir uns sehr.

Expand Down
7 changes: 2 additions & 5 deletions fastlane/metadata/en-US/description.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,10 @@ In addition to that, the document reader aims to support various other file form
- Images: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG, etc
- Videos: MP4, WEBM, etc
- Audio: MP3, OGG, etc
- Text files: CSV, TXT, HTML, RTF
- Text files: CSV, TXT, HTML, MD, RTF
- Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX)
- Apple iWork: Pages, Numbers, Keynote
- Libre Office and Open Office ODF (ODT, ODS, ODP, ODG)
- PostScript (EPS)
- AutoCAD (DXF)
- Photoshop (PSD)
- Libre Office and Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG)

This app is open source. We are not affiliated with OpenOffice, LibreOffice or similar. Made in Austria. ${ads} We highly appreciate all kinds of feedback via email.

Expand Down
7 changes: 2 additions & 5 deletions fastlane/metadata/es-ES/description.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,10 @@ Además, el lector de documentos procura admitir lo mejor posible muchos otros f
- Imágenes: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG, etc.
- Vídeos: MP4, WEBM, etc.
- Audio: MP3, OGG, etc.
- Archivos de texto: CSV, TXT, HTML, RTF
- Archivos de texto: CSV, TXT, HTML, MD, RTF
- Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX)
- Apple iWork: Pages, Numbers, Keynote
- Libre Office y Open Office ODF (ODT, ODS, ODP, ODG)
- PostScript (EPS)
- AutoCAD (DXF)
- Photoshop (PSD)
- Libre Office y Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG)

Esta aplicación es de código abierto. No estamos afiliados con OpenOffice, LibreOffice ni programas similares. Desarrollada en Austria. ${ads} Agradecemos muchísimo cualquier comentario por correo electrónico.

Expand Down
7 changes: 2 additions & 5 deletions fastlane/metadata/fr-FR/description.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,10 @@ Par ailleurs, la visionneuse de documents a pour objectif de prendre en charge a
- Images : JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG, etc
- Vidéos : MP4, WEBM, etc
- Audio : MP3, OGG, etc
- Fichiers texte : CSV, TXT, HTML, RTF
- Fichiers texte : CSV, TXT, HTML, MD, RTF
- Microsoft Office (OOXML) : Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX)
- Apple iWork : Pages, Numbers, Keynote
- Libre Office et Open Office ODF (ODT, ODS, ODP, ODG)
- PostScript (EPS)
- AutoCAD (DXF)
- Photoshop (PSD)
- Libre Office et Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG)

Cette application est open source. Nous ne sommes affiliés ni à OpenOffice, ni à LibreOffice, ni à aucun projet similaire. Fabriquée en Autriche. ${ads} Tous vos retours par e-mail sont les bienvenus.

Expand Down
7 changes: 2 additions & 5 deletions fastlane/metadata/hi/description.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,10 @@ LibreOffice या OpenOffice में बनी ODF फाइलें (ODT, O
- इमेज: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG आदि
- वीडियो: MP4, WEBM आदि
- ऑडियो: MP3, OGG आदि
- टेक्स्ट फाइलें: CSV, TXT, HTML, RTF
- टेक्स्ट फाइलें: CSV, TXT, HTML, MD, RTF
- Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX)
- Apple iWork: Pages, Numbers, Keynote
- Libre Office और Open Office ODF (ODT, ODS, ODP, ODG)
- PostScript (EPS)
- AutoCAD (DXF)
- Photoshop (PSD)
- Libre Office और Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG)

यह ऐप ओपन सोर्स है। OpenOffice, LibreOffice या इनसे मिलते-जुलते किसी भी संगठन से हमारा कोई संबंध नहीं है। ऑस्ट्रिया में बना। ${ads} हर तरह की राय ईमेल से भेजें, हमें बहुत अच्छा लगेगा।

Expand Down
Loading