From c60bf1b2566db8ccfe93739d5d4b76c6d209b1b1 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sat, 29 Aug 2026 11:31:31 +0200 Subject: [PATCH 1/6] Take odrcore 6.11.0 Three workarounds this app filed upstream come back fixed, and go: the fit-to-width user script, replaced by odrcore's new viewport mode; the charset check, now that a file which is not text no longer comes back as text; and the handling for a blanket link target, leaving only the web to send to the browser. States the spreadsheet limits rather than inheriting them, as OpenDocument.droid does. Pages, Keynote and Numbers keep the system's rendering. odrcore reads all three now, so the routing that caught them - a container the system knows as a document - no longer fires and they are named instead. It reads their text and not their styles or pictures, which is less than the phone draws. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Y7ALzy5VB32UfQn263Tbnk --- CHANGELOG.md | 17 ++ OpenDocumentReader.xcodeproj/project.pbxproj | 2 +- OpenDocumentReader/CoreWrapper.swift | 21 ++- .../DocumentViewController.swift | 155 ++++-------------- .../ArchiveDocumentTests.swift | 3 +- 5 files changed, 65 insertions(+), 133 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa2a167..72a4831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,12 +23,29 @@ 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. ### Changed +- The engine is odrcore 7.0.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] diff --git a/OpenDocumentReader.xcodeproj/project.pbxproj b/OpenDocumentReader.xcodeproj/project.pbxproj index 7e28a5a..a88c25a 100644 --- a/OpenDocumentReader.xcodeproj/project.pbxproj +++ b/OpenDocumentReader.xcodeproj/project.pbxproj @@ -830,7 +830,7 @@ repositoryURL = "https://github.com/opendocument-app/OpenDocument.core.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 6.10.1; + minimumVersion = 7.0.0; }; }; AD584FCD41577C8CDEE974AA /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */ = { diff --git a/OpenDocumentReader/CoreWrapper.swift b/OpenDocumentReader/CoreWrapper.swift index 334658b..75de870 100644 --- a/OpenDocumentReader/CoreWrapper.swift +++ b/OpenDocumentReader/CoreWrapper.swift @@ -91,6 +91,15 @@ 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 + @objc func translate( _ inputPath: String, cache cachePath: String, @@ -130,12 +139,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 @@ -157,6 +160,12 @@ 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 + // a WKWebView fits no top-level document, so the view fits itself + config.viewportMode = .fitWidthByView + // 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? diff --git a/OpenDocumentReader/DocumentViewController.swift b/OpenDocumentReader/DocumentViewController.swift index f1ec09f..f44f850 100644 --- a/OpenDocumentReader/DocumentViewController.swift +++ b/OpenDocumentReader/DocumentViewController.swift @@ -75,105 +75,6 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel } } - /// 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 served = meta.content; - var natural = document.documentElement.scrollWidth; - - // The web view's own width, which the viewport named below does not - // change: the visual viewport is that many CSS pixels at that scale. - function available() { - var seen = window.visualViewport; - - return seen ? Math.round(seen.width * seen.scale) : window.innerWidth; - } - - function fit() { - meta.setAttribute( - 'content', - natural > available() ? 'width=' + natural + ',user-scalable=yes' : served); - } - - // Across a resize the browser keeps the reader's place by holding on - // to whatever was against the top of the screen, and here it gets it - // wrong: the scale changes with the width, and the page comes back - // hundreds of pixels down - a page or more of a long document on an - // iPad, which is where the width really does change. It settles - // there some frames after the resize, so the place the reader was - // actually at is re-asserted until it has finished, and dropped the - // moment they take hold of the page themselves. - var holding = []; - - function hold(place) { - holding.forEach(clearTimeout); - holding = [0, 16, 50, 150, 300, 500].map(function (ms) { - return setTimeout(function () { window.scrollTo(window.scrollX, place); }, ms); - }); - } - - window.addEventListener('touchstart', function () { - holding.forEach(clearTimeout); - holding = []; - }, { passive: true }); - - // On every resize, not just now: this first runs before the web view - // has the width it will keep, and a page held at a width it no - // longer needs is left scrolled off its own top. - fit(); - window.addEventListener('resize', function () { - var was = window.scrollY; - fit(); - hold(was); - }); - })(); - """ - } - - /// 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() @@ -246,17 +147,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 } @@ -278,12 +182,11 @@ 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)) + documentNavigation = webview.load(URLRequest(url: page)) return } @@ -293,7 +196,7 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel return } - listingInReserve = nil + corePageInReserve = nil guard let response = navigationResponse.response as? HTTPURLResponse, response.statusCode >= 400, @@ -875,10 +778,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 @@ -887,21 +789,26 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel return } - installFitToWidth(for: url) - 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 } + /// Two files the system draws better, both falling back to `corePageInReserve` + /// when it turns out it cannot: a container the system knows as a document — + /// an `.epub` is composite content, a `.zip` is not — and iWork, whose text + /// odrcore reads but whose styles and pictures it does 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.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? + private static let iWorkExtensions: Set = ["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 diff --git a/OpenDocumentReaderTests/ArchiveDocumentTests.swift b/OpenDocumentReaderTests/ArchiveDocumentTests.swift index 9986f57..ed89b80 100644 --- a/OpenDocumentReaderTests/ArchiveDocumentTests.swift +++ b/OpenDocumentReaderTests/ArchiveDocumentTests.swift @@ -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")) From 86a49921bfca90b081cbd59b939ea5c591557ef8 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sat, 29 Aug 2026 11:31:31 +0200 Subject: [PATCH 2/6] Hit-test the run in the space the zoom leaves it in The view fits itself by applying a zoom now, and webkit leaves that out of getBoundingClientRect while elementFromPoint expects it in - so the tap the test aims at the middle of an editable run landed outside it and hit nothing. odrcore 6.11.0 ships getViewportRect for this. Only the test hit-tests in script; a real tap is webkit's own and was never affected. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Y7ALzy5VB32UfQn263Tbnk --- CHANGELOG.md | 2 +- OpenDocumentReader.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- OpenDocumentReaderTests/EditWorkflowTests.swift | 4 +++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72a4831..8122f33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ once the version tag exists. ### Changed -- The engine is odrcore 7.0.0, up from 6.10.1. +- 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. diff --git a/OpenDocumentReader.xcodeproj/project.pbxproj b/OpenDocumentReader.xcodeproj/project.pbxproj index a88c25a..43cd480 100644 --- a/OpenDocumentReader.xcodeproj/project.pbxproj +++ b/OpenDocumentReader.xcodeproj/project.pbxproj @@ -830,7 +830,7 @@ repositoryURL = "https://github.com/opendocument-app/OpenDocument.core.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 7.0.0; + minimumVersion = 6.11.0; }; }; AD584FCD41577C8CDEE974AA /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */ = { diff --git a/OpenDocumentReader.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OpenDocumentReader.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ddb8c64..56895ca 100644 --- a/OpenDocumentReader.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OpenDocumentReader.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/opendocument-app/OpenDocument.core.git", "state" : { - "revision" : "d1573b0cb8c9fd172b59ce785d40ba9ddb745ba2", - "version" : "6.10.1" + "revision" : "3fa5603c9093ac2df3ca0596d612ad11390e7f95", + "version" : "6.11.0" } }, { diff --git a/OpenDocumentReaderTests/EditWorkflowTests.swift b/OpenDocumentReaderTests/EditWorkflowTests.swift index 51edacf..4b0d7f4 100644 --- a/OpenDocumentReaderTests/EditWorkflowTests.swift +++ b/OpenDocumentReaderTests/EditWorkflowTests.swift @@ -107,7 +107,9 @@ class EditWorkflowTests: XCTestCase { """ (function () { var run = document.querySelector('[contenteditable]'); - var box = run.getBoundingClientRect(); + // not getBoundingClientRect: the view applies a zoom to fit, + // which webkit leaves out of it but elementFromPoint expects + var box = odr.getViewportRect(run); var hit = document.elementFromPoint(box.left + box.width / 2, box.top + box.height / 2); return hit ? hit.tagName : 'none'; From a131cf39c949bc3b99a09b6aed86b2894152841d Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sat, 29 Aug 2026 12:42:30 +0200 Subject: [PATCH 3/6] Say what the engine opens, in the listing too The same correction as OpenDocument.droid, minus one line. PostScript (EPS) and AutoCAD (DXF) have never been file types odrcore has. Photoshop (PSD) had a type but declared html output it could not produce, and 6.11.0 stops claiming it. HTML has no type either. Four claims with nothing behind them. Going the other way: rtf and Apple iWork were already promised here and are true as of this release, and flat ODF - FODT, FODS, FODP, FODG - opens like the packaged kind, which Info.plist already declares. Markdown is the line this listing does not get. The core opens one, but it has no signature and only decodes when it is asked for by name, and CoreWrapper decodes by detection alone - so a `.md` arrives here as the plain text it also is. `decode(path:as:)` is what would change that, and until something calls it this listing would be promising prose the app does not render. OpenDocument.droid asks, through `nameOutranksText`, and says markdown. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_013MnPChoTUsGrb6TMMy8nc1 --- fastlane/metadata/de-DE/description.txt | 7 ++----- fastlane/metadata/en-US/description.txt | 7 ++----- fastlane/metadata/es-ES/description.txt | 7 ++----- fastlane/metadata/fr-FR/description.txt | 7 ++----- fastlane/metadata/hi/description.txt | 7 ++----- fastlane/metadata/it/description.txt | 7 ++----- fastlane/metadata/pl/description.txt | 7 ++----- fastlane/metadata/pt-BR/description.txt | 7 ++----- fastlane/metadata/ru/description.txt | 7 ++----- fastlane/metadata/sv/description.txt | 7 ++----- fastlane/metadata/tr/description.txt | 7 ++----- 11 files changed, 22 insertions(+), 55 deletions(-) diff --git a/fastlane/metadata/de-DE/description.txt b/fastlane/metadata/de-DE/description.txt index d5cfb84..1b7415d 100644 --- a/fastlane/metadata/de-DE/description.txt +++ b/fastlane/metadata/de-DE/description.txt @@ -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, 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. diff --git a/fastlane/metadata/en-US/description.txt b/fastlane/metadata/en-US/description.txt index a4e1711..a43c6f1 100644 --- a/fastlane/metadata/en-US/description.txt +++ b/fastlane/metadata/en-US/description.txt @@ -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, 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. diff --git a/fastlane/metadata/es-ES/description.txt b/fastlane/metadata/es-ES/description.txt index d7d2790..6a3ee44 100644 --- a/fastlane/metadata/es-ES/description.txt +++ b/fastlane/metadata/es-ES/description.txt @@ -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, 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. diff --git a/fastlane/metadata/fr-FR/description.txt b/fastlane/metadata/fr-FR/description.txt index 9248131..95ca84d 100644 --- a/fastlane/metadata/fr-FR/description.txt +++ b/fastlane/metadata/fr-FR/description.txt @@ -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, 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. diff --git a/fastlane/metadata/hi/description.txt b/fastlane/metadata/hi/description.txt index 88c1b0e..ad026fd 100644 --- a/fastlane/metadata/hi/description.txt +++ b/fastlane/metadata/hi/description.txt @@ -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, 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} हर तरह की राय ईमेल से भेजें, हमें बहुत अच्छा लगेगा। diff --git a/fastlane/metadata/it/description.txt b/fastlane/metadata/it/description.txt index ac0e5e3..b6170f6 100644 --- a/fastlane/metadata/it/description.txt +++ b/fastlane/metadata/it/description.txt @@ -24,13 +24,10 @@ Oltre a questo, il lettore di documenti punta a supportare al meglio anche molti - Immagini: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG, ecc. - Video: MP4, WEBM, ecc. - Audio: MP3, OGG, ecc. -- File di testo: CSV, TXT, HTML, RTF +- File di testo: CSV, TXT, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote -- Libre Office e Open Office ODF (ODT, ODS, ODP, ODG) -- PostScript (EPS) -- AutoCAD (DXF) -- Photoshop (PSD) +- Libre Office e Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) Questa app è open source. Non siamo affiliati a OpenOffice, LibreOffice o simili. Made in Austria. ${ads} Ogni tuo commento via email è sempre benvenuto. diff --git a/fastlane/metadata/pl/description.txt b/fastlane/metadata/pl/description.txt index 53dcb3e..082ec70 100644 --- a/fastlane/metadata/pl/description.txt +++ b/fastlane/metadata/pl/description.txt @@ -24,13 +24,10 @@ Poza tym przeglądarka dokumentów stara się jak najlepiej obsługiwać równie - Obrazy: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG i inne - Filmy: MP4, WEBM i inne - Dźwięk: MP3, OGG i inne -- Pliki tekstowe: CSV, TXT, HTML, RTF +- Pliki tekstowe: CSV, TXT, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote -- Libre Office i Open Office ODF (ODT, ODS, ODP, ODG) -- PostScript (EPS) -- AutoCAD (DXF) -- Photoshop (PSD) +- Libre Office i Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) Ta aplikacja jest oprogramowaniem open source. Nie jesteśmy powiązani z OpenOffice, LibreOffice ani podobnymi projektami. Made in Austria. ${ads} Bardzo cenimy sobie każdą opinię przesłaną e-mailem. diff --git a/fastlane/metadata/pt-BR/description.txt b/fastlane/metadata/pt-BR/description.txt index a2ad7e1..f34c4b9 100644 --- a/fastlane/metadata/pt-BR/description.txt +++ b/fastlane/metadata/pt-BR/description.txt @@ -24,13 +24,10 @@ Além disso, o leitor de documentos procura abrir da melhor forma possível vár - Imagens: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG etc - Vídeos: MP4, WEBM etc - Áudio: MP3, OGG etc -- Arquivos de texto: CSV, TXT, HTML, RTF +- Arquivos de texto: CSV, TXT, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote -- Libre Office e Open Office ODF (ODT, ODS, ODP, ODG) -- PostScript (EPS) -- AutoCAD (DXF) -- Photoshop (PSD) +- Libre Office e Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) Este app é de código aberto. Não temos vínculo com o OpenOffice, o LibreOffice ou similares. Feito na Áustria. ${ads} Adoramos receber todo tipo de feedback por e-mail. diff --git a/fastlane/metadata/ru/description.txt b/fastlane/metadata/ru/description.txt index 8169823..b5b1f7a 100644 --- a/fastlane/metadata/ru/description.txt +++ b/fastlane/metadata/ru/description.txt @@ -24,13 +24,10 @@ - Изображения: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG и др. - Видео: MP4, WEBM и др. - Аудио: MP3, OGG и др. -- Текстовые файлы: CSV, TXT, HTML, RTF +- Текстовые файлы: CSV, TXT, 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} Будем рады любым отзывам по электронной почте. diff --git a/fastlane/metadata/sv/description.txt b/fastlane/metadata/sv/description.txt index 1a81c2b..da9d2d9 100644 --- a/fastlane/metadata/sv/description.txt +++ b/fastlane/metadata/sv/description.txt @@ -24,13 +24,10 @@ Dessutom stöder dokumentläsaren så många andra filformat som möjligt: - Bilder: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG med flera - Video: MP4, WEBM med flera - Ljud: MP3, OGG med flera -- Textfiler: CSV, TXT, HTML, RTF +- Textfiler: CSV, TXT, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote -- Libre Office och Open Office ODF (ODT, ODS, ODP, ODG) -- PostScript (EPS) -- AutoCAD (DXF) -- Photoshop (PSD) +- Libre Office och Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) Appen är öppen källkod. Vi har ingen koppling till OpenOffice, LibreOffice eller liknande. Tillverkad i Österrike. ${ads} Vi uppskattar all form av återkoppling via e-post. diff --git a/fastlane/metadata/tr/description.txt b/fastlane/metadata/tr/description.txt index 837b5b8..afcd288 100644 --- a/fastlane/metadata/tr/description.txt +++ b/fastlane/metadata/tr/description.txt @@ -24,13 +24,10 @@ Bunlara ek olarak belge okuyucu, diğer birçok dosya biçimini de elinden geldi - Resimler: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG vb. - Videolar: MP4, WEBM vb. - Ses: MP3, OGG vb. -- Metin dosyaları: CSV, TXT, HTML, RTF +- Metin dosyaları: CSV, TXT, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote -- Libre Office ve Open Office ODF (ODT, ODS, ODP, ODG) -- PostScript (EPS) -- AutoCAD (DXF) -- Photoshop (PSD) +- Libre Office ve Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) Bu uygulama açık kaynaklıdır. OpenOffice, LibreOffice veya benzeri programlarla herhangi bir bağlantımız yoktur. Avusturya'da üretilmiştir. ${ads} Her türlü geri bildiriminizi e-posta ile bize iletmenizden memnuniyet duyarız. From 5d5ba813c9d3a59b6fd8a1a5a4aebcc2e312874f Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sat, 29 Aug 2026 12:58:52 +0200 Subject: [PATCH 4/6] Keep fitting the page ourselves, and read a markdown file as prose Two things, both found by driving the app in a simulator. The new viewport mode goes back. It pins initial-scale=1.0 and leaves the fit to a script inside the view, and in a WKWebView that script's zoom never lands: an odt opened at actual size with the body text twice the size and only its first third on screen. Photographed against the shipping build, 11.4% of pixels differed; with the mode off, 1.2%. So the user script stays, and the mode is opendocument-app/OpenDocument.core#761. Markdown has no signature, so odrcore reads a .md as text and only its name can say otherwise. Opening again as the type the name states, under the rule OpenDocument.droid already uses: a document, or a format the core cannot detect from its bytes. Csv is neither and stays the core's own decision. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Y7ALzy5VB32UfQn263Tbnk --- OpenDocumentReader/CoreWrapper.swift | 26 ++++- .../DocumentViewController.swift | 109 +++++++++++++++++- .../EditWorkflowTests.swift | 4 +- .../OpenDocumentReaderTests.swift | 35 ++++++ 4 files changed, 164 insertions(+), 10 deletions(-) diff --git a/OpenDocumentReader/CoreWrapper.swift b/OpenDocumentReader/CoreWrapper.swift index 75de870..57c63c8 100644 --- a/OpenDocumentReader/CoreWrapper.swift +++ b/OpenDocumentReader/CoreWrapper.swift @@ -100,6 +100,28 @@ private func selectViews(_ views: [HtmlView], _ documentType: DocumentType) -> [ /// 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, @@ -120,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 ?? "") @@ -160,8 +182,6 @@ 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 - // a WKWebView fits no top-level document, so the view fits itself - config.viewportMode = .fitWidthByView // stated rather than inherited: a sheet past the limit is cut off silently config.spreadsheetLimit = Self.spreadsheetLimit config.spreadsheetCellLimit = NSNumber(value: Self.spreadsheetCellLimit) diff --git a/OpenDocumentReader/DocumentViewController.swift b/OpenDocumentReader/DocumentViewController.swift index f44f850..58edee9 100644 --- a/OpenDocumentReader/DocumentViewController.swift +++ b/OpenDocumentReader/DocumentViewController.swift @@ -75,6 +75,105 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel } } + /// 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 served = meta.content; + var natural = document.documentElement.scrollWidth; + + // The web view's own width, which the viewport named below does not + // change: the visual viewport is that many CSS pixels at that scale. + function available() { + var seen = window.visualViewport; + + return seen ? Math.round(seen.width * seen.scale) : window.innerWidth; + } + + function fit() { + meta.setAttribute( + 'content', + natural > available() ? 'width=' + natural + ',user-scalable=yes' : served); + } + + // Across a resize the browser keeps the reader's place by holding on + // to whatever was against the top of the screen, and here it gets it + // wrong: the scale changes with the width, and the page comes back + // hundreds of pixels down - a page or more of a long document on an + // iPad, which is where the width really does change. It settles + // there some frames after the resize, so the place the reader was + // actually at is re-asserted until it has finished, and dropped the + // moment they take hold of the page themselves. + var holding = []; + + function hold(place) { + holding.forEach(clearTimeout); + holding = [0, 16, 50, 150, 300, 500].map(function (ms) { + return setTimeout(function () { window.scrollTo(window.scrollX, place); }, ms); + }); + } + + window.addEventListener('touchstart', function () { + holding.forEach(clearTimeout); + holding = []; + }, { passive: true }); + + // On every resize, not just now: this first runs before the web view + // has the width it will keep, and a page held at a width it no + // longer needs is left scrolled off its own top. + fit(); + window.addEventListener('resize', function () { + var was = window.scrollY; + fit(); + hold(was); + }); + })(); + """ + } + + /// 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() @@ -186,6 +285,7 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel if let page = corePageInReserve { corePageInReserve = nil + installFitToWidth(for: page) documentNavigation = webview.load(URLRequest(url: page)) return @@ -789,13 +889,14 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel return } + installFitToWidth(for: url) + documentNavigation = self.webview.load(URLRequest(url: url)) } - /// Two files the system draws better, both falling back to `corePageInReserve` - /// when it turns out it cannot: a container the system knows as a document — - /// an `.epub` is composite content, a `.zip` is not — and iWork, whose text - /// odrcore reads but whose styles and pictures it does not. + /// Two the system draws better, both falling back to `corePageInReserve`: + /// a container it knows as a document (`.epub` is composite content, `.zip` + /// is not), and iWork, whose styles and pictures odrcore does not read. private func systemDrawsItBetter(_ doc: Document) -> Bool { let ext = doc.fileURL.pathExtension.lowercased() diff --git a/OpenDocumentReaderTests/EditWorkflowTests.swift b/OpenDocumentReaderTests/EditWorkflowTests.swift index 4b0d7f4..51edacf 100644 --- a/OpenDocumentReaderTests/EditWorkflowTests.swift +++ b/OpenDocumentReaderTests/EditWorkflowTests.swift @@ -107,9 +107,7 @@ class EditWorkflowTests: XCTestCase { """ (function () { var run = document.querySelector('[contenteditable]'); - // not getBoundingClientRect: the view applies a zoom to fit, - // which webkit leaves out of it but elementFromPoint expects - var box = odr.getViewportRect(run); + var box = run.getBoundingClientRect(); var hit = document.elementFromPoint(box.left + box.width / 2, box.top + box.height / 2); return hit ? hit.tagName : 'none'; diff --git a/OpenDocumentReaderTests/OpenDocumentReaderTests.swift b/OpenDocumentReaderTests/OpenDocumentReaderTests.swift index 86d056e..1000dd5 100644 --- a/OpenDocumentReaderTests/OpenDocumentReaderTests.swift +++ b/OpenDocumentReaderTests/OpenDocumentReaderTests.swift @@ -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 From 1ae89d6fe023decc8d7e260b9a254a0b42b027d4 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sat, 29 Aug 2026 12:59:59 +0200 Subject: [PATCH 5/6] Say markdown in the listing, now that the app reads one The line a131cf3 held back. `CoreWrapper` asks by name where the bytes only say text, so the promise is good; the wording is OpenDocument.droid's. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Y7ALzy5VB32UfQn263Tbnk --- CHANGELOG.md | 2 ++ fastlane/metadata/de-DE/description.txt | 2 +- fastlane/metadata/en-US/description.txt | 2 +- fastlane/metadata/es-ES/description.txt | 2 +- fastlane/metadata/fr-FR/description.txt | 2 +- fastlane/metadata/hi/description.txt | 2 +- fastlane/metadata/it/description.txt | 2 +- fastlane/metadata/pl/description.txt | 2 +- fastlane/metadata/pt-BR/description.txt | 2 +- fastlane/metadata/ru/description.txt | 2 +- fastlane/metadata/sv/description.txt | 2 +- fastlane/metadata/tr/description.txt | 2 +- 12 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8122f33..e9646d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ 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 diff --git a/fastlane/metadata/de-DE/description.txt b/fastlane/metadata/de-DE/description.txt index 1b7415d..3a9b1fb 100644 --- a/fastlane/metadata/de-DE/description.txt +++ b/fastlane/metadata/de-DE/description.txt @@ -24,7 +24,7 @@ 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, RTF +- Textdateien: CSV, TXT, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/en-US/description.txt b/fastlane/metadata/en-US/description.txt index a43c6f1..8919b2d 100644 --- a/fastlane/metadata/en-US/description.txt +++ b/fastlane/metadata/en-US/description.txt @@ -24,7 +24,7 @@ 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, RTF +- Text files: CSV, TXT, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/es-ES/description.txt b/fastlane/metadata/es-ES/description.txt index 6a3ee44..5a12e2d 100644 --- a/fastlane/metadata/es-ES/description.txt +++ b/fastlane/metadata/es-ES/description.txt @@ -24,7 +24,7 @@ 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, RTF +- Archivos de texto: CSV, TXT, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/fr-FR/description.txt b/fastlane/metadata/fr-FR/description.txt index 95ca84d..f845599 100644 --- a/fastlane/metadata/fr-FR/description.txt +++ b/fastlane/metadata/fr-FR/description.txt @@ -24,7 +24,7 @@ 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, RTF +- Fichiers texte : CSV, TXT, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/hi/description.txt b/fastlane/metadata/hi/description.txt index ad026fd..83597de 100644 --- a/fastlane/metadata/hi/description.txt +++ b/fastlane/metadata/hi/description.txt @@ -24,7 +24,7 @@ LibreOffice या OpenOffice में बनी ODF फाइलें (ODT, O - इमेज: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG आदि - वीडियो: MP4, WEBM आदि - ऑडियो: MP3, OGG आदि -- टेक्स्ट फाइलें: CSV, TXT, RTF +- टेक्स्ट फाइलें: CSV, TXT, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/it/description.txt b/fastlane/metadata/it/description.txt index b6170f6..18e17a7 100644 --- a/fastlane/metadata/it/description.txt +++ b/fastlane/metadata/it/description.txt @@ -24,7 +24,7 @@ Oltre a questo, il lettore di documenti punta a supportare al meglio anche molti - Immagini: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG, ecc. - Video: MP4, WEBM, ecc. - Audio: MP3, OGG, ecc. -- File di testo: CSV, TXT, RTF +- File di testo: CSV, TXT, MD, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote - Libre Office e Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/pl/description.txt b/fastlane/metadata/pl/description.txt index 082ec70..28e82cf 100644 --- a/fastlane/metadata/pl/description.txt +++ b/fastlane/metadata/pl/description.txt @@ -24,7 +24,7 @@ Poza tym przeglądarka dokumentów stara się jak najlepiej obsługiwać równie - Obrazy: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG i inne - Filmy: MP4, WEBM i inne - Dźwięk: MP3, OGG i inne -- Pliki tekstowe: CSV, TXT, RTF +- Pliki tekstowe: CSV, TXT, MD, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote - Libre Office i Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/pt-BR/description.txt b/fastlane/metadata/pt-BR/description.txt index f34c4b9..1345441 100644 --- a/fastlane/metadata/pt-BR/description.txt +++ b/fastlane/metadata/pt-BR/description.txt @@ -24,7 +24,7 @@ Além disso, o leitor de documentos procura abrir da melhor forma possível vár - Imagens: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG etc - Vídeos: MP4, WEBM etc - Áudio: MP3, OGG etc -- Arquivos de texto: CSV, TXT, RTF +- Arquivos de texto: CSV, TXT, MD, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote - Libre Office e Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/ru/description.txt b/fastlane/metadata/ru/description.txt index b5b1f7a..df4fe12 100644 --- a/fastlane/metadata/ru/description.txt +++ b/fastlane/metadata/ru/description.txt @@ -24,7 +24,7 @@ - Изображения: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG и др. - Видео: MP4, WEBM и др. - Аудио: MP3, OGG и др. -- Текстовые файлы: CSV, TXT, RTF +- Текстовые файлы: CSV, TXT, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/sv/description.txt b/fastlane/metadata/sv/description.txt index da9d2d9..b7650ed 100644 --- a/fastlane/metadata/sv/description.txt +++ b/fastlane/metadata/sv/description.txt @@ -24,7 +24,7 @@ Dessutom stöder dokumentläsaren så många andra filformat som möjligt: - Bilder: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG med flera - Video: MP4, WEBM med flera - Ljud: MP3, OGG med flera -- Textfiler: CSV, TXT, RTF +- Textfiler: CSV, TXT, MD, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote - Libre Office och Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/tr/description.txt b/fastlane/metadata/tr/description.txt index afcd288..1de0d79 100644 --- a/fastlane/metadata/tr/description.txt +++ b/fastlane/metadata/tr/description.txt @@ -24,7 +24,7 @@ Bunlara ek olarak belge okuyucu, diğer birçok dosya biçimini de elinden geldi - Resimler: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG vb. - Videolar: MP4, WEBM vb. - Ses: MP3, OGG vb. -- Metin dosyaları: CSV, TXT, RTF +- Metin dosyaları: CSV, TXT, MD, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote - Libre Office ve Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) From f6d94b7c203e24698582658460d32dc5add5b39c Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sat, 29 Aug 2026 13:39:12 +0200 Subject: [PATCH 6/6] Let the system draw a web page again odrcore has no html type, so an .html decodes as text and the reader showed the page's source. It used to be drawn: html, htm and xhtml were in the extension list #183 replaced with "ask the core", and nothing took over. `systemDrawsItBetter` already means what is needed here, so html joins iWork in it, by type rather than by extension. `public.xhtml` conforms to xml and not to html, so both types are named - xml alone would catch flat ODF, which odrcore does render. The listing gets HTML back with it. EPS, DXF and PSD stay out: a WKWebView answers canShowMIMEType false for all three, so there was never a native rendering to promise. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Y7ALzy5VB32UfQn263Tbnk --- .../DocumentViewController.swift | 14 +++++++--- .../ArchiveDocumentTests.swift | 28 +++++++++++++++++++ fastlane/metadata/de-DE/description.txt | 2 +- fastlane/metadata/en-US/description.txt | 2 +- fastlane/metadata/es-ES/description.txt | 2 +- fastlane/metadata/fr-FR/description.txt | 2 +- fastlane/metadata/hi/description.txt | 2 +- fastlane/metadata/it/description.txt | 2 +- fastlane/metadata/pl/description.txt | 2 +- fastlane/metadata/pt-BR/description.txt | 2 +- fastlane/metadata/ru/description.txt | 2 +- fastlane/metadata/sv/description.txt | 2 +- fastlane/metadata/tr/description.txt | 2 +- 13 files changed, 49 insertions(+), 15 deletions(-) diff --git a/OpenDocumentReader/DocumentViewController.swift b/OpenDocumentReader/DocumentViewController.swift index 58edee9..351e3aa 100644 --- a/OpenDocumentReader/DocumentViewController.swift +++ b/OpenDocumentReader/DocumentViewController.swift @@ -894,18 +894,24 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel documentNavigation = self.webview.load(URLRequest(url: url)) } - /// Two the system draws better, both falling back to `corePageInReserve`: - /// a container it knows as a document (`.epub` is composite content, `.zip` - /// is not), and iWork, whose styles and pictures odrcore does not read. + /// 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 Self.iWorkExtensions.contains(ext) + return Self.webPageTypes.contains(where: type.conforms(to:)) + || Self.iWorkExtensions.contains(ext) || (doc.isArchive && type.conforms(to: .compositeContent)) } + /// 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 = ["pages", "numbers", "key"] /// odrcore's page, held back while the system has the first go. diff --git a/OpenDocumentReaderTests/ArchiveDocumentTests.swift b/OpenDocumentReaderTests/ArchiveDocumentTests.swift index ed89b80..9673e9f 100644 --- a/OpenDocumentReaderTests/ArchiveDocumentTests.swift +++ b/OpenDocumentReaderTests/ArchiveDocumentTests.swift @@ -111,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 "

