Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added sepia mode to PDF reader #1070

Merged
merged 3 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Zotero.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,10 @@
B36FD9AF2A78FB13002D77E8 /* EditAnnotationFontSizeDbRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B36FD9AE2A78FB13002D77E8 /* EditAnnotationFontSizeDbRequest.swift */; };
B36FD9B12A7924CB002D77E8 /* FontSizePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B36FD9B02A7924CB002D77E8 /* FontSizePickerViewController.swift */; };
B36FD9B32A7929C8002D77E8 /* FontSizeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B36FD9B22A7929C8002D77E8 /* FontSizeCell.swift */; };
B370625E2D5F407100E1722B /* ReaderSettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3F9A4C22B04D0D800684030 /* ReaderSettingsState.swift */; };
B370625F2D5F595600E1722B /* PDFSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B35B473B25F11BCD0023B6F4 /* PDFSettings.swift */; };
B37062612D5F75E200E1722B /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B37062602D5F75DF00E1722B /* Appearance.swift */; };
B37062622D5F75E200E1722B /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B37062602D5F75DF00E1722B /* Appearance.swift */; };
B37080532AA7216E006F56B9 /* Localizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B37080512AA72135006F56B9 /* Localizable.swift */; };
B371494C25D585EA00D6391E /* RestoreDeletionsSyncAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B340868825D574D6000F4446 /* RestoreDeletionsSyncAction.swift */; };
B371494F25D585EF00D6391E /* MarkObjectsAsChangedByUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B340868C25D579C9000F4446 /* MarkObjectsAsChangedByUser.swift */; };
Expand Down Expand Up @@ -1805,6 +1809,7 @@
B36FD9AE2A78FB13002D77E8 /* EditAnnotationFontSizeDbRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAnnotationFontSizeDbRequest.swift; sourceTree = "<group>"; };
B36FD9B02A7924CB002D77E8 /* FontSizePickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSizePickerViewController.swift; sourceTree = "<group>"; };
B36FD9B22A7929C8002D77E8 /* FontSizeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSizeCell.swift; sourceTree = "<group>"; };
B37062602D5F75DF00E1722B /* Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appearance.swift; sourceTree = "<group>"; };
B37080512AA72135006F56B9 /* Localizable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localizable.swift; sourceTree = "<group>"; };
B372CEDF2486504600B423AE /* GroupVersionsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupVersionsRequest.swift; sourceTree = "<group>"; };
B372CEE22486512500B423AE /* GroupRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupRequest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2692,6 +2697,7 @@
B3B557122C884B7D00BD6325 /* AnnotationPreview.swift */,
B3F6AA392AB30663005BC22E /* AnnotationTool.swift */,
B311737A2AFCF004007D9741 /* AnnotationType.swift */,
B37062602D5F75DF00E1722B /* Appearance.swift */,
B305656123FC051E003304F2 /* AppGroup.swift */,
B305653A23FC051E003304F2 /* Attachment.swift */,
B339459026DE6C2900E59A02 /* AttachmentFileDeletedNotification.swift */,
Expand Down Expand Up @@ -5180,6 +5186,7 @@
B3D427D62CB67EFC0058453A /* AutoEmptyTrashController.swift in Sources */,
B30565EB23FC051E003304F2 /* Controllers.swift in Sources */,
B3A351E12715784A002E597A /* WebDavDownloadRequest.swift in Sources */,
B37062612D5F75E200E1722B /* Appearance.swift in Sources */,
B305CEBB29E6E67600B9E2B4 /* AssignItemsToTagDbRequest.swift in Sources */,
B39AF554290033CD001F400F /* TableOfContentsViewController.swift in Sources */,
B3593F78241A76E600760E20 /* CollectionEditError.swift in Sources */,
Expand Down Expand Up @@ -5736,6 +5743,7 @@
B3868541270DC3AA0068A022 /* WebDavScheme.swift in Sources */,
B352FFCC2CBE944400D0887B /* AutoEmptyTrashDbRequest.swift in Sources */,
B331F9AF2653CEA00099F6A6 /* ReadGroupDbRequest.swift in Sources */,
B370625F2D5F595600E1722B /* PDFSettings.swift in Sources */,
B33E8A4B27B6A39100CBC7DE /* CollectionCell.swift in Sources */,
B3DDC0CC2667825E00B2DFD1 /* RegularExpression+Extensions.swift in Sources */,
B30566EB23FC082A003304F2 /* ObjectsRequest.swift in Sources */,
Expand All @@ -5754,6 +5762,7 @@
B30566F123FC0845003304F2 /* CheckItemIsChangedDbRequest.swift in Sources */,
B3AFB83D2A0CF1EE008C2374 /* EmptyDecodable.swift in Sources */,
B36459E42644120D00A0C2C0 /* Color+Extension.swift in Sources */,
B370625E2D5F407100E1722B /* ReaderSettingsState.swift in Sources */,
B34ACC7F2514EB6100040C17 /* AnnotationColorGenerator.swift in Sources */,
B3FE79E42B333030009FBDBD /* DeleteDownloadDbRequest.swift in Sources */,
B305670423FC08A6003304F2 /* ReadAllCustomLibrariesDbRequest.swift in Sources */,
Expand Down Expand Up @@ -5902,6 +5911,7 @@
B3B557182C8860E600BD6325 /* CitationMetadata.swift in Sources */,
B305677023FC0A58003304F2 /* Alamofire+RxSwift.swift in Sources */,
B330261225DEAD9A00742025 /* RPageIndex.swift in Sources */,
B37062622D5F75E200E1722B /* Appearance.swift in Sources */,
B305670023FC086B003304F2 /* MarkObjectsAsDeletedDbRequest.swift in Sources */,
B34341B7260A49C300093E63 /* ReadCollectionAndLibraryDbRequest.swift in Sources */,
B305671023FC08C3003304F2 /* ReadSearchesDbRequest.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Zotero/Assets/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@
"pdf.settings.appearance.auto" = "Automatic";
"pdf.settings.appearance.light_mode" = "Light";
"pdf.settings.appearance.dark_mode" = "Dark";
"pdf.settings.appearance.sepia_mode" = "Sepia";
"pdf.settings.page_mode.title" = "Page Mode";
"pdf.settings.page_mode.single" = "Single";
"pdf.settings.page_mode.double" = "Double";
Expand Down
22 changes: 11 additions & 11 deletions Zotero/Controllers/AnnotationColorGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,33 @@ struct AnnotationColorGenerator {
private static let underlineOpacity: CGFloat = 1
private static let underlineDarkOpacity: CGFloat = 1

static func color(from color: UIColor, type: AnnotationType?, userInterfaceStyle: UIUserInterfaceStyle) -> (color: UIColor, alpha: CGFloat, blendMode: CGBlendMode?) {
static func color(from color: UIColor, type: AnnotationType?, appearance: Appearance) -> (color: UIColor, alpha: CGFloat, blendMode: CGBlendMode?) {
let opacity: CGFloat
switch type {
case .none, .note, .image, .ink, .freeText:
return (color, 1, nil)

case .highlight:
switch userInterfaceStyle {
switch appearance {
case .dark:
opacity = Self.highlightDarkOpacity

default:
case .light, .sepia:
opacity = Self.highlightOpacity
}

case .underline:
switch userInterfaceStyle {
switch appearance {
case .dark:
opacity = Self.underlineDarkOpacity

default:
case .light, .sepia:
opacity = Self.underlineOpacity
}
}

let adjustedColor: UIColor
switch userInterfaceStyle {
switch appearance {
case .dark:
var hue: CGFloat = 0
var sat: CGFloat = 0
Expand All @@ -52,24 +52,24 @@ struct AnnotationColorGenerator {
let adjustedSat = min(1, (sat * 1.2))
adjustedColor = UIColor(hue: hue, saturation: adjustedSat, brightness: brg, alpha: opacity)

default:
case .light, .sepia:
adjustedColor = color.withAlphaComponent(opacity)
}

return (adjustedColor, opacity, Self.blendMode(for: userInterfaceStyle, type: type))
return (adjustedColor, opacity, Self.blendMode(for: appearance, type: type))
}

static func blendMode(for userInterfaceStyle: UIUserInterfaceStyle, type: AnnotationType?) -> CGBlendMode? {
static func blendMode(for appearance: Appearance, type: AnnotationType?) -> CGBlendMode? {
switch type {
case .none, .note, .image, .ink, .freeText:
return nil

case .highlight, .underline:
switch userInterfaceStyle {
switch appearance {
case .dark:
return .lighten

default:
case .light, .sepia:
return .multiply
}
}
Expand Down
8 changes: 4 additions & 4 deletions Zotero/Controllers/AnnotationConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ struct AnnotationConverter {
static func annotations(
from items: Results<RItem>,
type: Kind = .zotero,
interfaceStyle: UIUserInterfaceStyle,
appearance: Appearance,
currentUserId: Int,
library: Library,
displayName: String,
Expand All @@ -221,7 +221,7 @@ struct AnnotationConverter {
return annotation(
from: dbAnnotation,
type: type,
interfaceStyle: interfaceStyle,
appearance: appearance,
currentUserId: currentUserId,
library: library,
displayName: displayName,
Expand All @@ -234,7 +234,7 @@ struct AnnotationConverter {
static func annotation(
from zoteroAnnotation: PDFDatabaseAnnotation,
type: Kind,
interfaceStyle: UIUserInterfaceStyle,
appearance: Appearance,
currentUserId: Int,
library: Library,
displayName: String,
Expand All @@ -244,7 +244,7 @@ struct AnnotationConverter {
let (color, alpha, blendMode) = AnnotationColorGenerator.color(
from: UIColor(hex: zoteroAnnotation.color),
type: zoteroAnnotation.type,
userInterfaceStyle: interfaceStyle
appearance: appearance
)
let annotation: PSPDFKit.Annotation

Expand Down
53 changes: 31 additions & 22 deletions Zotero/Controllers/AnnotationPreviewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class AnnotationPreviewController: NSObject {
let imageSize: CGSize
let imageScale: CGFloat
let includeAnnotation: Bool
let isDark: Bool
let appearance: Appearance
let type: PreviewType

init(
Expand All @@ -37,7 +37,7 @@ final class AnnotationPreviewController: NSObject {
parentKey: String,
libraryId: LibraryIdentifier,
includeAnnotation: Bool = false,
isDark: Bool = false,
appearance: Appearance,
type: PreviewType
) {
self.key = key
Expand All @@ -49,11 +49,11 @@ final class AnnotationPreviewController: NSObject {
self.imageSize = imageSize
self.imageScale = imageScale
self.includeAnnotation = includeAnnotation
self.isDark = isDark
self.appearance = appearance
self.type = type
}

init?(annotation: PSPDFKit.Annotation, parentKey: String, libraryId: LibraryIdentifier, imageSize: CGSize, imageScale: CGFloat, isDark: Bool, type: PreviewType) {
init?(annotation: PSPDFKit.Annotation, parentKey: String, libraryId: LibraryIdentifier, imageSize: CGSize, imageScale: CGFloat, appearance: Appearance, type: PreviewType) {
guard annotation.shouldRenderPreview, let document = annotation.document else { return nil }
key = annotation.previewId
self.parentKey = parentKey
Expand All @@ -64,7 +64,7 @@ final class AnnotationPreviewController: NSObject {
self.imageSize = imageSize
self.imageScale = imageScale
includeAnnotation = annotation is PSPDFKit.InkAnnotation || annotation is PSPDFKit.FreeTextAnnotation
self.isDark = isDark
self.appearance = appearance
self.type = type
}
}
Expand Down Expand Up @@ -141,6 +141,7 @@ extension AnnotationPreviewController {
key: key,
parentKey: parentKey,
libraryId: libraryId,
appearance: .light,
type: .temporary(subscriberKey: subscriberKey)
)
)
Expand All @@ -155,22 +156,22 @@ extension AnnotationPreviewController {
/// - parameter parentKey: Key of PDF item.
/// - parameter libraryId: Library identifier of item.
/// - parameter isDark: `true` if dark mode is on, `false` otherwise.
func store(for annotation: PSPDFKit.Annotation, parentKey: String, libraryId: LibraryIdentifier, isDark: Bool) {
func store(for annotation: PSPDFKit.Annotation, parentKey: String, libraryId: LibraryIdentifier, appearance: Appearance) {
queue.async { [weak self] in
guard
let self,
let data = EnqueuedData(annotation: annotation, parentKey: parentKey, libraryId: libraryId, imageSize: previewSize, imageScale: 0, isDark: isDark, type: .cachedAndReported)
let data = EnqueuedData(annotation: annotation, parentKey: parentKey, libraryId: libraryId, imageSize: previewSize, imageScale: 0, appearance: appearance, type: .cachedAndReported)
else { return }
enqueue(data: data)
}
}

func store(annotations: [PSPDFKit.Annotation], parentKey: String, libraryId: LibraryIdentifier, isDark: Bool) {
func store(annotations: [PSPDFKit.Annotation], parentKey: String, libraryId: LibraryIdentifier, appearance: Appearance) {
queue.async { [weak self] in
guard let self else { return }
for annotation in annotations {
guard
let data = EnqueuedData(annotation: annotation, parentKey: parentKey, libraryId: libraryId, imageSize: previewSize, imageScale: 0, isDark: isDark, type: .cachedOnly)
let data = EnqueuedData(annotation: annotation, parentKey: parentKey, libraryId: libraryId, imageSize: previewSize, imageScale: 0, appearance: appearance, type: .cachedOnly)
else { continue }
enqueue(data: data)
}
Expand All @@ -193,8 +194,9 @@ extension AnnotationPreviewController {
queue.async { [weak self] in
guard let self else { return }
for key in keys {
try? fileStorage.remove(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, isDark: true))
try? fileStorage.remove(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, isDark: false))
try? fileStorage.remove(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: .dark))
try? fileStorage.remove(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: .light))
try? fileStorage.remove(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: .sepia))
}
}
}
Expand All @@ -211,8 +213,8 @@ extension AnnotationPreviewController {
/// - parameter libraryId: Library identifier of item.
/// - parameter isDark: `true` if dark mode is on, `false` otherwise.
/// - returns: `true` if preview is available, `false` otherwise.
func hasPreview(for key: String, parentKey: String, libraryId: LibraryIdentifier, isDark: Bool) -> Bool {
return fileStorage.has(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, isDark: isDark))
func hasPreview(for key: String, parentKey: String, libraryId: LibraryIdentifier, appearance: Appearance) -> Bool {
return fileStorage.has(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: appearance))
}

/// Loads cached preview for given annotation.
Expand All @@ -221,12 +223,12 @@ extension AnnotationPreviewController {
/// - parameter libraryId: Library identifier of item.
/// - parameter isDark: `true` if dark mode is on, `false` otherwise.
/// - parameter completed: Completion handler which contains loaded preview or `nil` if loading wasn't successful.
func preview(for key: String, parentKey: String, libraryId: LibraryIdentifier, isDark: Bool, completed: @escaping (UIImage?) -> Void) {
func preview(for key: String, parentKey: String, libraryId: LibraryIdentifier, appearance: Appearance, completed: @escaping (UIImage?) -> Void) {
queue.async { [weak self] in
guard let self else { return }

do {
let data = try fileStorage.read(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, isDark: isDark))
let data = try fileStorage.read(Files.annotationPreview(annotationKey: key, pdfKey: parentKey, libraryId: libraryId, appearance: appearance))
let image = UIImage(data: data)
DispatchQueue.main.async {
completed(image)
Expand All @@ -242,9 +244,16 @@ extension AnnotationPreviewController {
/// Creates and enqueues a render request for PSPDFKit rendering engine.
private func enqueue(data: EnqueuedData) {
let options = RenderOptions()
if data.isDark {
switch data.appearance {
case .dark:
options.invertRenderColor = true
options.filters = [.colorCorrectInverted]

case .sepia:
options.filters = [.sepia]

case .light:
break
}

let request = MutableRenderRequest(document: data.document)
Expand All @@ -261,7 +270,7 @@ extension AnnotationPreviewController {
task.completionHandler = { [weak self] image, error in
let result: Result<UIImage, Swift.Error> = image.flatMap({ .success($0) }) ?? .failure(error ?? Error.imageNotAvailable)
self?.queue.async {
self?.completeRequest(with: result, key: data.key, parentKey: data.parentKey, libraryId: data.libraryId, isDark: data.isDark, type: data.type)
self?.completeRequest(with: result, key: data.key, parentKey: data.parentKey, libraryId: data.libraryId, appearance: data.appearance, type: data.type)
}
}

Expand All @@ -271,18 +280,18 @@ extension AnnotationPreviewController {
}
}

private func completeRequest(with result: Result<UIImage, Swift.Error>, key: String, parentKey: String, libraryId: LibraryIdentifier, isDark: Bool, type: PreviewType) {
private func completeRequest(with result: Result<UIImage, Swift.Error>, key: String, parentKey: String, libraryId: LibraryIdentifier, appearance: Appearance, type: PreviewType) {
switch result {
case .success(let image):
switch type {
case .temporary(let subscriberKey):
perform(event: .success(image), subscriberKey: subscriberKey)

case .cachedOnly:
cache(image: image, key: key, pdfKey: parentKey, libraryId: libraryId, isDark: isDark)
cache(image: image, key: key, pdfKey: parentKey, libraryId: libraryId, appearance: appearance)

case .cachedAndReported:
cache(image: image, key: key, pdfKey: parentKey, libraryId: libraryId, isDark: isDark)
cache(image: image, key: key, pdfKey: parentKey, libraryId: libraryId, appearance: appearance)
observable.on(.next((key, parentKey, image)))
}

Expand All @@ -305,15 +314,15 @@ extension AnnotationPreviewController {
subscribers[subscriberKey] = nil
}

private func cache(image: UIImage, key: String, pdfKey: String, libraryId: LibraryIdentifier, isDark: Bool) {
private func cache(image: UIImage, key: String, pdfKey: String, libraryId: LibraryIdentifier, appearance: Appearance) {
autoreleasepool {
guard let data = image.pngData() else {
DDLogError("AnnotationPreviewController: can't create data from image")
return
}

do {
try fileStorage.write(data, to: Files.annotationPreview(annotationKey: key, pdfKey: pdfKey, libraryId: libraryId, isDark: isDark), options: .atomicWrite)
try fileStorage.write(data, to: Files.annotationPreview(annotationKey: key, pdfKey: pdfKey, libraryId: libraryId, appearance: appearance), options: .atomicWrite)
} catch let error {
DDLogError("AnnotationPreviewController: can't store preview - \(error)")
}
Expand Down
Loading