Skip to content

Commit

Permalink
PDF reader & related refactoring (#834)
Browse files Browse the repository at this point in the history
* PDF reader & related refactoring

* removed unnecessary script

* Uppercase PDF

* Code style cleanup

* Restored annotation view image content
  • Loading branch information
michalrentka authored Jan 29, 2024
1 parent 65c9d47 commit 3e9a20e
Show file tree
Hide file tree
Showing 60 changed files with 2,539 additions and 1,494 deletions.
10 changes: 5 additions & 5 deletions ZShare/Controllers/TranslationWebViewHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,14 @@ final class TranslationWebViewHandler {
}
.flatMap { translators -> Single<Any> in
DDLogInfo("WebViewHandler: encode translators")
let encodedTranslators = WKWebView.encodeAsJSONForJavascript(translators)
let encodedTranslators = WebViewEncoder.encodeAsJSONForJavascript(translators)
return self.webViewHandler.call(javascript: "initTranslators(\(encodedTranslators));")
}
.flatMap({ _ -> Single<Any> in
DDLogInfo("WebViewHandler: call translate js")
let encodedHtml = WKWebView.encodeForJavascript(html.data(using: .utf8))
let encodedHtml = WebViewEncoder.encodeForJavascript(html.data(using: .utf8))
let jsonFramesData = try? JSONSerialization.data(withJSONObject: frames, options: .fragmentsAllowed)
let encodedFrames = jsonFramesData.flatMap({ WKWebView.encodeForJavascript($0) }) ?? "''"
let encodedFrames = jsonFramesData.flatMap({ WebViewEncoder.encodeForJavascript($0) }) ?? "''"
return self.webViewHandler.call(javascript: "translate('\(url.absoluteString)', \(encodedHtml), \(encodedFrames));")
})
.subscribe(onFailure: { [weak self] error in
Expand All @@ -169,8 +169,8 @@ final class TranslationWebViewHandler {
return Disposables.create()
}

let encodedSchema = WKWebView.encodeForJavascript(schemaData)
let encodedFormats = WKWebView.encodeForJavascript(dateFormatData)
let encodedSchema = WebViewEncoder.encodeForJavascript(schemaData)
let encodedFormats = WebViewEncoder.encodeForJavascript(dateFormatData)

DDLogInfo("WebViewHandler: loaded bundled files")

Expand Down
158 changes: 93 additions & 65 deletions Zotero.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Zotero/Assets/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@
"errors.pdf.cant_update_annotation" = "Can't update annotation.";
"errors.pdf.cant_add_annotations" = "Can't add annotations.";
"errors.pdf.cant_delete_annotations" = "Can't delete annotations.";
"errors.pdf.incompatible_document" = "This document is not supported.";
"errors.pdf.page_index_not_int" = "Incorrect format of page stored for this document.";

"accessibility.untitled" = "Untitled";
Expand Down
16 changes: 8 additions & 8 deletions Zotero/Controllers/AnnotationConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ struct AnnotationConverter {
username: String,
displayName: String,
boundingBoxConverter: AnnotationBoundingBoxConverter?
) -> DocumentAnnotation? {
) -> PDFDocumentAnnotation? {
guard let document = annotation.document, AnnotationsConfig.supported.contains(annotation.type) else { return nil }

let key = annotation.key ?? annotation.uuid
Expand Down Expand Up @@ -113,7 +113,7 @@ struct AnnotationConverter {
return nil
}

return DocumentAnnotation(
return PDFDocumentAnnotation(
key: key,
type: type,
page: page,
Expand Down Expand Up @@ -191,7 +191,7 @@ struct AnnotationConverter {
) -> [PSPDFKit.Annotation] {
return items.map({ item in
return self.annotation(
from: DatabaseAnnotation(item: item),
from: PDFDatabaseAnnotation(item: item),
type: type,
interfaceStyle: interfaceStyle,
currentUserId: currentUserId,
Expand All @@ -204,7 +204,7 @@ struct AnnotationConverter {
}

static func annotation(
from zoteroAnnotation: DatabaseAnnotation,
from zoteroAnnotation: PDFDatabaseAnnotation,
type: Kind,
interfaceStyle: UIUserInterfaceStyle,
currentUserId: Int,
Expand Down Expand Up @@ -260,7 +260,7 @@ struct AnnotationConverter {

/// Creates corresponding `SquareAnnotation`.
/// - parameter annotation: Zotero annotation.
private static func areaAnnotation(from annotation: Annotation, type: Kind, color: UIColor, boundingBoxConverter: AnnotationBoundingBoxConverter) -> PSPDFKit.SquareAnnotation {
private static func areaAnnotation(from annotation: PDFAnnotation, type: Kind, color: UIColor, boundingBoxConverter: AnnotationBoundingBoxConverter) -> PSPDFKit.SquareAnnotation {
let square: PSPDFKit.SquareAnnotation
switch type {
case .export:
Expand All @@ -280,7 +280,7 @@ struct AnnotationConverter {
/// Creates corresponding `HighlightAnnotation`.
/// - parameter annotation: Zotero annotation.
private static func highlightAnnotation(
from annotation: Annotation,
from annotation: PDFAnnotation,
type: Kind,
color: UIColor,
alpha: CGFloat,
Expand All @@ -305,7 +305,7 @@ struct AnnotationConverter {

/// Creates corresponding `NoteAnnotation`.
/// - parameter annotation: Zotero annotation.
private static func noteAnnotation(from annotation: Annotation, type: Kind, color: UIColor, boundingBoxConverter: AnnotationBoundingBoxConverter) -> PSPDFKit.NoteAnnotation {
private static func noteAnnotation(from annotation: PDFAnnotation, type: Kind, color: UIColor, boundingBoxConverter: AnnotationBoundingBoxConverter) -> PSPDFKit.NoteAnnotation {
let note: PSPDFKit.NoteAnnotation
switch type {
case .export:
Expand All @@ -323,7 +323,7 @@ struct AnnotationConverter {
return note
}

private static func inkAnnotation(from annotation: Annotation, type: Kind, color: UIColor, boundingBoxConverter: AnnotationBoundingBoxConverter) -> PSPDFKit.InkAnnotation {
private static func inkAnnotation(from annotation: PDFAnnotation, type: Kind, color: UIColor, boundingBoxConverter: AnnotationBoundingBoxConverter) -> PSPDFKit.InkAnnotation {
let lines = annotation.paths(boundingBoxConverter: boundingBoxConverter).map({ group in
return group.map({ DrawingPoint(cgPoint: $0) })
})
Expand Down
10 changes: 5 additions & 5 deletions Zotero/Controllers/Citation/CitationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class CitationController: NSObject {
}
itemsData.append(data)
}
return WKWebView.encodeAsJSONForJavascript(itemsData)
return WebViewEncoder.encodeAsJSONForJavascript(itemsData)
}

/// Bibliography happens once for selected item(s). Appropriate style and locale xmls are loaded, webView is initialized and loaded with index.html. When everything is loaded,
Expand Down Expand Up @@ -266,7 +266,7 @@ class CitationController: NSObject {
let localeData = try Data(contentsOf: localeUrl)
let styleData = try self.fileStorage.read(Files.style(filename: styleFilename))

subscriber(.success((WKWebView.encodeForJavascript(styleData), WKWebView.encodeForJavascript(localeData))))
subscriber(.success((WebViewEncoder.encodeForJavascript(styleData), WebViewEncoder.encodeForJavascript(localeData))))
} catch let error {
DDLogError("CitationController: can't read locale or style - \(error)")
subscriber(.failure(Error.styleOrLocaleMissing))
Expand Down Expand Up @@ -302,7 +302,7 @@ class CitationController: NSObject {
return Disposables.create()
}

subscriber(.success(WKWebView.encodeForJavascript(schemaData)))
subscriber(.success(WebViewEncoder.encodeForJavascript(schemaData)))

return Disposables.create()
}
Expand All @@ -323,7 +323,7 @@ class CitationController: NSObject {

items.first?.realm?.invalidate()

subscriber(.success(WKWebView.encodeAsJSONForJavascript(data)))
subscriber(.success(WebViewEncoder.encodeAsJSONForJavascript(data)))
} catch let error {
DDLogError("CitationController: can't read items - \(error)")
subscriber(.failure(error))
Expand Down Expand Up @@ -498,7 +498,7 @@ extension CitationController: WKScriptMessageHandler {

case .csl:
if let csl = jsResult as? [[String: Any]] {
result = .success(WKWebView.encodeAsJSONForJavascript(csl))
result = .success(WebViewEncoder.encodeAsJSONForJavascript(csl))
} else {
DDLogError("CitationController: CSL got unknown response - \(jsResult)")
result = .failure(Error.missingResponse)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// CreateAnnotationsDbRequest.swift
// CreatePDFAnnotationsDbRequest.swift
// Zotero
//
// Created by Michal Rentka on 31.08.2022.
Expand All @@ -10,10 +10,10 @@ import UIKit

import RealmSwift

struct CreateAnnotationsDbRequest: DbRequest {
struct CreatePDFAnnotationsDbRequest: DbRequest {
let attachmentKey: String
let libraryId: LibraryIdentifier
let annotations: [DocumentAnnotation]
let annotations: [PDFDocumentAnnotation]
let userId: Int

unowned let schemaController: SchemaController
Expand All @@ -29,7 +29,7 @@ struct CreateAnnotationsDbRequest: DbRequest {
}
}

private func create(annotation: DocumentAnnotation, parent: RItem, in database: Realm) {
private func create(annotation: PDFDocumentAnnotation, parent: RItem, in database: Realm) {
let item: RItem

if let _item = database.objects(RItem.self).filter(.key(annotation.key, in: self.libraryId)).first {
Expand Down Expand Up @@ -70,8 +70,8 @@ struct CreateAnnotationsDbRequest: DbRequest {
item.changes.append(RObjectChange.create(changes: changes))
}

private func addFields(for annotation: Annotation, to item: RItem, database: Realm) {
for field in FieldKeys.Item.Annotation.allFields(for: annotation.type) {
private func addFields(for annotation: PDFDocumentAnnotation, to item: RItem, database: Realm) {
for field in FieldKeys.Item.Annotation.allPDFFields(for: annotation.type) {
let rField = RItemField()
rField.key = field.key
rField.baseKey = field.baseKey
Expand Down Expand Up @@ -110,7 +110,7 @@ struct CreateAnnotationsDbRequest: DbRequest {
private func add(rects: [CGRect], to item: RItem, changes: inout RItemChanges, database: Realm) {
guard !rects.isEmpty else { return }

let page = UInt(DatabaseAnnotation(item: item).page)
let page = UInt(PDFDatabaseAnnotation(item: item).page)

for rect in rects {
let dbRect = self.boundingBoxConverter.convertToDb(rect: rect, page: page) ?? rect
Expand All @@ -128,7 +128,7 @@ struct CreateAnnotationsDbRequest: DbRequest {
private func add(paths: [[CGPoint]], to item: RItem, changes: inout RItemChanges, database: Realm) {
guard !paths.isEmpty else { return }

let page = UInt(DatabaseAnnotation(item: item).page)
let page = UInt(PDFDatabaseAnnotation(item: item).page)

for (idx, path) in paths.enumerated() {
let rPath = RPath()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct EditAnnotationPathsDbRequest: DbRequest {

func process(in database: Realm) throws {
guard let item = database.objects(RItem.self).filter(.key(self.key, in: self.libraryId)).first else { return }
let page = UInt(DatabaseAnnotation(item: item).page)
let page = UInt(PDFDatabaseAnnotation(item: item).page)
let dbPaths = self.paths.map { path in
return path.map({ self.boundingBoxConverter.convertToDb(point: $0, page: page) ?? $0 })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct EditAnnotationRectsDbRequest: DbRequest {

func process(in database: Realm) throws {
guard let item = database.objects(RItem.self).filter(.key(self.key, in: self.libraryId)).first else { return }
let page = UInt(DatabaseAnnotation(item: item).page)
let page = UInt(PDFDatabaseAnnotation(item: item).page)
let dbRects = self.rects.map({ self.boundingBoxConverter.convertToDb(rect: $0, page: page) ?? $0 })
guard self.rects(dbRects, differFrom: item.rects) else { return }
self.sync(rects: dbRects, in: item, database: database)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct EditItemFieldsDbRequest: DbRequest {
if didChange {
item.changes.append(RObjectChange.create(changes: RItemChanges.fields))
item.changeType = .user
item.dateModified = Date()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ struct EditTagsForItemDbRequest: DbRequest {
item.rawType = item.rawType
item.changeType = .user
item.changes.append(RObjectChange.create(changes: RItemChanges.tags))
item.dateModified = Date()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@ struct SplitAnnotationsDbRequest: DbRequest {
}
}

/// Splits annotation if it exceedes position limit. If it is within limit, it returs original annotation.
/// - parameter annotation: Annotation to split
/// - parameter activeColor: Currently active color
/// - parameter viewModel: View model
/// - returns: Array with original annotation if limit was not exceeded. Otherwise array of new split annotations.
/// Splits database annotation if it exceedes position limit.
/// - parameter item: Database annotation to split
/// - parameter database: Database
private func split(item: RItem, database: Realm) {
guard let annotationType = item.fields.filter(.key(FieldKeys.Item.Annotation.type)).first.flatMap({ AnnotationType(rawValue: $0.value) }) else { return }

Expand Down
6 changes: 6 additions & 0 deletions Zotero/Controllers/Formatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ extension Formatter {
return formatter
}()

static let iso8601WithFractionalSeconds: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter
}()

static let dateAndTime: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale.autoupdatingCurrent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ final class LookupWebViewHandler {

private func _lookUp(identifier: String) {
DDLogInfo("LookupWebViewHandler: call translate js")
let encodedIdentifiers = WKWebView.encodeForJavascript(identifier.data(using: .utf8))
let encodedIdentifiers = WebViewEncoder.encodeForJavascript(identifier.data(using: .utf8))
return self.webViewHandler.call(javascript: "lookup(\(encodedIdentifiers));")
.subscribe(on: MainScheduler.instance)
.observe(on: MainScheduler.instance)
Expand Down Expand Up @@ -151,7 +151,7 @@ final class LookupWebViewHandler {
}
.flatMap { translators -> Single<Any> in
DDLogInfo("LookupWebViewHandler: encode translators")
let encodedTranslators = WKWebView.encodeAsJSONForJavascript(translators)
let encodedTranslators = WebViewEncoder.encodeAsJSONForJavascript(translators)
return self.webViewHandler.call(javascript: "initTranslators(\(encodedTranslators));")
}
}
Expand Down Expand Up @@ -179,8 +179,8 @@ final class LookupWebViewHandler {
return Disposables.create()
}

let encodedSchema = WKWebView.encodeForJavascript(schemaData)
let encodedFormats = WKWebView.encodeForJavascript(dateFormatData)
let encodedSchema = WebViewEncoder.encodeForJavascript(schemaData)
let encodedFormats = WebViewEncoder.encodeForJavascript(dateFormatData)

DDLogInfo("WebViewHandler: loaded bundled files")

Expand Down
5 changes: 3 additions & 2 deletions Zotero/Controllers/Web View Handling/WebViewHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class WebViewHandler: NSObject {

// MARK: - Lifecycle

init(webView: WKWebView, javascriptHandlers: [String]?) {
init(webView: WKWebView, javascriptHandlers: [String]?, userAgent: String? = nil) {
let storage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: AppGroup.identifier)
storage.cookieAcceptPolicy = .always

Expand All @@ -45,6 +45,7 @@ final class WebViewHandler: NSObject {
super.init()

webView.navigationDelegate = self
webView.customUserAgent = "Zotero_iOS/\(DeviceInfoProvider.versionString ?? "")-\(DeviceInfoProvider.buildString ?? "")"

if let handlers = javascriptHandlers {
handlers.forEach { handler in
Expand Down Expand Up @@ -92,7 +93,7 @@ final class WebViewHandler: NSObject {
}

func sendMessaging(response payload: [String: Any], for messageId: Int) {
let script = "Zotero.Messaging.receiveResponse('\(messageId)', \(WKWebView.encodeAsJSONForJavascript(payload)));"
let script = "Zotero.Messaging.receiveResponse('\(messageId)', \(WebViewEncoder.encodeAsJSONForJavascript(payload)));"
inMainThread { [weak self] in
self?.webView?.evaluateJavaScript(script, completionHandler: nil)
}
Expand Down
26 changes: 26 additions & 0 deletions Zotero/Controllers/WebViewEncoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// WebViewEncoder.swift
// Zotero
//
// Created by Michal Rentka on 15.11.2023.
// Copyright © 2023 Corporation for Digital Scholarship. All rights reserved.
//

import Foundation

struct WebViewEncoder {
static func optionalToJs(_ value: String?) -> String {
return value.flatMap({ "'" + $0 + "'" }) ?? "null"
}

/// Encodes data which need to be sent to `webView`. All data that is passed to JS is Base64 encoded so that it can be sent as a simple `String`.
static func encodeForJavascript(_ data: Data?) -> String {
return data.flatMap({ "'" + $0.base64EncodedString(options: .endLineWithLineFeed) + "'" }) ?? "null"
}

/// Encodes as JSON payload so that it can be sent to `webView`.
static func encodeAsJSONForJavascript(_ payload: Any) -> String {
let data = try? JSONSerialization.data(withJSONObject: payload, options: .prettyPrinted)
return self.encodeForJavascript(data)
}
}
2 changes: 2 additions & 0 deletions Zotero/Extensions/Localizable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,8 @@ internal enum L10n {
internal static let cantDeleteAnnotations = L10n.tr("Localizable", "errors.pdf.cant_delete_annotations", fallback: "Can't delete annotations.")
/// Can't update annotation.
internal static let cantUpdateAnnotation = L10n.tr("Localizable", "errors.pdf.cant_update_annotation", fallback: "Can't update annotation.")
/// This document is not supported.
internal static let incompatibleDocument = L10n.tr("Localizable", "errors.pdf.incompatible_document", fallback: "This document is not supported.")
/// The combined annotation would be too large.
internal static let mergeTooBig = L10n.tr("Localizable", "errors.pdf.merge_too_big", fallback: "The combined annotation would be too large.")
/// Unable to merge annotations
Expand Down
15 changes: 0 additions & 15 deletions Zotero/Extensions/WKWebView+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,4 @@ extension WKWebView {
return Disposables.create()
}
}

static func optionalToJs(_ value: String?) -> String {
return value.flatMap({ "'" + $0 + "'" }) ?? "null"
}

/// Encodes data which need to be sent to `webView`. All data that is passed to JS is Base64 encoded so that it can be sent as a simple `String`.
static func encodeForJavascript(_ data: Data?) -> String {
return data.flatMap({ "'" + $0.base64EncodedString(options: .endLineWithLineFeed) + "'" }) ?? "null"
}

/// Encodes as JSON payload so that it can be sent to `webView`.
static func encodeAsJSONForJavascript(_ payload: Any) -> String {
let data = try? JSONSerialization.data(withJSONObject: payload, options: .prettyPrinted)
return self.encodeForJavascript(data)
}
}
Loading

0 comments on commit 3e9a20e

Please sign in to comment.