Heading

".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 { diff --git a/fastlane/metadata/de-DE/description.txt b/fastlane/metadata/de-DE/description.txt index 3a9b1fb..54110bb 100644 --- a/fastlane/metadata/de-DE/description.txt +++ b/fastlane/metadata/de-DE/description.txt @@ -24,7 +24,7 @@ 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, MD, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/en-US/description.txt b/fastlane/metadata/en-US/description.txt index 8919b2d..7dee3c7 100644 --- a/fastlane/metadata/en-US/description.txt +++ b/fastlane/metadata/en-US/description.txt @@ -24,7 +24,7 @@ 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, MD, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/es-ES/description.txt b/fastlane/metadata/es-ES/description.txt index 5a12e2d..c0c2a94 100644 --- a/fastlane/metadata/es-ES/description.txt +++ b/fastlane/metadata/es-ES/description.txt @@ -24,7 +24,7 @@ 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, MD, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/fr-FR/description.txt b/fastlane/metadata/fr-FR/description.txt index f845599..6d9ca40 100644 --- a/fastlane/metadata/fr-FR/description.txt +++ b/fastlane/metadata/fr-FR/description.txt @@ -24,7 +24,7 @@ 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, MD, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/hi/description.txt b/fastlane/metadata/hi/description.txt index 83597de..204a21e 100644 --- a/fastlane/metadata/hi/description.txt +++ b/fastlane/metadata/hi/description.txt @@ -24,7 +24,7 @@ LibreOffice या OpenOffice में बनी ODF फाइलें (ODT, O - इमेज: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG आदि - वीडियो: MP4, WEBM आदि - ऑडियो: MP3, OGG आदि -- टेक्स्ट फाइलें: CSV, TXT, MD, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/it/description.txt b/fastlane/metadata/it/description.txt index 18e17a7..b175e7f 100644 --- a/fastlane/metadata/it/description.txt +++ b/fastlane/metadata/it/description.txt @@ -24,7 +24,7 @@ Oltre a questo, il lettore di documenti punta a supportare al meglio anche molti - Immagini: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG, ecc. - Video: MP4, WEBM, ecc. - Audio: MP3, OGG, ecc. -- File di testo: CSV, TXT, MD, RTF +- File di testo: CSV, TXT, HTML, MD, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote - Libre Office e Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/pl/description.txt b/fastlane/metadata/pl/description.txt index 28e82cf..341d539 100644 --- a/fastlane/metadata/pl/description.txt +++ b/fastlane/metadata/pl/description.txt @@ -24,7 +24,7 @@ Poza tym przeglądarka dokumentów stara się jak najlepiej obsługiwać równie - Obrazy: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG i inne - Filmy: MP4, WEBM i inne - Dźwięk: MP3, OGG i inne -- Pliki tekstowe: CSV, TXT, MD, RTF +- Pliki tekstowe: CSV, TXT, HTML, MD, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote - Libre Office i Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/pt-BR/description.txt b/fastlane/metadata/pt-BR/description.txt index 1345441..e888272 100644 --- a/fastlane/metadata/pt-BR/description.txt +++ b/fastlane/metadata/pt-BR/description.txt @@ -24,7 +24,7 @@ Além disso, o leitor de documentos procura abrir da melhor forma possível vár - Imagens: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG etc - Vídeos: MP4, WEBM etc - Áudio: MP3, OGG etc -- Arquivos de texto: CSV, TXT, MD, RTF +- Arquivos 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 e Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/ru/description.txt b/fastlane/metadata/ru/description.txt index df4fe12..093582f 100644 --- a/fastlane/metadata/ru/description.txt +++ b/fastlane/metadata/ru/description.txt @@ -24,7 +24,7 @@ - Изображения: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG и др. - Видео: MP4, WEBM и др. - Аудио: MP3, OGG и др. -- Текстовые файлы: CSV, TXT, MD, 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, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/sv/description.txt b/fastlane/metadata/sv/description.txt index b7650ed..e1abd78 100644 --- a/fastlane/metadata/sv/description.txt +++ b/fastlane/metadata/sv/description.txt @@ -24,7 +24,7 @@ Dessutom stöder dokumentläsaren så många andra filformat som möjligt: - Bilder: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG med flera - Video: MP4, WEBM med flera - Ljud: MP3, OGG med flera -- Textfiler: CSV, TXT, MD, RTF +- Textfiler: CSV, TXT, HTML, MD, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote - Libre Office och Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG) diff --git a/fastlane/metadata/tr/description.txt b/fastlane/metadata/tr/description.txt index 1de0d79..e21eadb 100644 --- a/fastlane/metadata/tr/description.txt +++ b/fastlane/metadata/tr/description.txt @@ -24,7 +24,7 @@ Bunlara ek olarak belge okuyucu, diğer birçok dosya biçimini de elinden geldi - Resimler: JPG, JPEG, GIF, PNG, WEBP, TIFF, BMP, SVG vb. - Videolar: MP4, WEBM vb. - Ses: MP3, OGG vb. -- Metin dosyaları: CSV, TXT, MD, RTF +- Metin dosyaları: CSV, TXT, HTML, MD, RTF - Microsoft Office (OOXML): Word (DOC, DOCX), Excel (XLS, XLSX), PowerPoint (PPT, PPTX) - Apple iWork: Pages, Numbers, Keynote - Libre Office ve Open Office ODF (ODT, ODS, ODP, ODG, FODT, FODS, FODP, FODG)