diff --git a/Zotero/Scenes/Detail/HTML:EPUB/HtmlEpubCoordinator.swift b/Zotero/Scenes/Detail/HTML:EPUB/HtmlEpubCoordinator.swift
index 2797d3d4e..85fd73778 100644
--- a/Zotero/Scenes/Detail/HTML:EPUB/HtmlEpubCoordinator.swift
+++ b/Zotero/Scenes/Detail/HTML:EPUB/HtmlEpubCoordinator.swift
@@ -22,6 +22,7 @@ protocol HtmlEpubSidebarCoordinatorDelegate: AnyObject {
for annotation: HtmlEpubAnnotation,
userId: Int,
library: Library,
+ highlightFont: UIFont,
sender: UIButton,
userInterfaceStyle: UIUserInterfaceStyle,
saveAction: @escaping AnnotationEditSaveAction,
@@ -188,6 +189,7 @@ extension HtmlEpubCoordinator: HtmlEpubSidebarCoordinatorDelegate {
for annotation: HtmlEpubAnnotation,
userId: Int,
library: Library,
+ highlightFont: UIFont,
sender: UIButton,
userInterfaceStyle: UIUserInterfaceStyle,
saveAction: @escaping AnnotationEditSaveAction,
@@ -196,6 +198,8 @@ extension HtmlEpubCoordinator: HtmlEpubSidebarCoordinatorDelegate {
let navigationController = NavigationViewController()
navigationController.overrideUserInterfaceStyle = userInterfaceStyle
+ let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? HtmlEpubAnnotationsDelegate)?
+ .parseAndCacheIfNeededAttributedText(for: annotation, with: highlightFont) ?? .init(string: "")
let coordinator = AnnotationEditCoordinator(
data: AnnotationEditState.Data(
type: annotation.type,
@@ -203,7 +207,8 @@ extension HtmlEpubCoordinator: HtmlEpubSidebarCoordinatorDelegate {
color: annotation.color,
lineWidth: 0,
pageLabel: annotation.pageLabel,
- highlightText: annotation.text ?? "",
+ highlightText: highlightText,
+ highlightFont: highlightFont,
fontSize: 12
),
saveAction: saveAction,
@@ -241,7 +246,11 @@ extension HtmlEpubCoordinator: HtmlEpubSidebarCoordinatorDelegate {
let navigationController = NavigationViewController()
navigationController.overrideUserInterfaceStyle = userInterfaceStyle
let author = viewModel.state.library.identifier == .custom(.myLibrary) ? "" : annotation.author
- let comment = viewModel.state.comments[annotation.key] ?? NSAttributedString()
+ let comment: NSAttributedString = (self.navigationController?.viewControllers.first as? HtmlEpubAnnotationsDelegate)?
+ .parseAndCacheIfNeededAttributedComment(for: annotation) ?? .init(string: "")
+ let highlightFont = viewModel.state.textFont
+ let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? HtmlEpubAnnotationsDelegate)?
+ .parseAndCacheIfNeededAttributedText(for: annotation, with: highlightFont) ?? .init(string: "")
let editability = annotation.editability(currentUserId: viewModel.state.userId, library: viewModel.state.library)
let data = AnnotationPopoverState.Data(
libraryId: viewModel.state.library.identifier,
@@ -252,7 +261,8 @@ extension HtmlEpubCoordinator: HtmlEpubSidebarCoordinatorDelegate {
color: annotation.color,
lineWidth: 0,
pageLabel: annotation.pageLabel,
- highlightText: annotation.text ?? "",
+ highlightText: highlightText,
+ highlightFont: highlightFont,
tags: annotation.tags,
showsDeleteButton: editability != .notEditable
)
diff --git a/Zotero/Scenes/Detail/HTML:EPUB/Models/HtmlEpubReaderAction.swift b/Zotero/Scenes/Detail/HTML:EPUB/Models/HtmlEpubReaderAction.swift
index 257133ae9..f2f08e7e1 100644
--- a/Zotero/Scenes/Detail/HTML:EPUB/Models/HtmlEpubReaderAction.swift
+++ b/Zotero/Scenes/Detail/HTML:EPUB/Models/HtmlEpubReaderAction.swift
@@ -6,7 +6,7 @@
// Copyright © 2023 Corporation for Digital Scholarship. All rights reserved.
//
-import Foundation
+import UIKit
enum HtmlEpubReaderAction {
case changeFilter(AnnotationsFilter?)
@@ -18,6 +18,7 @@ enum HtmlEpubReaderAction {
case initialiseReader
case loadDocument
case parseAndCacheComment(key: String, comment: String)
+ case parseAndCacheText(key: String, text: String, font: UIFont)
case removeAnnotation(String)
case removeSelectedAnnotations
case saveAnnotations([String: Any])
@@ -36,5 +37,5 @@ enum HtmlEpubReaderAction {
case setViewState([String: Any])
case showAnnotationPopover(key: String, rect: CGRect)
case toggleTool(AnnotationTool)
- case updateAnnotationProperties(key: String, color: String, lineWidth: CGFloat, pageLabel: String, updateSubsequentLabels: Bool, highlightText: String)
+ case updateAnnotationProperties(key: String, color: String, lineWidth: CGFloat, pageLabel: String, updateSubsequentLabels: Bool, highlightText: NSAttributedString)
}
diff --git a/Zotero/Scenes/Detail/HTML:EPUB/Models/HtmlEpubReaderState.swift b/Zotero/Scenes/Detail/HTML:EPUB/Models/HtmlEpubReaderState.swift
index 4e9e6b964..4bad724ef 100644
--- a/Zotero/Scenes/Detail/HTML:EPUB/Models/HtmlEpubReaderState.swift
+++ b/Zotero/Scenes/Detail/HTML:EPUB/Models/HtmlEpubReaderState.swift
@@ -65,6 +65,7 @@ struct HtmlEpubReaderState: ViewModelState {
let userId: Int
let username: String
let commentFont: UIFont
+ let textFont: UIFont
var documentData: DocumentData?
var settings: HtmlEpubSettings
@@ -83,6 +84,7 @@ struct HtmlEpubReaderState: ViewModelState {
var annotationPopoverRect: CGRect?
var documentSearchTerm: String?
var comments: [String: NSAttributedString]
+ var texts: [String: (String, [UIFont: NSAttributedString])]
var changes: Changes
var error: Error?
/// Updates that need to be performed on html/epub document
@@ -110,9 +112,11 @@ struct HtmlEpubReaderState: ViewModelState {
self.userId = userId
self.username = username
commentFont = PDFReaderLayout.annotationLayout.font
+ textFont = PDFReaderLayout.annotationLayout.font
sortedKeys = []
annotations = [:]
comments = [:]
+ texts = [:]
sidebarEditingEnabled = false
selectedAnnotationCommentActive = false
toolColors = [
diff --git a/Zotero/Scenes/Detail/HTML:EPUB/ViewModels/HtmlEpubReaderActionHandler.swift b/Zotero/Scenes/Detail/HTML:EPUB/ViewModels/HtmlEpubReaderActionHandler.swift
index ba22b2d10..6dafa747e 100644
--- a/Zotero/Scenes/Detail/HTML:EPUB/ViewModels/HtmlEpubReaderActionHandler.swift
+++ b/Zotero/Scenes/Detail/HTML:EPUB/ViewModels/HtmlEpubReaderActionHandler.swift
@@ -113,6 +113,9 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
state.comments[key] = self.htmlAttributedStringConverter.convert(text: comment, baseAttributes: [.font: viewModel.state.commentFont])
}
+ case .parseAndCacheText(let key, let text, let font):
+ updateTextCache(key: key, text: text, font: font, viewModel: viewModel)
+
case .updateAnnotationProperties(let key, let color, let lineWidth, let pageLabel, let updateSubsequentLabels, let highlightText):
set(color: color, lineWidth: lineWidth, pageLabel: pageLabel, updateSubsequentLabels: updateSubsequentLabels, highlightText: highlightText, key: key, viewModel: viewModel)
@@ -148,6 +151,17 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
}
}
+ private func updateTextCache(key: String, text: String, font: UIFont, viewModel: ViewModel) {
+ update(viewModel: viewModel, notifyListeners: false) { state in
+ var (cachedText, attributedTextByFont) = state.texts[key, default: (text, [:])]
+ if cachedText != text {
+ attributedTextByFont = [:]
+ }
+ attributedTextByFont[font] = htmlAttributedStringConverter.convert(text: text, baseAttributes: [.font: font])
+ state.texts[key] = (text, attributedTextByFont)
+ }
+ }
+
private func changeIdleTimer(disabled: Bool, in viewModel: ViewModel) {
guard viewModel.state.settings.idleTimerDisabled != disabled else { return }
var settings = viewModel.state.settings
@@ -314,10 +328,19 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
}
}
- private func set(color: String, lineWidth: CGFloat, pageLabel: String, updateSubsequentLabels: Bool, highlightText: String, key: String, viewModel: ViewModel) {
+ private func set(
+ color: String,
+ lineWidth: CGFloat,
+ pageLabel: String,
+ updateSubsequentLabels: Bool,
+ highlightText: NSAttributedString,
+ key: String,
+ viewModel: ViewModel
+ ) {
+ let text = htmlAttributedStringConverter.convert(attributedString: highlightText)
let values = [
KeyBaseKeyPair(key: FieldKeys.Item.Annotation.pageLabel, baseKey: nil): pageLabel,
- KeyBaseKeyPair(key: FieldKeys.Item.Annotation.text, baseKey: nil): highlightText,
+ KeyBaseKeyPair(key: FieldKeys.Item.Annotation.text, baseKey: nil): text,
KeyBaseKeyPair(key: FieldKeys.Item.Annotation.color, baseKey: nil): color,
KeyBaseKeyPair(key: FieldKeys.Item.Annotation.Position.lineWidth, baseKey: FieldKeys.Item.Annotation.position): "\(Decimal(lineWidth).rounded(to: 3))"
]
@@ -697,6 +720,7 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
// Get sorted database keys
var keys = viewModel.state.snapshotKeys ?? viewModel.state.sortedKeys
var annotations: [String: HtmlEpubAnnotation] = viewModel.state.annotations
+ var texts = viewModel.state.texts
var comments = viewModel.state.comments
var selectionDeleted = false
var popoverWasInserted = false
@@ -730,7 +754,28 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
if item.changeType == .sync {
// Update comment if it's remote sync change
DDLogInfo("HtmlEpubReaderActionHandler: update comment")
- comments[key] = htmlAttributedStringConverter.convert(text: annotation.comment, baseAttributes: [.font: viewModel.state.commentFont])
+ let textCacheTuple: (String, [UIFont: NSAttributedString])?
+ let comment: NSAttributedString?
+ // Annotation text
+ switch annotation.type {
+ case .highlight, .underline:
+ textCacheTuple = annotation.text.flatMap({
+ ($0, [viewModel.state.textFont: htmlAttributedStringConverter.convert(text: $0, baseAttributes: [.font: viewModel.state.textFont])])
+ })
+
+ case .note, .image, .ink, .freeText:
+ textCacheTuple = nil
+ }
+ texts[key] = textCacheTuple
+ // Annotation comment
+ switch annotation.type {
+ case .note, .highlight, .image, .underline:
+ comment = htmlAttributedStringConverter.convert(text: annotation.comment, baseAttributes: [.font: viewModel.state.commentFont])
+
+ case .ink, .freeText:
+ comment = nil
+ }
+ comments[key] = comment
}
}
}
@@ -810,6 +855,7 @@ final class HtmlEpubReaderActionHandler: ViewModelActionHandler, BackgroundDbPro
state.annotations = annotations
state.documentUpdate = HtmlEpubReaderState.DocumentUpdate(deletions: deletedPdfAnnotations, insertions: insertedPdfAnnotations, modifications: updatedPdfAnnotations)
state.comments = comments
+ state.texts = texts
// Filter updated keys to include only keys that are actually available in `sortedKeys`. If filter/search is turned on and an item is edited so that it disappears from the filter/search,
// `updatedKeys` will try to update it while the key will be deleted from data source at the same time.
state.updatedAnnotationKeys = updatedKeys.filter({ state.sortedKeys.contains($0) })
diff --git a/Zotero/Scenes/Detail/HTML:EPUB/Views/HtmlEpubReaderViewController.swift b/Zotero/Scenes/Detail/HTML:EPUB/Views/HtmlEpubReaderViewController.swift
index 2d68cde8f..d1d1d1f05 100644
--- a/Zotero/Scenes/Detail/HTML:EPUB/Views/HtmlEpubReaderViewController.swift
+++ b/Zotero/Scenes/Detail/HTML:EPUB/Views/HtmlEpubReaderViewController.swift
@@ -510,3 +510,28 @@ extension HtmlEpubReaderViewController: UIPopoverPresentationControllerDelegate
}
extension HtmlEpubReaderViewController: HtmlEpubReaderContainerDelegate {}
+
+extension HtmlEpubReaderViewController: HtmlEpubAnnotationsDelegate {
+ func parseAndCacheIfNeededAttributedText(for annotation: HtmlEpubAnnotation, with font: UIFont) -> NSAttributedString? {
+ guard let text = annotation.text, !text.isEmpty else { return nil }
+
+ if let attributedText = viewModel.state.texts[annotation.key]?.1[font] {
+ return attributedText
+ }
+
+ viewModel.process(action: .parseAndCacheText(key: annotation.key, text: text, font: font))
+ return viewModel.state.texts[annotation.key]?.1[font]
+ }
+
+ func parseAndCacheIfNeededAttributedComment(for annotation: HtmlEpubAnnotation) -> NSAttributedString? {
+ let comment = annotation.comment
+ guard !comment.isEmpty else { return nil }
+
+ if let attributedComment = viewModel.state.comments[annotation.key] {
+ return attributedComment
+ }
+
+ viewModel.process(action: .parseAndCacheComment(key: annotation.key, comment: comment))
+ return viewModel.state.comments[annotation.key]
+ }
+}
diff --git a/Zotero/Scenes/Detail/HTML:EPUB/Views/HtmlEpubSidebarViewController.swift b/Zotero/Scenes/Detail/HTML:EPUB/Views/HtmlEpubSidebarViewController.swift
index 057c9670d..789db95cf 100644
--- a/Zotero/Scenes/Detail/HTML:EPUB/Views/HtmlEpubSidebarViewController.swift
+++ b/Zotero/Scenes/Detail/HTML:EPUB/Views/HtmlEpubSidebarViewController.swift
@@ -10,6 +10,11 @@ import UIKit
import RxSwift
+protocol HtmlEpubAnnotationsDelegate: AnyObject {
+ func parseAndCacheIfNeededAttributedText(for annotation: HtmlEpubAnnotation, with font: UIFont) -> NSAttributedString?
+ func parseAndCacheIfNeededAttributedComment(for annotation: HtmlEpubAnnotation) -> NSAttributedString?
+}
+
class HtmlEpubSidebarViewController: UIViewController {
private static let cellId = "AnnotationCell"
private let viewModel: ViewModel
@@ -274,17 +279,18 @@ class HtmlEpubSidebarViewController: UIViewController {
for: annotation,
userId: viewModel.state.userId,
library: viewModel.state.library,
+ highlightFont: viewModel.state.textFont,
sender: sender,
userInterfaceStyle: viewModel.state.settings.appearance.userInterfaceStyle,
- saveAction: { [weak self] color, lineWidth, _, pageLabel, updateSubsequentLabels, highlightText in
+ saveAction: { [weak self] data, updateSubsequentLabels in
self?.viewModel.process(
action: .updateAnnotationProperties(
key: key,
- color: color,
- lineWidth: lineWidth,
- pageLabel: pageLabel,
+ color: data.color,
+ lineWidth: data.lineWidth,
+ pageLabel: data.pageLabel,
updateSubsequentLabels: updateSubsequentLabels,
- highlightText: highlightText
+ highlightText: data.highlightText
)
)
},
diff --git a/Zotero/Scenes/Detail/PDF/PDFCoordinator.swift b/Zotero/Scenes/Detail/PDF/PDFCoordinator.swift
index 8895da2aa..136191744 100644
--- a/Zotero/Scenes/Detail/PDF/PDFCoordinator.swift
+++ b/Zotero/Scenes/Detail/PDF/PDFCoordinator.swift
@@ -199,9 +199,9 @@ extension PDFCoordinator: PdfReaderCoordinatorDelegate {
navigationController.overrideUserInterfaceStyle = userInterfaceStyle
let author = viewModel.state.library.identifier == .custom(.myLibrary) ? "" : annotation.author(displayName: viewModel.state.displayName, username: viewModel.state.username)
- let comment: NSAttributedString = (self.navigationController?.viewControllers.first as? AnnotationsDelegate)?.parseAndCacheIfNeededAttributedComment(for: annotation) ?? .init(string: "")
+ let comment: NSAttributedString = (self.navigationController?.viewControllers.first as? PDFAnnotationsDelegate)?.parseAndCacheIfNeededAttributedComment(for: annotation) ?? .init(string: "")
let highlightFont = viewModel.state.textEditorFont
- let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? AnnotationsDelegate)?
+ let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? PDFAnnotationsDelegate)?
.parseAndCacheIfNeededAttributedText(for: annotation, with: highlightFont) ?? .init(string: "")
let editability = annotation.editability(currentUserId: viewModel.state.userId, library: viewModel.state.library)
@@ -621,7 +621,7 @@ extension PDFCoordinator: PdfAnnotationsCoordinatorDelegate {
let navigationController = NavigationViewController()
navigationController.overrideUserInterfaceStyle = userInterfaceStyle
- let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? AnnotationsDelegate)?
+ let highlightText: NSAttributedString = (self.navigationController?.viewControllers.first as? PDFAnnotationsDelegate)?
.parseAndCacheIfNeededAttributedText(for: annotation, with: highlightFont) ?? .init(string: "")
let coordinator = AnnotationEditCoordinator(
data: AnnotationEditState.Data(
diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift
index 8cf8e3d31..e19ac8b78 100644
--- a/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift
+++ b/Zotero/Scenes/Detail/PDF/Views/PDFAnnotationsViewController.swift
@@ -15,7 +15,7 @@ import RxSwift
typealias AnnotationsViewControllerAction = (AnnotationView.Action, Annotation, UIButton) -> Void
-protocol AnnotationsDelegate: AnyObject {
+protocol PDFAnnotationsDelegate: AnyObject {
func parseAndCacheIfNeededAttributedText(for annotation: PDFAnnotation, with font: UIFont) -> NSAttributedString?
func parseAndCacheIfNeededAttributedComment(for annotation: PDFAnnotation) -> NSAttributedString?
}
@@ -36,7 +36,7 @@ final class PDFAnnotationsViewController: UIViewController {
private var dataSource: TableViewDiffableDataSource!
private var searchController: UISearchController!
- weak var parentDelegate: (PDFReaderContainerDelegate & SidebarDelegate & AnnotationsDelegate)?
+ weak var parentDelegate: (PDFReaderContainerDelegate & SidebarDelegate & PDFAnnotationsDelegate)?
weak var coordinatorDelegate: PdfAnnotationsCoordinatorDelegate?
weak var boundingBoxConverter: AnnotationBoundingBoxConverter?
diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift
index 5d0f33cf5..6d4d776a0 100644
--- a/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift
+++ b/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift
@@ -807,7 +807,7 @@ extension PDFReaderViewController: SidebarDelegate {
}
}
-extension PDFReaderViewController: AnnotationsDelegate {
+extension PDFReaderViewController: PDFAnnotationsDelegate {
func parseAndCacheIfNeededAttributedText(for annotation: any PDFAnnotation, with font: UIFont) -> NSAttributedString? {
guard let text = annotation.text, !text.isEmpty else { return nil }
diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFSidebarViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFSidebarViewController.swift
index 85f6b1346..97cc1eba2 100644
--- a/Zotero/Scenes/Detail/PDF/Views/PDFSidebarViewController.swift
+++ b/Zotero/Scenes/Detail/PDF/Views/PDFSidebarViewController.swift
@@ -47,7 +47,7 @@ class PDFSidebarViewController: UIViewController {
private weak var controllerContainer: UIView!
private weak var currentController: UIViewController?
private var controllerDisposeBag: DisposeBag?
- weak var parentDelegate: (PDFReaderContainerDelegate & SidebarDelegate & AnnotationsDelegate)?
+ weak var parentDelegate: (PDFReaderContainerDelegate & SidebarDelegate & PDFAnnotationsDelegate)?
weak var coordinatorDelegate: PdfAnnotationsCoordinatorDelegate?
weak var boundingBoxConverter: AnnotationBoundingBoxConverter?