From bec438c104489630fd1245e02f574557236de404 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Thu, 19 Dec 2024 16:33:03 +0200 Subject: [PATCH 01/33] Bundle zotero/pdf-worker --- .gitignore | 1 + .gitmodules | 3 +++ Zotero.xcodeproj/project.pbxproj | 21 +++++++++++++++++++++ pdf-worker | 1 + scripts/bundle_pdf_worker.sh | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+) create mode 160000 pdf-worker create mode 100755 scripts/bundle_pdf_worker.sh diff --git a/.gitignore b/.gitignore index 35ac99399..3c9ad6e4f 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,4 @@ bundled/translators bundled/styles bundled/locales bundled/note_editor +bundled/pdf_worker diff --git a/.gitmodules b/.gitmodules index bfc30a394..de5b9b806 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "note-editor"] path = note-editor url = https://github.com/zotero/note-editor +[submodule "pdf-worker"] + path = pdf-worker + url = https://github.com/zotero/pdf-worker diff --git a/Zotero.xcodeproj/project.pbxproj b/Zotero.xcodeproj/project.pbxproj index 234200535..286161fe4 100644 --- a/Zotero.xcodeproj/project.pbxproj +++ b/Zotero.xcodeproj/project.pbxproj @@ -4325,6 +4325,7 @@ B3593F142668D37900FA4BB2 /* Bundle Styles */, B34A85AC243CB1E4003D5638 /* Bundle Translators */, B3A1D03D2BA321B30040FEE9 /* Bundle Note Editor */, + 61BDE15F2D1458D200100612 /* Bundle PDF Worker */, B337A5AD244F229400AFD13D /* SwiftGen */, B30D59512206F60400884C4A /* Sources */, B30D59522206F60400884C4A /* Frameworks */, @@ -4655,6 +4656,26 @@ shellPath = /bin/sh; shellScript = "swiftlint\n"; }; + 61BDE15F2D1458D200100612 /* Bundle PDF Worker */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/scripts/bundle_pdf_worker.sh", + ); + name = "Bundle PDF Worker"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "sh \"$SCRIPT_INPUT_FILE_0\"\n"; + }; B3087B87245190F500FF05ED /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/pdf-worker b/pdf-worker new file mode 160000 index 000000000..01901bf65 --- /dev/null +++ b/pdf-worker @@ -0,0 +1 @@ +Subproject commit 01901bf65e12741e727df38eaaa24a67d4fc6a5e diff --git a/scripts/bundle_pdf_worker.sh b/scripts/bundle_pdf_worker.sh new file mode 100755 index 000000000..f81891622 --- /dev/null +++ b/scripts/bundle_pdf_worker.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +realpath() { + [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" +} + +SCRIPT_PATH=`realpath "$0"` +SCRIPT_DIR=`dirname "$SCRIPT_PATH"` +WORKER_SUBMODULE_DIR="$SCRIPT_DIR/../pdf-worker" +WORKER_DIR="$SCRIPT_DIR/../bundled/pdf_worker" +HASH_FILE="$WORKER_DIR/pdf_worker_hash.txt" +CURRENT_HASH=`git ls-tree --object-only HEAD "$WORKER_SUBMODULE_DIR"` + +if [ -d "$WORKER_DIR" ]; then + if [ -f "$HASH_FILE" ]; then + CACHED_HASH=`cat "$HASH_FILE"` + else + CACHED_HASH=0 + fi + + if [ $CACHED_HASH == $CURRENT_HASH ]; then + exit + else + rm -rf "$WORKER_DIR" + fi +fi + +cd "$WORKER_SUBMODULE_DIR" +npm ci +npm run build +mv "$SCRIPT_DIR/../pdf-worker/build" "$WORKER_DIR" +echo "$CURRENT_HASH" > "$HASH_FILE" \ No newline at end of file From a9de38d4b38082003846683302762cd382f16b8e Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Tue, 28 Jan 2025 10:09:48 +0200 Subject: [PATCH 02/33] Add RecognizerController Add PDFWorkerController Add support for PDF worker --- Zotero.xcodeproj/project.pbxproj | 14 + Zotero/Assets/en.lproj/Localizable.strings | 1 + Zotero/Controllers/Controllers.swift | 11 + .../IdentifierLookupController.swift | 8 +- Zotero/Controllers/PDFWorkerController.swift | 213 ++++++++++ Zotero/Controllers/RecognizerController.swift | 402 ++++++++++++++++++ .../PDFWorkerWebViewHandler.swift | 251 +++++++++++ .../Web View Handling/WebViewHandler.swift | 17 + Zotero/Extensions/Localizable.swift | 2 + Zotero/Models/API/ItemResponse.swift | 7 + Zotero/Models/FieldKeys.swift | 6 + .../Detail/Items/Models/ItemAction.swift | 8 +- .../ViewModels/ItemsToolbarController.swift | 4 +- .../Items/Views/ItemsTableViewHandler.swift | 2 +- .../Items/Views/ItemsViewController.swift | 17 + .../Views/RItemsTableViewDataSource.swift | 34 +- .../Trash/Views/TrashViewController.swift | 2 +- .../Views/LibrariesViewController.swift | 20 +- Zotero/Scenes/Master/MasterCoordinator.swift | 20 +- 19 files changed, 1008 insertions(+), 31 deletions(-) create mode 100644 Zotero/Controllers/PDFWorkerController.swift create mode 100644 Zotero/Controllers/RecognizerController.swift create mode 100644 Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift diff --git a/Zotero.xcodeproj/project.pbxproj b/Zotero.xcodeproj/project.pbxproj index 286161fe4..200ef6640 100644 --- a/Zotero.xcodeproj/project.pbxproj +++ b/Zotero.xcodeproj/project.pbxproj @@ -9,6 +9,9 @@ /* Begin PBXBuildFile section */ 61099E6E2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m in Sources */ = {isa = PBXBuildFile; fileRef = 61099E6D2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m */; }; 61099E6F2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m in Sources */ = {isa = PBXBuildFile; fileRef = 61099E6D2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m */; }; + 61168F612D50D4B6005495E8 /* PDFWorkerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61168F602D50D4B6005495E8 /* PDFWorkerController.swift */; }; + 612361072D439D6A007FA575 /* PDFWorkerWebViewHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612361062D439D6A007FA575 /* PDFWorkerWebViewHandler.swift */; }; + 612361082D439D6A007FA575 /* PDFWorkerWebViewHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612361062D439D6A007FA575 /* PDFWorkerWebViewHandler.swift */; }; 6135EF9A2C2ABCD7008DA17B /* SquareAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6135EF992C2ABCD7008DA17B /* SquareAnnotationView.swift */; }; 6135EF9C2C2ABDD1008DA17B /* AnnotationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6135EF9B2C2ABDD1008DA17B /* AnnotationManager.swift */; }; 61391B7E2B3C6ED9003B314A /* CopyBibliographyAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61391B7D2B3C6ED9003B314A /* CopyBibliographyAction.swift */; }; @@ -35,6 +38,7 @@ 614D65822A79C9AC007CF449 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 614D65802A79C9AC007CF449 /* Localizable.stringsdict */; }; 614D65832A79C9B0007CF449 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 614D65802A79C9AC007CF449 /* Localizable.stringsdict */; }; 614D65872A8030C9007CF449 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 614D65862A8030C9007CF449 /* OrderedCollections */; }; + 615C10E12D5BA17A001F1E8E /* RecognizerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615C10E02D5BA17A001F1E8E /* RecognizerController.swift */; }; 61639F852AE03B8500026003 /* InstantPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61639F842AE03B8500026003 /* InstantPresenter.swift */; }; 618404262A4456A9005AAF22 /* IdentifierLookupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618404252A4456A9005AAF22 /* IdentifierLookupController.swift */; }; 618D83E72BAAC88C00E7966B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 618D83E62BAAC88C00E7966B /* PrivacyInfo.xcprivacy */; }; @@ -1300,6 +1304,8 @@ 61099E6C2C91BAF300EDD92C /* NSDecimalNumber+Rounding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDecimalNumber+Rounding.h"; sourceTree = ""; }; 61099E6D2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSDecimalNumber+Rounding.m"; sourceTree = ""; }; 611486502A9CD511002EEBEF /* ci_post_xcodebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_post_xcodebuild.sh; sourceTree = ""; }; + 61168F602D50D4B6005495E8 /* PDFWorkerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFWorkerController.swift; sourceTree = ""; }; + 612361062D439D6A007FA575 /* PDFWorkerWebViewHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFWorkerWebViewHandler.swift; sourceTree = ""; }; 6135EF992C2ABCD7008DA17B /* SquareAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquareAnnotationView.swift; sourceTree = ""; }; 6135EF9B2C2ABDD1008DA17B /* AnnotationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnotationManager.swift; sourceTree = ""; }; 61391B7D2B3C6ED9003B314A /* CopyBibliographyAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyBibliographyAction.swift; sourceTree = ""; }; @@ -1309,6 +1315,7 @@ 613F32782C1086950088EF70 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 614D65812A79C9AC007CF449 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 614D65842A7BCC22007CF449 /* ci_post_clone.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = ""; }; + 615C10E02D5BA17A001F1E8E /* RecognizerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecognizerController.swift; sourceTree = ""; }; 61639F842AE03B8500026003 /* InstantPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPresenter.swift; sourceTree = ""; }; 618404252A4456A9005AAF22 /* IdentifierLookupController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifierLookupController.swift; sourceTree = ""; }; 618D83E62BAAC88C00E7966B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; @@ -2362,7 +2369,9 @@ 61E24DCB2ABB385E00D75F50 /* OpenItemsController.swift */, B34A9F6325BF1ABB007C9A4A /* PDFDocumentExporter.swift */, B32B8A562B18A08900A9A741 /* PDFThumbnailController.swift */, + 61168F602D50D4B6005495E8 /* PDFWorkerController.swift */, B3C6D551261C9F2E0068B9FE /* PlaceholderTextViewDelegate.swift */, + 615C10E02D5BA17A001F1E8E /* RecognizerController.swift */, B378F4CC242CD45700B88A05 /* RepoParserDelegate.swift */, B305646A23FC051E003304F2 /* RItemLocaleController.swift */, B305646B23FC051E003304F2 /* SchemaController.swift */, @@ -3603,6 +3612,7 @@ isa = PBXGroup; children = ( B323703F2C0DC65600170779 /* LookupWebViewHandler.swift */, + 612361062D439D6A007FA575 /* PDFWorkerWebViewHandler.swift */, B39E0131283276830091CE4A /* WebViewHandler.swift */, ); path = "Web View Handling"; @@ -5038,6 +5048,7 @@ B39B18E8223947050019F467 /* main.swift in Sources */, B361820824C9B53600B30D56 /* KeyboardResponder.swift in Sources */, B3ADAE512833BEE300D46271 /* LookupAction.swift in Sources */, + 61168F612D50D4B6005495E8 /* PDFWorkerController.swift in Sources */, B3B1EE062502675E00D8BC1E /* Parsing.swift in Sources */, B305669823FC051F003304F2 /* RCollection.swift in Sources */, B3E44642248FBE9B007FE8AB /* LinkType.swift in Sources */, @@ -5150,6 +5161,7 @@ B3401D5B2567DAAE00BB8D6E /* AnnotationPopoverCoordinator.swift in Sources */, B39505E82AE9469300F73079 /* AnnotationPopoverState.swift in Sources */, B3B953D22459B4D800FC96DB /* UITableViewCell+SwiftUI.swift in Sources */, + 612361072D439D6A007FA575 /* PDFWorkerWebViewHandler.swift in Sources */, B353F204242E52610062EE24 /* Database.swift in Sources */, B30565E023FC051E003304F2 /* ReadAnyChangedObjectsInLibraryDbRequest.swift in Sources */, B30566C023FC051F003304F2 /* DownloadBatch.swift in Sources */, @@ -5374,6 +5386,7 @@ B30566A723FC051F003304F2 /* DelayIntervals.swift in Sources */, B37DD576272AAF500038537D /* FilterAttachmentsDbRequest.swift in Sources */, B3E8FE042714292E00F51458 /* StorageSettingsAction.swift in Sources */, + 615C10E12D5BA17A001F1E8E /* RecognizerController.swift in Sources */, B3A081C025D2AD390088EC24 /* LoadDeletionsSyncAction.swift in Sources */, B3DCDF1F240945B40039ED0D /* CollectionsPickerActionHandler.swift in Sources */, B357DEE324165DEE00E06153 /* DebugLogging.swift in Sources */, @@ -5773,6 +5786,7 @@ B305670C23FC08BA003304F2 /* ReadItemDbRequest.swift in Sources */, B305670923FC08B3003304F2 /* ReadCollectionDbRequest.swift in Sources */, B3AB38A7238EC02D008A1ABB /* ExtensionViewModel.swift in Sources */, + 612361082D439D6A007FA575 /* PDFWorkerWebViewHandler.swift in Sources */, B3501F5425139B40007961DB /* Rounding+Extensions.swift in Sources */, B34341A1260A0A4800093E63 /* CollectionIdentifier.swift in Sources */, B3868538270D92DA0068A022 /* RawDataEncoding.swift in Sources */, diff --git a/Zotero/Assets/en.lproj/Localizable.strings b/Zotero/Assets/en.lproj/Localizable.strings index 1d278f411..0d7ca29d0 100644 --- a/Zotero/Assets/en.lproj/Localizable.strings +++ b/Zotero/Assets/en.lproj/Localizable.strings @@ -139,6 +139,7 @@ "items.action.create_parent" = "Create Parent Item"; "items.action.download" = "Download"; "items.action.remove_download" = "Remove Download"; +"items.action.retrieve_metadata" = "Retrieve Metadata"; "items.filters.title" = "Filters"; "items.filters.downloads" = "Downloaded Files"; "items.filters.tags" = "Tags"; diff --git a/Zotero/Controllers/Controllers.swift b/Zotero/Controllers/Controllers.swift index d70b24b52..639bdd8db 100644 --- a/Zotero/Controllers/Controllers.swift +++ b/Zotero/Controllers/Controllers.swift @@ -274,6 +274,8 @@ final class Controllers { controllers?.remoteFileDownloader.stop() // Cancel all background uploads controllers?.backgroundUploadObserver.cancelAllUploads() + // Cancel all PDF workers + controllers?.pdfWorkerController.cancellAllWorks() // Clear user controllers self.userControllers = nil // Clear API auth token @@ -308,6 +310,8 @@ final class UserControllers { let fileDownloader: AttachmentDownloader let remoteFileDownloader: RemoteAttachmentDownloader let identifierLookupController: IdentifierLookupController + let pdfWorkerController: PDFWorkerController + let recognizerController: RecognizerController let webSocketController: WebSocketController let fileCleanupController: AttachmentFileCleanupController let citationController: CitationController @@ -395,6 +399,13 @@ final class UserControllers { dateParser: controllers.dateParser, remoteFileDownloader: remoteFileDownloader ) + pdfWorkerController = PDFWorkerController() + recognizerController = RecognizerController( + pdfWorkerController: pdfWorkerController, + apiClient: controllers.apiClient, + translatorsController: controllers.translatorsAndStylesController, + schemaController: controllers.schemaController + ) self.webSocketController = webSocketController self.fileCleanupController = fileCleanupController citationController = CitationController( diff --git a/Zotero/Controllers/IdentifierLookupController.swift b/Zotero/Controllers/IdentifierLookupController.swift index 79b896454..ed86bea9e 100644 --- a/Zotero/Controllers/IdentifierLookupController.swift +++ b/Zotero/Controllers/IdentifierLookupController.swift @@ -13,10 +13,6 @@ import OrderedCollections import CocoaLumberjackSwift import RxSwift -protocol IdentifierLookupWebViewProvider: AnyObject { - func addWebView() -> WKWebView -} - protocol IdentifierLookupPresenter: AnyObject { func isPresenting() -> Bool } @@ -143,7 +139,7 @@ final class IdentifierLookupController { return (savedCount, failedCount, totalCount) } - internal weak var webViewProvider: IdentifierLookupWebViewProvider? + internal weak var webViewProvider: WebViewProvider? internal weak var presenter: IdentifierLookupPresenter? { didSet { guard presenter == nil, oldValue != nil else { return } @@ -198,7 +194,7 @@ final class IdentifierLookupController { } var lookupWebViewHandler: LookupWebViewHandler? inMainThread(sync: true) { - if let webView = self.webViewProvider?.addWebView() { + if let webView = self.webViewProvider?.addWebView(configuration: nil) { lookupWebViewHandler = LookupWebViewHandler(webView: webView, translatorsController: self.translatorsController) } } diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift new file mode 100644 index 000000000..e073d610f --- /dev/null +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -0,0 +1,213 @@ +// +// PDFWorkerController.swift +// Zotero +// +// Created by Miltiadis Vasilakis on 3/2/25. +// Copyright © 2025 Corporation for Digital Scholarship. All rights reserved. +// + +import Foundation +import WebKit +import OrderedCollections + +import CocoaLumberjackSwift +import RxSwift + +final class PDFWorkerController { + // MARK: Types + struct PDFWork: Hashable { + enum Kind { + case recognizer + case fullText + } + + let file: FileData + let kind: Kind + } + + struct Update { + enum Kind { + case failed + case cancelled + case inProgress + case extractedRecognizerData(data: [String: Any]) + case extractedFullText(data: [String: Any]) + } + + let work: PDFWork + let kind: Kind + } + + enum PDFWorkState { + case enqueued + case inProgress + } + + // MARK: Properties + private let dispatchSpecificKey: DispatchSpecificKey + private let accessQueueLabel: String + private let accessQueue: DispatchQueue + private let disposeBag: DisposeBag + + internal weak var webViewProvider: WebViewProvider? + + // Accessed only via accessQueue + private static let maxConcurrentPDFWorkers: Int = 1 + private var queue: OrderedDictionary)> = [:] + private var pdfWorkerWebViewHandlersByPDFWork: [PDFWork: PDFWorkerWebViewHandler] = [:] + + // MARK: Object Lifecycle + init() { + dispatchSpecificKey = DispatchSpecificKey() + accessQueueLabel = "org.zotero.PDFWorkerController.accessQueue" + accessQueue = DispatchQueue(label: accessQueueLabel, qos: .userInteractive, attributes: .concurrent) + accessQueue.setSpecific(key: dispatchSpecificKey, value: accessQueueLabel) + disposeBag = DisposeBag() + } + + // MARK: Actions + func queue(work: PDFWork, completion: @escaping (_ observable: PublishSubject?) -> Void) { + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { + completion(nil) + return + } + if let (_, observable) = queue[work] { + completion(observable) + return + } + let state: PDFWorkState = .enqueued + let observable: PublishSubject = PublishSubject() + queue[work] = (state, observable) + completion(observable) + + startWorkIfNeeded() + } + } + + private func startWorkIfNeeded() { + guard pdfWorkerWebViewHandlersByPDFWork.count < Self.maxConcurrentPDFWorkers else { return } + let works = queue.keys + for work in works { + guard let (state, observable) = queue[work] else { continue } + switch state { + case .enqueued: + start(work: work, observable: observable) + startWorkIfNeeded() + return + + case .inProgress: + break + } + } + + func start(work: PDFWork, observable: PublishSubject) { + var pdfWorkerWebViewHandler = pdfWorkerWebViewHandlersByPDFWork[work] + if pdfWorkerWebViewHandler == nil { + DispatchQueue.main.sync { [weak webViewProvider] in + guard let webViewProvider else { return } + let configuration = WKWebViewConfiguration() + configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs") + let webView = webViewProvider.addWebView(configuration: configuration) + pdfWorkerWebViewHandler = PDFWorkerWebViewHandler(webView: webView) + } + } + guard let pdfWorkerWebViewHandler else { + DDLogError("PDFWorkerController: can't create PDFWorkerWebViewHandler instance") + cleanupPDFWorker(for: work) { observable in + observable?.on(.next(Update(work: work, kind: .failed))) + } + return + } + pdfWorkerWebViewHandlersByPDFWork[work] = pdfWorkerWebViewHandler + setupObserver(for: pdfWorkerWebViewHandler) + queue[work] = (.inProgress, observable) + observable.on(.next(Update(work: work, kind: .inProgress))) + switch work.kind { + case .recognizer: + pdfWorkerWebViewHandler.recognize(file: work.file) + + case .fullText: + pdfWorkerWebViewHandler.getFullText(file: work.file) + } + + func setupObserver(for pdfWorkerWebViewHandler: PDFWorkerWebViewHandler) { + pdfWorkerWebViewHandler.observable + .subscribe(onNext: { process(result: $0) }) + .disposed(by: disposeBag) + + func process(result: Result) { + switch result { + case .success(let data): + switch data { + case .recognizerData(let data): + cleanupPDFWorker(for: work) { observable in + observable?.on(.next(Update(work: work, kind: .extractedRecognizerData(data: data)))) + } + + case .fullText(let data): + cleanupPDFWorker(for: work) { observable in + observable?.on(.next(Update(work: work, kind: .extractedFullText(data: data)))) + } + } + + case .failure(let error): + DDLogError("PDFWorkerController: recognizer failed - \(error)") + cleanupPDFWorker(for: work) { observable in + observable?.on(.next(Update(work: work, kind: .failed))) + } + } + } + } + } + } + + func cancel(work: PDFWork) { + cleanupPDFWorker(for: work) { observable in + DDLogInfo("PDFWorkerController: cancelled \(work)") + observable?.on(.next(Update(work: work, kind: .cancelled))) + } + } + + func cancellAllWorks() { + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { return } + DDLogInfo("PDFWorkerController: cancel all works") + // Immediatelly release all PDFWorker web views. + let keys = pdfWorkerWebViewHandlersByPDFWork.keys + for key in keys { + guard let webView = pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: key)?.webViewHandler.webView else { continue } + DispatchQueue.main.async { + webView.removeFromSuperview() + } + } + // Then cancel actual works, and send cancelled event for each queued work. + let works = queue.keys + for work in works { + cancel(work: work) + } + } + } + + private func cleanupPDFWorker(for work: PDFWork, completion: @escaping (_ observable: PublishSubject?) -> Void) { + if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { + cleanup(for: work, completion: completion) + } else { + accessQueue.async(flags: .barrier) { + cleanup(for: work, completion: completion) + } + } + + func cleanup(for work: PDFWork, completion: @escaping (_ observable: PublishSubject?) -> Void) { + let observable = queue.removeValue(forKey: work).flatMap({ $0.observable }) + DDLogInfo("PDFWorkerController: cleaned up for \(work)") + if let webView = pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: work)?.webViewHandler.webView { + DispatchQueue.main.async { + webView.removeFromSuperview() + } + } + completion(observable) + startWorkIfNeeded() + } + } +} diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift new file mode 100644 index 000000000..79906c783 --- /dev/null +++ b/Zotero/Controllers/RecognizerController.swift @@ -0,0 +1,402 @@ +// +// RecognizerController.swift +// Zotero +// +// Created by Miltiadis Vasilakis on 11/2/25. +// Copyright © 2025 Corporation for Digital Scholarship. All rights reserved. +// + +import Foundation +import WebKit +import OrderedCollections + +import CocoaLumberjackSwift +import RxSwift + +final class RecognizerController { + // MARK: Types + struct RemoteRecognizerResponse: Decodable { + struct Author: Decodable { + let firstName, lastName: String? + } + let arxiv, doi, isbn: String? + let abstract, language: String? + let title, type, year, pages, volume, url, issue, issn, container, publisher: String? + let authors: [Author] + } + + struct RecognizerRequest: ApiResponseRequest { + typealias Response = RemoteRecognizerResponse + let parameters: [String: Any]? + + var endpoint: ApiEndpoint { .other(URL(string: "https://services.zotero.org/recognizer/recognize")!) } + var httpMethod: ApiHttpMethod { .post } + var encoding: ApiParameterEncoding { .json } + var headers: [String: String]? { nil } + + init(parameters: [String: Any]) { + self.parameters = parameters + } + } + + struct RecognizerTask: Hashable { + let file: FileData + } + + enum Error: Swift.Error { + case cantStartPDFWorker + case pdfWorkerError + case recognizerFailed + case remoteRecognizerFailed + case cantCreateLookupWebViewHandler + case identifierNotAccepted + case lookupFailed + case parseFailed + } + + struct Update { + enum Kind { + case failed(Error) + case cancelled + case recognitionInProgress + case remoteRecognitionInProgress(data: [String: Any]) + case identifierLookupInProgress(response: RemoteRecognizerResponse) + case translated(item: ItemResponse) + } + + let task: RecognizerTask + let kind: Kind + } + + enum RecognizerTaskState { + case enqueued + case recognitionInProgress + case remoteRecognitionInProgress + case identifierLookupInProgress + } + + // MARK: Properties + private unowned let pdfWorkerController: PDFWorkerController + private unowned let apiClient: ApiClient + private unowned let translatorsController: TranslatorsAndStylesController + private unowned let schemaController: SchemaController + private let dispatchSpecificKey: DispatchSpecificKey + private let accessQueueLabel: String + private let accessQueue: DispatchQueue + private let disposeBag: DisposeBag + + internal weak var webViewProvider: WebViewProvider? + + // Accessed only via accessQueue + private static let maxConcurrentRecognizerTasks: Int = 1 + private var queue: OrderedDictionary)> = [:] + private var lookupWebViewHandlersByRecognizerTask: [RecognizerTask: LookupWebViewHandler] = [:] + + // MARK: Object Lifecycle + init(pdfWorkerController: PDFWorkerController, apiClient: ApiClient, translatorsController: TranslatorsAndStylesController, schemaController: SchemaController) { + self.pdfWorkerController = pdfWorkerController + self.apiClient = apiClient + self.translatorsController = translatorsController + self.schemaController = schemaController + dispatchSpecificKey = DispatchSpecificKey() + accessQueueLabel = "org.zotero.RecognizerController.accessQueue" + accessQueue = DispatchQueue(label: accessQueueLabel, qos: .userInteractive, attributes: .concurrent) + accessQueue.setSpecific(key: dispatchSpecificKey, value: accessQueueLabel) + disposeBag = DisposeBag() + } + + // MARK: Actions + func queue(task: RecognizerTask, completion: @escaping (_ observable: PublishSubject?) -> Void) { + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { + completion(nil) + return + } + if let (_, observable) = queue[task] { + completion(observable) + return + } + let state: RecognizerTaskState = .enqueued + let observable: PublishSubject = PublishSubject() + queue[task] = (state, observable) + completion(observable) + + startRecognitionIfNeeded() + } + } + + private func startRecognitionIfNeeded() { + let recognitionInProgressCount = queue.filter({ $0.value.state == .recognitionInProgress }).count + guard recognitionInProgressCount < Self.maxConcurrentRecognizerTasks else { return } + let tasks = queue.keys + for task in tasks { + guard let (state, observable) = queue[task] else { continue } + switch state { + case .enqueued: + start(task: task, observable: observable) + startRecognitionIfNeeded() + return + + case .recognitionInProgress, .remoteRecognitionInProgress, .identifierLookupInProgress: + break + } + } + + func start(task: RecognizerTask, observable: PublishSubject) { + queue[task] = (.recognitionInProgress, observable) + observable.on(.next(Update(task: task, kind: .recognitionInProgress))) + + pdfWorkerController.queue(work: PDFWorkerController.PDFWork(file: task.file, kind: .recognizer)) { [weak self] pdfWorkerObservable in + guard let self else { return } + guard let pdfWorkerObservable else { + DDLogError("RecognizerController: can't create start PDF worker") + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(.cantStartPDFWorker)))) + } + return + } + pdfWorkerObservable + .subscribe(onNext: { process(update: $0) }) + .disposed(by: disposeBag) + } + + func process(update: PDFWorkerController.Update) { + switch update.kind { + case .failed: + DDLogError("RecognizerController: recognizer failed") + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(.recognizerFailed)))) + } + + case .cancelled: + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .cancelled))) + } + + case .inProgress: + break + + case .extractedRecognizerData(data: let data): + DDLogInfo("RecognizerController: extracted recognizer data") + startRemoteRecognition(for: task, with: data) + + case .extractedFullText: + DDLogError("RecognizerController: PDF worker error") + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(.pdfWorkerError)))) + } + } + } + } + } + + private func startRemoteRecognition(for task: RecognizerTask, with data: [String: Any]) { + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { return } + guard let (_, observable) = queue[task] else { + startRecognitionIfNeeded() + return + } + queue[task] = (.remoteRecognitionInProgress, observable) + observable.on(.next(Update(task: task, kind: .remoteRecognitionInProgress(data: data)))) + + apiClient.send(request: RecognizerRequest(parameters: data)).subscribe( + onSuccess: { [weak self] (response: (RemoteRecognizerResponse, HTTPURLResponse)) in + DDLogInfo("RecognizerController: remote recognizer response received") + guard let self else { return } + process(response: response.0) + }, + onFailure: { [weak self] error in + DDLogError("RecognizerController: remote recognizer request failed: \(error)") + self?.cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(error as! Error)))) + } + } + ) + .disposed(by: disposeBag) + } + + func process(response: RemoteRecognizerResponse) { + var identifier: String? + var copyTagsAsAutomatic = false + if let arxiv = response.arxiv { + identifier = "arXiv: \(arxiv)" + } else if let doi = response.doi { + identifier = "DOI: \(doi)" + } else if let isbn = response.isbn { + identifier = "ISBN: \(isbn)" + copyTagsAsAutomatic = true + } + if let identifier { + startIdentifierLookup(for: task, with: identifier, response: response, copyTagsAsAutomatic: copyTagsAsAutomatic) + return + } + if let title = response.title { + let creators = response.authors.map({ CreatorResponse(creatorType: "author", firstName: $0.firstName, lastName: $0.lastName) }) + var fields: [KeyBaseKeyPair: String] = [:] + fields[KeyBaseKeyPair(key: FieldKeys.Item.title, baseKey: nil)] = title + fields[KeyBaseKeyPair(key: FieldKeys.Item.abstract, baseKey: nil)] = response.abstract + fields[KeyBaseKeyPair(key: FieldKeys.Item.date, baseKey: nil)] = response.year + fields[KeyBaseKeyPair(key: FieldKeys.Item.pages, baseKey: nil)] = response.pages + fields[KeyBaseKeyPair(key: FieldKeys.Item.volume, baseKey: nil)] = response.volume + fields[KeyBaseKeyPair(key: FieldKeys.Item.url, baseKey: nil)] = response.url + fields[KeyBaseKeyPair(key: FieldKeys.Item.language, baseKey: nil)] = response.language + let rawType: String + if response.type == "book-chapter" { + rawType = "bookSection" + fields[KeyBaseKeyPair(key: FieldKeys.Item.bookTitle, baseKey: nil)] = response.container + fields[KeyBaseKeyPair(key: FieldKeys.Item.publisher, baseKey: nil)] = response.publisher + } else { + rawType = "journalArticle" + fields[KeyBaseKeyPair(key: FieldKeys.Item.issue, baseKey: nil)] = response.issue + fields[KeyBaseKeyPair(key: FieldKeys.Item.issn, baseKey: nil)] = response.issn + fields[KeyBaseKeyPair(key: FieldKeys.Item.publicationTitle, baseKey: nil)] = response.container + } + let accessDate = Date() + + let itemResponse = ItemResponse( + rawType: rawType, + key: KeyGenerator.newKey, + library: LibraryResponse(id: 0, name: "", type: "", links: nil), + parentKey: nil, + collectionKeys: [], + links: nil, + parsedDate: nil, + isTrash: false, + version: 0, + dateModified: accessDate, + dateAdded: accessDate, + fields: fields, + tags: [], + creators: creators, + relations: [:], + createdBy: nil, + lastModifiedBy: nil, + rects: nil, + paths: nil + ) + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .translated(item: itemResponse)))) + } + return + } + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(.remoteRecognizerFailed)))) + } + } + } + + private func startIdentifierLookup(for task: RecognizerTask, with identifier: String, response: RemoteRecognizerResponse, copyTagsAsAutomatic: Bool) { + accessQueue.async(flags: .barrier) { [weak self] in + DDLogInfo("RecognizerController: starting identifier lookup - \(identifier)") + guard let self else { return } + guard let (_, observable) = queue[task] else { + startRecognitionIfNeeded() + return + } + var lookupWebViewHandler = lookupWebViewHandlersByRecognizerTask[task] + if lookupWebViewHandler == nil { + DispatchQueue.main.sync { [weak webViewProvider] in + guard let webViewProvider else { return } + let webView = webViewProvider.addWebView(configuration: nil) + lookupWebViewHandler = LookupWebViewHandler(webView: webView, translatorsController: self.translatorsController) + } + } + guard let lookupWebViewHandler else { + DDLogError("RecognizerController: can't create LookupWebViewHandler instance") + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(.cantCreateLookupWebViewHandler)))) + } + return + } + lookupWebViewHandlersByRecognizerTask[task] = lookupWebViewHandler + setupObserver(for: lookupWebViewHandler) + queue[task] = (.identifierLookupInProgress, observable) + observable.on(.next(Update(task: task, kind: .identifierLookupInProgress(response: response)))) + lookupWebViewHandler.lookUp(identifier: identifier) + + func setupObserver(for lookupWebViewHandler: LookupWebViewHandler) { + lookupWebViewHandler.observable + .subscribe(onNext: { process(result: $0) }) + .disposed(by: disposeBag) + + func process(result: Result) { + switch result { + case .success(let data): + switch data { + case .identifiers(let identifiers): + if identifiers.isEmpty { + DDLogError("RecognizerController: identifier not accepted by LookupWebViewHandler") + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(.identifierNotAccepted)))) + } + } + + case .item(let data): + guard data["identifier"] as? [String: String] != nil else { + DDLogWarn("RecognizerController: lookup item data don't contain identifier") + return + } + guard data.count > 1 else { return } + if let error = data["error"] { + DDLogError("RecognizerController: lookup failed - \(error)") + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(.lookupFailed)))) + } + return + } + guard let itemData = data["data"] as? [[String: Any]], + let item = itemData.first.flatMap({ + var item = $0 + item[FieldKeys.Item.abstract] = item[FieldKeys.Item.abstract] ?? response.abstract + item[FieldKeys.Item.language] = item[FieldKeys.Item.language] ?? response.language + return item + }), + var itemResponse = try? ItemResponse(translatorResponse: item, schemaController: schemaController) + else { + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(.parseFailed)))) + } + return + } + if copyTagsAsAutomatic, !itemResponse.tags.isEmpty { + itemResponse = itemResponse.copyWithAutomaticTags + } + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .translated(item: itemResponse)))) + } + } + + case .failure(let error): + DDLogError("RecognizerController: identifier lookup failed - \(error)") + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(error as! RecognizerController.Error)))) + } + } + } + } + } + } + + private func cleanupTask(for task: RecognizerTask, completion: @escaping (_ observable: PublishSubject?) -> Void) { + if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { + cleanup(for: task, completion: completion) + } else { + accessQueue.async(flags: .barrier) { + cleanup(for: task, completion: completion) + } + } + + func cleanup(for task: RecognizerTask, completion: @escaping (_ observable: PublishSubject?) -> Void) { + let observable = queue.removeValue(forKey: task).flatMap({ $0.observable }) + DDLogInfo("RecognizerController: cleaned up for \(task)") + if let webView = lookupWebViewHandlersByRecognizerTask.removeValue(forKey: task)?.webViewHandler.webView { + DispatchQueue.main.async { + webView.removeFromSuperview() + } + } + completion(observable) + startRecognitionIfNeeded() + } + } +} diff --git a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift new file mode 100644 index 000000000..27cd4529e --- /dev/null +++ b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift @@ -0,0 +1,251 @@ +// +// PDFWorkerWebViewHandler.swift +// Zotero +// +// Created by Miltiadis Vasilakis on 24/1/25. +// Copyright © 2025 Corporation for Digital Scholarship. All rights reserved. +// + +import Foundation +import WebKit + +import CocoaLumberjackSwift +import RxCocoa +import RxSwift + +final class PDFWorkerWebViewHandler { + /// Handlers for communication with JS in `webView` + enum JSHandlers: String, CaseIterable { + /// Handler used for reporting recognizer data. + case recognizerData = "recognizerDataHandler" + /// Handler used for reporting full text. + case fullText = "fullTextHandler" + /// Handler used to log JS debug info. + case log = "logHandler" + } + + enum PDFWorkerData { + case recognizerData(data: [String: Any]) + case fullText(data: [String: Any]) + } + + private enum InitializationResult { + case initialized + case inProgress + case failed(Swift.Error) + } + + let webViewHandler: WebViewHandler + private let disposeBag: DisposeBag + let observable: PublishSubject> + + private var isLoading: BehaviorRelay + + init(webView: WKWebView) { + webViewHandler = WebViewHandler(webView: webView, javascriptHandlers: JSHandlers.allCases.map({ $0.rawValue })) + observable = PublishSubject() + disposeBag = DisposeBag() + isLoading = BehaviorRelay(value: .inProgress) + + webViewHandler.receivedMessageHandler = { [weak self] name, body in + self?.receiveMessage(name: name, body: body) + } + + initialize() + .subscribe(on: MainScheduler.instance) + .observe(on: MainScheduler.instance) + .subscribe(onSuccess: { [weak self] _ in + DDLogInfo("PDFWorkerWebViewHandler: initialization succeeded") + self?.isLoading.accept(.initialized) + }, onFailure: { [weak self] error in + DDLogInfo("PDFWorkerWebViewHandler: initialization failed - \(error)") + self?.isLoading.accept(.failed(error)) + }) + .disposed(by: disposeBag) + + func initialize() -> Single { + DDLogInfo("PDFWorkerWebViewHandler: initialize web view") + let baseURL = Bundle.main.bundleURL.appendingPathComponent("Bundled/pdf_worker", isDirectory: true) + return webViewHandler.loadHTMLString(Self.htmlString, baseURL: baseURL) + .flatMap { _ in + Single.just(Void()) + } + } + } + + private func performCall(completion: @escaping () -> Void) { + switch isLoading.value { + case .failed(let error): + observable.on(.next(.failure(error))) + + case .initialized: + completion() + + case .inProgress: + isLoading.filter { result in + switch result { + case .inProgress: + return false + + case .initialized, .failed: + return true + } + } + .first() + .subscribe(onSuccess: { [weak self] result in + guard let self, let result else { return } + switch result { + case .failed(let error): + observable.on(.next(.failure(error))) + + case .initialized: + completion() + + case .inProgress: + break + } + }) + .disposed(by: disposeBag) + } + } + + func recognize(file: FileData) { + let filePath = file.createUrl().path + performCall { + performRecognize(for: filePath) + } + + func performRecognize(for path: String) { + DDLogInfo("PDFWorkerWebViewHandler: call recognize js") + return webViewHandler.call(javascript: "recognize('\(path)');") + .subscribe(on: MainScheduler.instance) + .observe(on: MainScheduler.instance) + .subscribe(onFailure: { [weak self] error in + DDLogError("PDFWorkerWebViewHandler: recognize failed - \(error)") + self?.observable.on(.next(.failure(error))) + }) + .disposed(by: disposeBag) + } + } + + func getFullText(file: FileData) { + let filePath = file.createUrl().path + performCall { + performGetFullText(for: filePath) + } + + func performGetFullText(for path: String) { + DDLogInfo("PDFWorkerWebViewHandler: call getFullText js") + return webViewHandler.call(javascript: "getFullText('\(path)');") + .subscribe(on: MainScheduler.instance) + .observe(on: MainScheduler.instance) + .subscribe(onFailure: { [weak self] error in + DDLogError("PDFWorkerWebViewHandler: getFullText failed - \(error)") + self?.observable.on(.next(.failure(error))) + }) + .disposed(by: disposeBag) + } + } + + /// Communication with JS in `webView`. The `webView` sends a message through one of the registered `JSHandlers`, which is received here. + /// Each message contains a `messageId` in the body, which is used to identify the message in case a response is expected. + private func receiveMessage(name: String, body: Any) { + guard let handler = JSHandlers(rawValue: name) else { return } + + switch handler { + case .recognizerData: + guard let data = (body as? [String: Any])?["recognizerData"] as? [String: Any] else { return } + observable.on(.next(.success(.recognizerData(data: data)))) + + case .fullText: + guard let data = (body as? [String: Any])?["fullText"] as? [String: Any] else { return } + observable.on(.next(.success(.recognizerData(data: data)))) + + case .log: + DDLogInfo("JSLOG: \(body)") + } + } + + static let htmlString: String = """ + + + + + PDF Worker + + + + + +""" +} diff --git a/Zotero/Controllers/Web View Handling/WebViewHandler.swift b/Zotero/Controllers/Web View Handling/WebViewHandler.swift index 72f43252a..73bfbfd94 100644 --- a/Zotero/Controllers/Web View Handling/WebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/WebViewHandler.swift @@ -12,6 +12,10 @@ import WebKit import CocoaLumberjackSwift import RxSwift +protocol WebViewProvider: AnyObject { + func addWebView(configuration: WKWebViewConfiguration?) -> WKWebView +} + final class WebViewHandler: NSObject { enum Error: Swift.Error { case webViewMissing @@ -51,6 +55,10 @@ final class WebViewHandler: NSObject { javascriptHandlers?.forEach { handler in webView.configuration.userContentController.add(self, name: handler) } + +#if DEBUG + webView.isInspectable = true +#endif } // MARK: - Actions @@ -70,6 +78,15 @@ final class WebViewHandler: NSObject { return createWebLoadedSingle() } + func loadHTMLString(_ string: String, baseURL: URL?) -> Single<()> { + guard let webView else { + DDLogError("WebViewHandler: web view is nil") + return .error(Error.webViewMissing) + } + webView.loadHTMLString(string, baseURL: baseURL) + return createWebLoadedSingle() + } + func load(webUrl: URL) -> Single<()> { guard let webView else { DDLogError("WebViewHandler: web view is nil") diff --git a/Zotero/Extensions/Localizable.swift b/Zotero/Extensions/Localizable.swift index 1a3ad60a1..ae64ee23a 100644 --- a/Zotero/Extensions/Localizable.swift +++ b/Zotero/Extensions/Localizable.swift @@ -874,6 +874,8 @@ internal enum L10n { internal static let removeDownload = L10n.tr("Localizable", "items.action.remove_download", fallback: "Remove Download") /// Remove from Collection internal static let removeFromCollection = L10n.tr("Localizable", "items.action.remove_from_collection", fallback: "Remove from Collection") + /// Retrieve Metadata + internal static let retrieveMetadata = L10n.tr("Localizable", "items.action.retrieve_metadata", fallback: "Retrieve Metadata") } internal enum CreatorSummary { /// %@ and %@ diff --git a/Zotero/Models/API/ItemResponse.swift b/Zotero/Models/API/ItemResponse.swift index 57e8882bb..7d53884fd 100644 --- a/Zotero/Models/API/ItemResponse.swift +++ b/Zotero/Models/API/ItemResponse.swift @@ -508,6 +508,13 @@ struct CreatorResponse { let lastName: String? let name: String? + init(creatorType: String, firstName: String? = nil, lastName: String? = nil, name: String? = nil) { + self.creatorType = creatorType + self.firstName = firstName + self.lastName = lastName + self.name = name + } + init(response: [String: Any]) throws { self.creatorType = try response.apiGet(key: "creatorType", caller: Self.self) self.firstName = response["firstName"] as? String diff --git a/Zotero/Models/FieldKeys.swift b/Zotero/Models/FieldKeys.swift index c8e8c9b8a..e8baa52ea 100644 --- a/Zotero/Models/FieldKeys.swift +++ b/Zotero/Models/FieldKeys.swift @@ -28,6 +28,12 @@ struct FieldKeys { static let url = "url" static let accessDate = "accessDate" static let extra = "extra" + static let language = "language" + static let pages = "pages" + static let volume = "volume" + static let issue = "issue" + static let issn = "issn" + static let bookTitle = "bookTitle" struct Attachment { static let linkMode = "linkMode" diff --git a/Zotero/Scenes/Detail/Items/Models/ItemAction.swift b/Zotero/Scenes/Detail/Items/Models/ItemAction.swift index 595dde88b..80f4022ac 100644 --- a/Zotero/Scenes/Detail/Items/Models/ItemAction.swift +++ b/Zotero/Scenes/Detail/Items/Models/ItemAction.swift @@ -10,7 +10,7 @@ import UIKit struct ItemAction { enum Kind { - case addToCollection, delete, duplicate, removeFromCollection, restore, trash, sort, filter, createParent, copyCitation, copyBibliography, share, download, removeDownload + case addToCollection, delete, duplicate, removeFromCollection, restore, trash, sort, filter, createParent, retrieveMetadata, copyCitation, copyBibliography, share, download, removeDownload } private enum Image { @@ -33,7 +33,7 @@ struct ItemAction { var isDestructive: Bool { switch self.type { case .delete, .trash: return true - case .addToCollection, .duplicate, .removeFromCollection, .restore, .sort, .filter, .createParent, .copyCitation, .copyBibliography, .share, .download, .removeDownload: return false + case .addToCollection, .duplicate, .removeFromCollection, .restore, .sort, .filter, .createParent, .retrieveMetadata, .copyCitation, .copyBibliography, .share, .download, .removeDownload: return false } } @@ -69,6 +69,10 @@ struct ItemAction { self.title = L10n.Items.Action.createParent self._image = .system("plus") + case .retrieveMetadata: + self.title = L10n.Items.Action.retrieveMetadata + self._image = .system("sparkle.magnifyingglass") + case .sort: self.title = "" self._image = .system("arrow.up.arrow.down") diff --git a/Zotero/Scenes/Detail/Items/ViewModels/ItemsToolbarController.swift b/Zotero/Scenes/Detail/Items/ViewModels/ItemsToolbarController.swift index 822ea42be..5aaad66fc 100644 --- a/Zotero/Scenes/Detail/Items/ViewModels/ItemsToolbarController.swift +++ b/Zotero/Scenes/Detail/Items/ViewModels/ItemsToolbarController.swift @@ -105,7 +105,7 @@ final class ItemsToolbarController { case .addToCollection, .trash, .delete, .removeFromCollection, .restore, .share, .download, .removeDownload: item.tag = ToolbarItem.empty.tag - case .sort, .filter, .createParent, .copyCitation, .copyBibliography, .duplicate: + case .sort, .filter, .createParent, .retrieveMetadata, .copyCitation, .copyBibliography, .duplicate: break } switch action.type { @@ -133,7 +133,7 @@ final class ItemsToolbarController { case .removeDownload: item.accessibilityLabel = L10n.Accessibility.Items.removeDownloads - case .sort, .filter, .createParent, .copyCitation, .copyBibliography, .duplicate: + case .sort, .filter, .createParent, .retrieveMetadata, .copyCitation, .copyBibliography, .duplicate: break } item.rx.tap.subscribe(onNext: { [weak self] _ in diff --git a/Zotero/Scenes/Detail/Items/Views/ItemsTableViewHandler.swift b/Zotero/Scenes/Detail/Items/Views/ItemsTableViewHandler.swift index f5784e551..2ec89c874 100644 --- a/Zotero/Scenes/Detail/Items/Views/ItemsTableViewHandler.swift +++ b/Zotero/Scenes/Detail/Items/Views/ItemsTableViewHandler.swift @@ -128,7 +128,7 @@ final class ItemsTableViewHandler: NSObject { case .duplicate, .restore: contextualAction.backgroundColor = .systemBlue - case .addToCollection, .createParent: + case .addToCollection, .createParent, .retrieveMetadata: contextualAction.backgroundColor = .systemOrange case .removeFromCollection: diff --git a/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift b/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift index 323bbcf60..4f48d1b1f 100644 --- a/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift +++ b/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift @@ -163,6 +163,23 @@ final class ItemsViewController: BaseItemsViewController { animated: true ) + case .retrieveMetadata: + guard let key = selectedKeys.first, + case .attachment(let attachment, let parentKey) = viewModel.state.itemAccessories[key], + parentKey == nil, + let file = attachment.file as? FileData, + file.mimeType == "application/pdf", + let recognizerController = controllers.userControllers?.recognizerController + else { return } + recognizerController.queue(task: RecognizerController.RecognizerTask(file: file)) { [weak self] observable in + guard let self else { return } + observable?.subscribe { update in + // TODO: Implement + print("Recognizer controller update: \(update)") + } + .disposed(by: disposeBag) + } + case .duplicate: guard let key = selectedKeys.first else { return } viewModel.process(action: .loadItemToDuplicate(key)) diff --git a/Zotero/Scenes/Detail/Items/Views/RItemsTableViewDataSource.swift b/Zotero/Scenes/Detail/Items/Views/RItemsTableViewDataSource.swift index 5558613bd..859c4ca9a 100644 --- a/Zotero/Scenes/Detail/Items/Views/RItemsTableViewDataSource.swift +++ b/Zotero/Scenes/Detail/Items/Views/RItemsTableViewDataSource.swift @@ -141,27 +141,37 @@ extension RItemsTableViewDataSource: ItemsTableViewDataSource { actions.append(contentsOf: [ItemAction(type: .copyCitation), ItemAction(type: .copyBibliography), ItemAction(type: .share)]) } + let attachment = accessory(forKey: item.key)?.attachment + let location = attachment?.location + // Add parent creation for standalone attachments if item.rawType == ItemTypes.attachment && item.parent == nil { actions.append(ItemAction(type: .createParent)) + if attachment?.file?.mimeType == "application/pdf" { + switch location { + case .local, .localAndChangedRemotely: + actions.append(ItemAction(type: .retrieveMetadata)) + + case .none, .remote, .remoteMissing: + break + } + } } // Add download/remove downloaded option for attachments - if let accessory = accessory(forKey: item.key), let location = accessory.attachment?.location { - switch location { - case .local: - actions.append(ItemAction(type: .removeDownload)) + switch location { + case .local: + actions.append(ItemAction(type: .removeDownload)) - case .remote: - actions.append(ItemAction(type: .download)) + case .remote: + actions.append(ItemAction(type: .download)) - case .localAndChangedRemotely: - actions.append(ItemAction(type: .download)) - actions.append(ItemAction(type: .removeDownload)) + case .localAndChangedRemotely: + actions.append(ItemAction(type: .download)) + actions.append(ItemAction(type: .removeDownload)) - case .remoteMissing: - break - } + case .none, .remoteMissing: + break } guard viewModel.state.library.metadataEditable else { return actions } diff --git a/Zotero/Scenes/Detail/Trash/Views/TrashViewController.swift b/Zotero/Scenes/Detail/Trash/Views/TrashViewController.swift index f8c5f0ed7..87cad44a8 100644 --- a/Zotero/Scenes/Detail/Trash/Views/TrashViewController.swift +++ b/Zotero/Scenes/Detail/Trash/Views/TrashViewController.swift @@ -146,7 +146,7 @@ final class TrashViewController: BaseItemsViewController { private func process(action: ItemAction.Kind, for selectedKeys: Set, button: UIBarButtonItem?, completionAction: ((Bool) -> Void)?) { switch action { - case .createParent, .duplicate, .trash, .copyBibliography, .copyCitation, .share, .addToCollection, .removeFromCollection: + case .createParent, .retrieveMetadata, .duplicate, .trash, .copyBibliography, .copyCitation, .share, .addToCollection, .removeFromCollection: // These actions are not available in trash collection break diff --git a/Zotero/Scenes/Master/Libraries/Views/LibrariesViewController.swift b/Zotero/Scenes/Master/Libraries/Views/LibrariesViewController.swift index 1f500ab7a..cfbadd5aa 100644 --- a/Zotero/Scenes/Master/Libraries/Views/LibrariesViewController.swift +++ b/Zotero/Scenes/Master/Libraries/Views/LibrariesViewController.swift @@ -20,6 +20,8 @@ final class LibrariesViewController: UIViewController { private let viewModel: ViewModel private unowned let syncScheduler: SynchronizationScheduler private unowned let identifierLookupController: IdentifierLookupController + private unowned let pdfWorkerController: PDFWorkerController + private unowned let recognizerController: RecognizerController private let disposeBag: DisposeBag private var refreshController: SyncRefreshController? @@ -30,14 +32,24 @@ final class LibrariesViewController: UIViewController { // MARK: - Lifecycle - init(viewModel: ViewModel, syncScheduler: SynchronizationScheduler, identifierLookupController: IdentifierLookupController) { + init( + viewModel: ViewModel, + syncScheduler: SynchronizationScheduler, + identifierLookupController: IdentifierLookupController, + pdfWorkerController: PDFWorkerController, + recognizerController: RecognizerController + ) { self.viewModel = viewModel self.syncScheduler = syncScheduler self.identifierLookupController = identifierLookupController + self.pdfWorkerController = pdfWorkerController + self.recognizerController = recognizerController self.disposeBag = DisposeBag() super.init(nibName: "LibrariesViewController", bundle: nil) identifierLookupController.webViewProvider = self + pdfWorkerController.webViewProvider = self + recognizerController.webViewProvider = self } required init?(coder: NSCoder) { @@ -232,9 +244,9 @@ extension LibrariesViewController: UITableViewDataSource, UITableViewDelegate { extension LibrariesViewController: BottomSheetObserver { } -extension LibrariesViewController: IdentifierLookupWebViewProvider { - func addWebView() -> WKWebView { - let webView = WKWebView() +extension LibrariesViewController: WebViewProvider { + func addWebView(configuration: WKWebViewConfiguration?) -> WKWebView { + let webView: WKWebView = configuration.flatMap({ WKWebView(frame: .zero, configuration: $0) }) ?? WKWebView() webView.isHidden = true view.insertSubview(webView, at: 0) return webView diff --git a/Zotero/Scenes/Master/MasterCoordinator.swift b/Zotero/Scenes/Master/MasterCoordinator.swift index e2a925ff5..915e3f621 100644 --- a/Zotero/Scenes/Master/MasterCoordinator.swift +++ b/Zotero/Scenes/Master/MasterCoordinator.swift @@ -59,7 +59,9 @@ final class MasterCoordinator: NSObject, Coordinator { let librariesController = self.createLibrariesViewController( dbStorage: userControllers.dbStorage, syncScheduler: userControllers.syncScheduler, - identifierLookupController: userControllers.identifierLookupController + identifierLookupController: userControllers.identifierLookupController, + pdfWorkerController: userControllers.pdfWorkerController, + recognizerController: userControllers.recognizerController ) let collectionsController = self.createCollectionsViewController( libraryId: self.visibleLibraryId, @@ -72,9 +74,21 @@ final class MasterCoordinator: NSObject, Coordinator { self.navigationController?.setViewControllers([librariesController, collectionsController], animated: animated) } - private func createLibrariesViewController(dbStorage: DbStorage, syncScheduler: SynchronizationScheduler, identifierLookupController: IdentifierLookupController) -> UIViewController { + private func createLibrariesViewController( + dbStorage: DbStorage, + syncScheduler: SynchronizationScheduler, + identifierLookupController: IdentifierLookupController, + pdfWorkerController: PDFWorkerController, + recognizerController: RecognizerController + ) -> UIViewController { let viewModel = ViewModel(initialState: LibrariesState(), handler: LibrariesActionHandler(dbStorage: dbStorage)) - let controller = LibrariesViewController(viewModel: viewModel, syncScheduler: syncScheduler, identifierLookupController: identifierLookupController) + let controller = LibrariesViewController( + viewModel: viewModel, + syncScheduler: syncScheduler, + identifierLookupController: identifierLookupController, + pdfWorkerController: pdfWorkerController, + recognizerController: recognizerController + ) controller.coordinatorDelegate = self return controller } From 01413d8e9e90aaa09e445dbfec5f37ae4f7f7a4a Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Thu, 13 Feb 2025 16:29:31 +0200 Subject: [PATCH 03/33] Use recognizer controller when sharing a local PDF file --- .../ShareViewController.swift | 38 ++++++++- ZShare/ViewModels/ExtensionViewModel.swift | 78 ++++++++++++++++--- Zotero.xcodeproj/project.pbxproj | 14 ++++ 3 files changed, 115 insertions(+), 15 deletions(-) diff --git a/ZShare/View Controllers/ShareViewController.swift b/ZShare/View Controllers/ShareViewController.swift index e22c1ff77..4ce868b6b 100644 --- a/ZShare/View Controllers/ShareViewController.swift +++ b/ZShare/View Controllers/ShareViewController.swift @@ -67,6 +67,7 @@ final class ShareViewController: UIViewController { private var fileStorage: FileStorage! private var debugLogging: DebugLogging! private var schemaController: SchemaController! + private var pdfWorkerController: PDFWorkerController! private var secureStorage: KeychainSecureStorage! private var viewModel: ExtensionViewModel! private var storeCancellable: AnyCancellable? @@ -714,15 +715,18 @@ final class ShareViewController: UIViewController { let translatorsController = TranslatorsAndStylesController(apiClient: apiClient, bundledDataStorage: bundledDataStorage, fileStorage: fileStorage) let secureStorage = KeychainSecureStorage() let webDavController = WebDavControllerImpl(dbStorage: dbStorage, fileStorage: fileStorage, sessionStorage: SecureWebDavSessionStorage(secureStorage: secureStorage)) + let pdfWorkerController = PDFWorkerController() apiClient.set(authToken: ("Bearer " + session.apiToken)) translatorsController.updateFromRepo(type: .shareExtension) + pdfWorkerController.webViewProvider = self self.fileStorage = fileStorage self.schemaController = schemaController self.dbStorage = dbStorage self.bundledDataStorage = bundledDataStorage self.translatorsController = translatorsController + self.pdfWorkerController = pdfWorkerController self.secureStorage = secureStorage self.viewModel = self.createViewModel(for: session.userId, dbStorage: dbStorage, apiClient: apiClient, schemaController: schemaController, fileStorage: fileStorage, @@ -741,9 +745,37 @@ final class ShareViewController: UIViewController { let attachmentDownloader = AttachmentDownloader(userId: userId, apiClient: apiClient, fileStorage: fileStorage, dbStorage: dbStorage, webDavController: webDavController) let syncController = SyncController(userId: userId, apiClient: apiClient, dbStorage: dbStorage, fileStorage: fileStorage, schemaController: schemaController, dateParser: dateParser, backgroundUploaderContext: backgroundUploadContext, webDavController: webDavController, attachmentDownloader: attachmentDownloader, syncDelayIntervals: DelayIntervals.sync, maxRetryCount: DelayIntervals.retry.count) + let recognizerController = RecognizerController( + pdfWorkerController: pdfWorkerController, + apiClient: apiClient, + translatorsController: translatorsController, + schemaController: schemaController + ) + recognizerController.webViewProvider = self + + return ExtensionViewModel( + webView: webView, + apiClient: apiClient, + attachmentDownloader: attachmentDownloader, + backgroundUploader: backgroundUploader, + backgroundUploadObserver: backgroundUploadObserver, + dbStorage: dbStorage, + schemaController: schemaController, + webDavController: webDavController, + dateParser: dateParser, + fileStorage: fileStorage, + syncController: syncController, + translatorsController: translatorsController, + recognizerController: recognizerController + ) + } +} - return ExtensionViewModel(webView: self.webView, apiClient: apiClient, attachmentDownloader: attachmentDownloader, backgroundUploader: backgroundUploader, backgroundUploadObserver: backgroundUploadObserver, dbStorage: dbStorage, - schemaController: schemaController, webDavController: webDavController, dateParser: dateParser, fileStorage: fileStorage, syncController: syncController, - translatorsController: translatorsController) +extension ShareViewController: WebViewProvider { + func addWebView(configuration: WKWebViewConfiguration?) -> WKWebView { + let webView: WKWebView = configuration.flatMap({ WKWebView(frame: .zero, configuration: $0) }) ?? WKWebView() + webView.isHidden = true + view.insertSubview(webView, at: 0) + return webView } } diff --git a/ZShare/ViewModels/ExtensionViewModel.swift b/ZShare/ViewModels/ExtensionViewModel.swift index 1a4cf3809..a8bc802a2 100644 --- a/ZShare/ViewModels/ExtensionViewModel.swift +++ b/ZShare/ViewModels/ExtensionViewModel.swift @@ -315,6 +315,7 @@ final class ExtensionViewModel { private let backgroundUploadObserver: BackgroundUploadObserver private let backgroundQueue: DispatchQueue private let backgroundScheduler: SerialDispatchQueueScheduler + private let recognizerController: RecognizerController private let disposeBag: DisposeBag // Custom `URLSession` has to be used for downloading, instead of existing `apiClient`, so that we can include original cookies in download requests. private let downloadUrlSession: URLSession @@ -325,8 +326,21 @@ final class ExtensionViewModel { let mtime: Int } - init(webView: WKWebView, apiClient: ApiClient, attachmentDownloader: AttachmentDownloader, backgroundUploader: BackgroundUploader, backgroundUploadObserver: BackgroundUploadObserver, dbStorage: DbStorage, schemaController: SchemaController, - webDavController: WebDavController, dateParser: DateParser, fileStorage: FileStorage, syncController: SyncController, translatorsController: TranslatorsAndStylesController) { + init( + webView: WKWebView, + apiClient: ApiClient, + attachmentDownloader: AttachmentDownloader, + backgroundUploader: BackgroundUploader, + backgroundUploadObserver: BackgroundUploadObserver, + dbStorage: DbStorage, + schemaController: SchemaController, + webDavController: WebDavController, + dateParser: DateParser, + fileStorage: FileStorage, + syncController: SyncController, + translatorsController: TranslatorsAndStylesController, + recognizerController: RecognizerController + ) { let queue = DispatchQueue(label: "org.zotero.ZShare.BackgroundQueue", qos: .userInteractive) let storage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: AppGroup.identifier) @@ -355,6 +369,7 @@ final class ExtensionViewModel { self.translationHandler = TranslationWebViewHandler(webView: webView, translatorsController: translatorsController) self.backgroundQueue = queue self.backgroundScheduler = SerialDispatchQueueScheduler(queue: queue, internalSerialQueueName: "org.zotero.ZShare.BackgroundScheduler") + self.recognizerController = recognizerController self.state = State() self.disposeBag = DisposeBag() @@ -501,17 +516,56 @@ final class ExtensionViewModel { let filename = url.lastPathComponent let tmpFile = Files.temporaryFile(ext: url.pathExtension) - self.copyFile(from: url.path, to: tmpFile) - .subscribe(with: self, onSuccess: { `self`, _ in - var state = self.state - state.processedAttachment = .file(file: tmpFile, filename: filename) - state.expectedAttachment = (filename, tmpFile) - state.attachmentState = .processed - self.state = state - }, onFailure: { `self`, _ in - self.state.attachmentState = .failed(.fileMissing) + copyFile(from: url.path, to: tmpFile) + .subscribe(onSuccess: { [weak self] _ in + guard let self else { return } + guard fileStorage.isPdf(file: tmpFile), let file = tmpFile as? FileData else { + updateState(with: .processed) + return + } + recognize(file: file) + }, onFailure: { [weak self] _ in + self?.state.attachmentState = .failed(.fileMissing) }) - .disposed(by: self.disposeBag) + .disposed(by: disposeBag) + + func updateState(with attachmentState: State.AttachmentState) { + var state = self.state + state.processedAttachment = .file(file: tmpFile, filename: filename) + state.expectedAttachment = (filename, tmpFile) + state.attachmentState = attachmentState + self.state = state + } + + func recognize(file: FileData) { + recognizerController.queue(task: RecognizerController.RecognizerTask(file: file)) { [weak self] observable in + guard let self else { return } + observable?.subscribe { [weak self] update in + guard let self else { return } + switch update.kind { + case .failed(let error): + DDLogError("ExtensionViewModel: could not recognize shared file - \(error)") + updateState(with: .processed) + + case .cancelled: + updateState(with: .processed) + + case .recognitionInProgress, .remoteRecognitionInProgress, .identifierLookupInProgress: + updateState(with: .decoding) + + case .translated(item: let item): + var state = self.state + state.expectedItem = item + state.expectedAttachment = (filename, tmpFile) + state.attachmentState = .processed + // attachment is only used to get an optional URL, so we can safely pass an empty dictionary + state.processedAttachment = .itemWithAttachment(item: item, attachment: [:], attachmentFile: file) + self.state = state + } + } + .disposed(by: disposeBag) + } + } } private func process(remoteFileUrl url: URL, contentType: String, cookies: String, userAgent: String, referrer: String) { diff --git a/Zotero.xcodeproj/project.pbxproj b/Zotero.xcodeproj/project.pbxproj index 200ef6640..a0d87dc5f 100644 --- a/Zotero.xcodeproj/project.pbxproj +++ b/Zotero.xcodeproj/project.pbxproj @@ -39,6 +39,10 @@ 614D65832A79C9B0007CF449 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 614D65802A79C9AC007CF449 /* Localizable.stringsdict */; }; 614D65872A8030C9007CF449 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 614D65862A8030C9007CF449 /* OrderedCollections */; }; 615C10E12D5BA17A001F1E8E /* RecognizerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615C10E02D5BA17A001F1E8E /* RecognizerController.swift */; }; + 615C10E22D5E22F1001F1E8E /* RecognizerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615C10E02D5BA17A001F1E8E /* RecognizerController.swift */; }; + 615C10E32D5E230D001F1E8E /* PDFWorkerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61168F602D50D4B6005495E8 /* PDFWorkerController.swift */; }; + 615C10E42D5E3229001F1E8E /* LookupWebViewHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B323703F2C0DC65600170779 /* LookupWebViewHandler.swift */; }; + 615C10E62D5E329F001F1E8E /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 615C10E52D5E329F001F1E8E /* OrderedCollections */; }; 61639F852AE03B8500026003 /* InstantPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61639F842AE03B8500026003 /* InstantPresenter.swift */; }; 618404262A4456A9005AAF22 /* IdentifierLookupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618404252A4456A9005AAF22 /* IdentifierLookupController.swift */; }; 618D83E72BAAC88C00E7966B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 618D83E62BAAC88C00E7966B /* PrivacyInfo.xcprivacy */; }; @@ -2245,6 +2249,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 615C10E62D5E329F001F1E8E /* OrderedCollections in Frameworks */, B35C9C0C2642A0E40004C47E /* RealmSwift in Frameworks */, B356A33725248C9E003F1943 /* RxRelay in Frameworks */, B3F7B06A2524A78F00E51377 /* CocoaLumberjackSwift in Frameworks */, @@ -4444,6 +4449,7 @@ B3F7B0722524A8D500E51377 /* KeychainSwift */, B3F7B0742524A8D800E51377 /* ZIPFoundation */, B35C9C0B2642A0E40004C47E /* RealmSwift */, + 615C10E52D5E329F001F1E8E /* OrderedCollections */, ); productName = ZShare; productReference = B311B31C2386A39800BCF592 /* ZShare.appex */; @@ -5665,6 +5671,7 @@ B305676823FC0A0B003304F2 /* Predicates.swift in Sources */, B305676923FC0A0B003304F2 /* SortType.swift in Sources */, B305676A23FC0A0B003304F2 /* SyncableObject.swift in Sources */, + 615C10E32D5E230D001F1E8E /* PDFWorkerController.swift in Sources */, B3748D2026566F4800ED05F8 /* ContainerViewController.swift in Sources */, B305676B23FC0A0B003304F2 /* Tag.swift in Sources */, B305676C23FC0A0B003304F2 /* UpdatableObject.swift in Sources */, @@ -5692,6 +5699,7 @@ B3F0FDCB2888185D00949BC9 /* MarkFileAsDownloadedDbRequest.swift in Sources */, B305674623FC09AF003304F2 /* SearchesResponse.swift in Sources */, B305674723FC09AF003304F2 /* SchemaResponse.swift in Sources */, + 615C10E22D5E22F1001F1E8E /* RecognizerController.swift in Sources */, B300B3362429234C00C1FE1E /* TranslatorMetadata.swift in Sources */, B305674923FC09AF003304F2 /* RTag.swift in Sources */, B39C9AB7252B589F00462D27 /* Threading+Extras.swift in Sources */, @@ -5736,6 +5744,7 @@ B305671B23FC08DC003304F2 /* StoreSearchesDbRequest.swift in Sources */, B30566FE23FC0867003304F2 /* MarkGroupAsLocalOnlyDbRequest.swift in Sources */, B3AB38A9238EC060008A1ABB /* AllCollectionPickerActionHandler.swift in Sources */, + 615C10E42D5E3229001F1E8E /* LookupWebViewHandler.swift in Sources */, B305673923FC0936003304F2 /* SyncProgressHandler.swift in Sources */, B33E8A4427B6769A00CBC7DE /* CollectionTree.swift in Sources */, B305672323FC08F6003304F2 /* FileStorage.swift in Sources */, @@ -6788,6 +6797,11 @@ package = 614D65852A8030C9007CF449 /* XCRemoteSwiftPackageReference "swift-collections" */; productName = OrderedCollections; }; + 615C10E52D5E329F001F1E8E /* OrderedCollections */ = { + isa = XCSwiftPackageProductDependency; + package = 614D65852A8030C9007CF449 /* XCRemoteSwiftPackageReference "swift-collections" */; + productName = OrderedCollections; + }; B3445F4526EF5EE9007D4009 /* CrashReporter */ = { isa = XCSwiftPackageProductDependency; package = B3445F4426EF5EE9007D4009 /* XCRemoteSwiftPackageReference "plcrashreporter" */; From ad75e6604a387a0fbc387ae073bcea234a2de782 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Fri, 14 Feb 2025 16:17:10 +0200 Subject: [PATCH 04/33] Fix RecognizerController identifier lookup workflow --- Zotero/Controllers/RecognizerController.swift | 388 +++++++++++------- 1 file changed, 243 insertions(+), 145 deletions(-) diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index 79906c783..22879d454 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -15,6 +15,39 @@ import RxSwift final class RecognizerController { // MARK: Types + enum RemoteRecognizerIdentifier { + case arXiv(String) + case doi(String) + case isbn(String) + case title(String) + + var identifierWithPrefix: String { + switch self { + case .arXiv(let identifier): + return "arXiv: \(identifier)" + + case .doi(let identifier): + return "DOI: \(identifier)" + + case .isbn(let identifier): + return "ISBN: \(identifier)" + + case .title(let identifier): + return identifier + } + } + + var copyTagsAsAutomatic: Bool { + switch self { + case .isbn: + return true + + case .arXiv, .doi, .title: + return false + } + } + } + struct RemoteRecognizerResponse: Decodable { struct Author: Decodable { let firstName, lastName: String? @@ -48,10 +81,8 @@ final class RecognizerController { case pdfWorkerError case recognizerFailed case remoteRecognizerFailed - case cantCreateLookupWebViewHandler - case identifierNotAccepted - case lookupFailed - case parseFailed + case noRemainingIdentifiersForLookup + case unexpectedState } struct Update { @@ -60,7 +91,7 @@ final class RecognizerController { case cancelled case recognitionInProgress case remoteRecognitionInProgress(data: [String: Any]) - case identifierLookupInProgress(response: RemoteRecognizerResponse) + case identifierLookupInProgress(response: RemoteRecognizerResponse, identifier: String) case translated(item: ItemResponse) } @@ -71,8 +102,8 @@ final class RecognizerController { enum RecognizerTaskState { case enqueued case recognitionInProgress - case remoteRecognitionInProgress - case identifierLookupInProgress + case remoteRecognitionInProgress(data: [String: Any]) + case identifiersLookupInProgress(response: RemoteRecognizerResponse, currentIdentifier: RemoteRecognizerIdentifier, pendingIdentifiers: [RemoteRecognizerIdentifier]) } // MARK: Properties @@ -126,8 +157,16 @@ final class RecognizerController { } private func startRecognitionIfNeeded() { - let recognitionInProgressCount = queue.filter({ $0.value.state == .recognitionInProgress }).count - guard recognitionInProgressCount < Self.maxConcurrentRecognizerTasks else { return } + let runningRecognizerTasksCount = queue.filter({ + switch $0.value.state { + case .enqueued: + return false + + case .recognitionInProgress, .remoteRecognitionInProgress, .identifiersLookupInProgress: + return true + } + }).count + guard runningRecognizerTasksCount < Self.maxConcurrentRecognizerTasks else { return } let tasks = queue.keys for task in tasks { guard let (state, observable) = queue[task] else { continue } @@ -137,7 +176,7 @@ final class RecognizerController { startRecognitionIfNeeded() return - case .recognitionInProgress, .remoteRecognitionInProgress, .identifierLookupInProgress: + case .recognitionInProgress, .remoteRecognitionInProgress, .identifiersLookupInProgress: break } } @@ -149,7 +188,7 @@ final class RecognizerController { pdfWorkerController.queue(work: PDFWorkerController.PDFWork(file: task.file, kind: .recognizer)) { [weak self] pdfWorkerObservable in guard let self else { return } guard let pdfWorkerObservable else { - DDLogError("RecognizerController: can't create start PDF worker") + DDLogError("RecognizerController: \(task) - can't create start PDF worker") cleanupTask(for: task) { observable in observable?.on(.next(Update(task: task, kind: .failed(.cantStartPDFWorker)))) } @@ -163,7 +202,7 @@ final class RecognizerController { func process(update: PDFWorkerController.Update) { switch update.kind { case .failed: - DDLogError("RecognizerController: recognizer failed") + DDLogError("RecognizerController: \(task) - recognizer failed") cleanupTask(for: task) { observable in observable?.on(.next(Update(task: task, kind: .failed(.recognizerFailed)))) } @@ -177,11 +216,11 @@ final class RecognizerController { break case .extractedRecognizerData(data: let data): - DDLogInfo("RecognizerController: extracted recognizer data") + DDLogInfo("RecognizerController: \(task) - extracted recognizer data") startRemoteRecognition(for: task, with: data) case .extractedFullText: - DDLogError("RecognizerController: PDF worker error") + DDLogError("RecognizerController: \(task) - PDF worker error") cleanupTask(for: task) { observable in observable?.on(.next(Update(task: task, kind: .failed(.pdfWorkerError)))) } @@ -197,17 +236,16 @@ final class RecognizerController { startRecognitionIfNeeded() return } - queue[task] = (.remoteRecognitionInProgress, observable) + queue[task] = (.remoteRecognitionInProgress(data: data), observable) observable.on(.next(Update(task: task, kind: .remoteRecognitionInProgress(data: data)))) apiClient.send(request: RecognizerRequest(parameters: data)).subscribe( - onSuccess: { [weak self] (response: (RemoteRecognizerResponse, HTTPURLResponse)) in - DDLogInfo("RecognizerController: remote recognizer response received") - guard let self else { return } + onSuccess: { (response: (RemoteRecognizerResponse, HTTPURLResponse)) in + DDLogInfo("RecognizerController: \(task) - remote recognizer response received") process(response: response.0) }, onFailure: { [weak self] error in - DDLogError("RecognizerController: remote recognizer request failed: \(error)") + DDLogError("RecognizerController: \(task) - remote recognizer request failed: \(error)") self?.cleanupTask(for: task) { observable in observable?.on(.next(Update(task: task, kind: .failed(error as! Error)))) } @@ -217,165 +255,225 @@ final class RecognizerController { } func process(response: RemoteRecognizerResponse) { - var identifier: String? - var copyTagsAsAutomatic = false - if let arxiv = response.arxiv { - identifier = "arXiv: \(arxiv)" - } else if let doi = response.doi { - identifier = "DOI: \(doi)" - } else if let isbn = response.isbn { - identifier = "ISBN: \(isbn)" - copyTagsAsAutomatic = true + var identifiers: [RemoteRecognizerIdentifier] = [] + if let identifier = response.arxiv { + identifiers.append(.arXiv(identifier)) } - if let identifier { - startIdentifierLookup(for: task, with: identifier, response: response, copyTagsAsAutomatic: copyTagsAsAutomatic) - return + if let identifier = response.doi { + identifiers.append(.doi(identifier)) } - if let title = response.title { - let creators = response.authors.map({ CreatorResponse(creatorType: "author", firstName: $0.firstName, lastName: $0.lastName) }) - var fields: [KeyBaseKeyPair: String] = [:] - fields[KeyBaseKeyPair(key: FieldKeys.Item.title, baseKey: nil)] = title - fields[KeyBaseKeyPair(key: FieldKeys.Item.abstract, baseKey: nil)] = response.abstract - fields[KeyBaseKeyPair(key: FieldKeys.Item.date, baseKey: nil)] = response.year - fields[KeyBaseKeyPair(key: FieldKeys.Item.pages, baseKey: nil)] = response.pages - fields[KeyBaseKeyPair(key: FieldKeys.Item.volume, baseKey: nil)] = response.volume - fields[KeyBaseKeyPair(key: FieldKeys.Item.url, baseKey: nil)] = response.url - fields[KeyBaseKeyPair(key: FieldKeys.Item.language, baseKey: nil)] = response.language - let rawType: String - if response.type == "book-chapter" { - rawType = "bookSection" - fields[KeyBaseKeyPair(key: FieldKeys.Item.bookTitle, baseKey: nil)] = response.container - fields[KeyBaseKeyPair(key: FieldKeys.Item.publisher, baseKey: nil)] = response.publisher - } else { - rawType = "journalArticle" - fields[KeyBaseKeyPair(key: FieldKeys.Item.issue, baseKey: nil)] = response.issue - fields[KeyBaseKeyPair(key: FieldKeys.Item.issn, baseKey: nil)] = response.issn - fields[KeyBaseKeyPair(key: FieldKeys.Item.publicationTitle, baseKey: nil)] = response.container - } - let accessDate = Date() - - let itemResponse = ItemResponse( - rawType: rawType, - key: KeyGenerator.newKey, - library: LibraryResponse(id: 0, name: "", type: "", links: nil), - parentKey: nil, - collectionKeys: [], - links: nil, - parsedDate: nil, - isTrash: false, - version: 0, - dateModified: accessDate, - dateAdded: accessDate, - fields: fields, - tags: [], - creators: creators, - relations: [:], - createdBy: nil, - lastModifiedBy: nil, - rects: nil, - paths: nil - ) + if let identifier = response.isbn { + identifiers.append(.isbn(identifier)) + } + if let identifier = response.title { + identifiers.append(.title(identifier)) + } + guard !identifiers.isEmpty else { cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .translated(item: itemResponse)))) + observable?.on(.next(Update(task: task, kind: .failed(.remoteRecognizerFailed)))) } return } - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.remoteRecognizerFailed)))) - } + startIdentifiersLookup(for: task, with: response, pendingIdentifiers: identifiers) } } - private func startIdentifierLookup(for task: RecognizerTask, with identifier: String, response: RemoteRecognizerResponse, copyTagsAsAutomatic: Bool) { + private func startIdentifiersLookup(for task: RecognizerTask, with response: RemoteRecognizerResponse, pendingIdentifiers: [RemoteRecognizerIdentifier]) { accessQueue.async(flags: .barrier) { [weak self] in - DDLogInfo("RecognizerController: starting identifier lookup - \(identifier)") guard let self else { return } - guard let (_, observable) = queue[task] else { + guard let (state, _) = queue[task] else { startRecognitionIfNeeded() return } - var lookupWebViewHandler = lookupWebViewHandlersByRecognizerTask[task] - if lookupWebViewHandler == nil { - DispatchQueue.main.sync { [weak webViewProvider] in - guard let webViewProvider else { return } - let webView = webViewProvider.addWebView(configuration: nil) - lookupWebViewHandler = LookupWebViewHandler(webView: webView, translatorsController: self.translatorsController) + guard case .remoteRecognitionInProgress = state else { + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) } + return + } + lookupNextIdentifier(for: task, with: response, pendingIdentifiers: pendingIdentifiers) + } + } + + private func enqueueNextIdentifierLookup(for task: RecognizerTask) { + if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { + _enqueueNextIdentifierLookup(for: task) + } else { + accessQueue.async(flags: .barrier) { + _enqueueNextIdentifierLookup(for: task) + } + } + + func _enqueueNextIdentifierLookup(for task: RecognizerTask) { + guard let (state, _) = queue[task] else { + startRecognitionIfNeeded() + return } - guard let lookupWebViewHandler else { - DDLogError("RecognizerController: can't create LookupWebViewHandler instance") + guard case .identifiersLookupInProgress(let response, _, let pendingIdentifiers) = state else { cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.cantCreateLookupWebViewHandler)))) + observable?.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) } return } - lookupWebViewHandlersByRecognizerTask[task] = lookupWebViewHandler - setupObserver(for: lookupWebViewHandler) - queue[task] = (.identifierLookupInProgress, observable) - observable.on(.next(Update(task: task, kind: .identifierLookupInProgress(response: response)))) - lookupWebViewHandler.lookUp(identifier: identifier) + lookupNextIdentifier(for: task, with: response, pendingIdentifiers: pendingIdentifiers) + } + } - func setupObserver(for lookupWebViewHandler: LookupWebViewHandler) { - lookupWebViewHandler.observable - .subscribe(onNext: { process(result: $0) }) - .disposed(by: disposeBag) + private func lookupNextIdentifier(for task: RecognizerTask, with response: RemoteRecognizerResponse, pendingIdentifiers: [RemoteRecognizerIdentifier]) { + DDLogInfo("RecognizerController: \(task) - looking up next identifier from \(pendingIdentifiers)") + guard let (_, observable) = queue[task] else { + startRecognitionIfNeeded() + return + } + guard !pendingIdentifiers.isEmpty else { + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(.noRemainingIdentifiersForLookup)))) + } + return + } + var remainingIdentifiers = pendingIdentifiers + let identifier = remainingIdentifiers.removeFirst() + queue[task] = (.identifiersLookupInProgress(response: response, currentIdentifier: identifier, pendingIdentifiers: remainingIdentifiers), observable) - func process(result: Result) { - switch result { - case .success(let data): - switch data { - case .identifiers(let identifiers): - if identifiers.isEmpty { - DDLogError("RecognizerController: identifier not accepted by LookupWebViewHandler") - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.identifierNotAccepted)))) + switch identifier { + case .arXiv, .doi, .isbn: + lookup(identifier: identifier.identifierWithPrefix, copyTagsAsAutomatic: identifier.copyTagsAsAutomatic, remainingIdentifiers: remainingIdentifiers) + + case .title(let title): + use(title: title, with: response) + } + + func lookup(identifier: String, copyTagsAsAutomatic: Bool, remainingIdentifiers: [RemoteRecognizerIdentifier]) { + DDLogInfo("RecognizerController: \(task) - looking up identifier \(identifier)") + guard let lookupWebViewHandler = getLookupWebViewHandler(for: task) else { + enqueueNextIdentifierLookup(for: task) + return + } + observable.on(.next(Update(task: task, kind: .identifierLookupInProgress(response: response, identifier: identifier)))) + lookupWebViewHandler.lookUp(identifier: identifier) + + func getLookupWebViewHandler(for task: RecognizerTask) -> LookupWebViewHandler? { + if let lookupWebViewHandler = lookupWebViewHandlersByRecognizerTask[task] { + return lookupWebViewHandler + } + var lookupWebViewHandler: LookupWebViewHandler? + DispatchQueue.main.sync { [weak self, weak webViewProvider] in + guard let self, let webViewProvider else { return } + let webView = webViewProvider.addWebView(configuration: nil) + lookupWebViewHandler = LookupWebViewHandler(webView: webView, translatorsController: translatorsController) + } + guard let lookupWebViewHandler else { + DDLogWarn("RecognizerController: \(task) - can't create LookupWebViewHandler instance") + return nil + } + lookupWebViewHandlersByRecognizerTask[task] = lookupWebViewHandler + setupObserver(for: lookupWebViewHandler) + return lookupWebViewHandler + + func setupObserver(for lookupWebViewHandler: LookupWebViewHandler) { + lookupWebViewHandler.observable + .subscribe(onNext: { process(result: $0) }) + .disposed(by: disposeBag) + + func process(result: Result) { + switch result { + case .success(let data): + switch data { + case .identifiers(let identifiers): + if identifiers.isEmpty { + DDLogWarn("RecognizerController: \(task) - identifier not accepted by LookupWebViewHandler") + enqueueNextIdentifierLookup(for: task) } - } - case .item(let data): - guard data["identifier"] as? [String: String] != nil else { - DDLogWarn("RecognizerController: lookup item data don't contain identifier") - return - } - guard data.count > 1 else { return } - if let error = data["error"] { - DDLogError("RecognizerController: lookup failed - \(error)") - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.lookupFailed)))) + case .item(let data): + guard data["identifier"] as? [String: String] != nil else { + DDLogWarn("RecognizerController: \(task) - lookup item data don't contain identifier") + return + } + guard data.count > 1 else { return } + if let error = data["error"] { + DDLogWarn("RecognizerController: \(task) - lookup failed - \(error)") + enqueueNextIdentifierLookup(for: task) + return + } + guard let itemData = data["data"] as? [[String: Any]], + let item = itemData.first.flatMap({ + var item = $0 + item[FieldKeys.Item.abstract] = item[FieldKeys.Item.abstract] ?? response.abstract + item[FieldKeys.Item.language] = item[FieldKeys.Item.language] ?? response.language + return item + }), + var itemResponse = try? ItemResponse(translatorResponse: item, schemaController: schemaController) + else { + DDLogWarn("RecognizerController: \(task) - parse failed") + enqueueNextIdentifierLookup(for: task) + return + } + if copyTagsAsAutomatic, !itemResponse.tags.isEmpty { + itemResponse = itemResponse.copyWithAutomaticTags } - return - } - guard let itemData = data["data"] as? [[String: Any]], - let item = itemData.first.flatMap({ - var item = $0 - item[FieldKeys.Item.abstract] = item[FieldKeys.Item.abstract] ?? response.abstract - item[FieldKeys.Item.language] = item[FieldKeys.Item.language] ?? response.language - return item - }), - var itemResponse = try? ItemResponse(translatorResponse: item, schemaController: schemaController) - else { cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.parseFailed)))) + observable?.on(.next(Update(task: task, kind: .translated(item: itemResponse)))) } - return } - if copyTagsAsAutomatic, !itemResponse.tags.isEmpty { - itemResponse = itemResponse.copyWithAutomaticTags - } - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .translated(item: itemResponse)))) - } - } - case .failure(let error): - DDLogError("RecognizerController: identifier lookup failed - \(error)") - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(error as! RecognizerController.Error)))) + case .failure(let error): + DDLogError("RecognizerController: \(task) - identifier lookup failed - \(error)") + enqueueNextIdentifierLookup(for: task) } } } } } + + func use(title: String, with response: RemoteRecognizerResponse) { + let creators = response.authors.map({ CreatorResponse(creatorType: "author", firstName: $0.firstName, lastName: $0.lastName) }) + var fields: [KeyBaseKeyPair: String] = [:] + fields[KeyBaseKeyPair(key: FieldKeys.Item.title, baseKey: nil)] = title + fields[KeyBaseKeyPair(key: FieldKeys.Item.abstract, baseKey: nil)] = response.abstract + fields[KeyBaseKeyPair(key: FieldKeys.Item.date, baseKey: nil)] = response.year + fields[KeyBaseKeyPair(key: FieldKeys.Item.pages, baseKey: nil)] = response.pages + fields[KeyBaseKeyPair(key: FieldKeys.Item.volume, baseKey: nil)] = response.volume + fields[KeyBaseKeyPair(key: FieldKeys.Item.url, baseKey: nil)] = response.url + fields[KeyBaseKeyPair(key: FieldKeys.Item.language, baseKey: nil)] = response.language + let rawType: String + if response.type == "book-chapter" { + rawType = "bookSection" + fields[KeyBaseKeyPair(key: FieldKeys.Item.bookTitle, baseKey: nil)] = response.container + fields[KeyBaseKeyPair(key: FieldKeys.Item.publisher, baseKey: nil)] = response.publisher + } else { + rawType = "journalArticle" + fields[KeyBaseKeyPair(key: FieldKeys.Item.issue, baseKey: nil)] = response.issue + fields[KeyBaseKeyPair(key: FieldKeys.Item.issn, baseKey: nil)] = response.issn + fields[KeyBaseKeyPair(key: FieldKeys.Item.publicationTitle, baseKey: nil)] = response.container + } + let accessDate = Date() + + let itemResponse = ItemResponse( + rawType: rawType, + key: KeyGenerator.newKey, + library: LibraryResponse(id: 0, name: "", type: "", links: nil), + parentKey: nil, + collectionKeys: [], + links: nil, + parsedDate: nil, + isTrash: false, + version: 0, + dateModified: accessDate, + dateAdded: accessDate, + fields: fields, + tags: [], + creators: creators, + relations: [:], + createdBy: nil, + lastModifiedBy: nil, + rects: nil, + paths: nil + ) + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .translated(item: itemResponse)))) + } + } } private func cleanupTask(for task: RecognizerTask, completion: @escaping (_ observable: PublishSubject?) -> Void) { @@ -389,7 +487,7 @@ final class RecognizerController { func cleanup(for task: RecognizerTask, completion: @escaping (_ observable: PublishSubject?) -> Void) { let observable = queue.removeValue(forKey: task).flatMap({ $0.observable }) - DDLogInfo("RecognizerController: cleaned up for \(task)") + DDLogInfo("RecognizerController: \(task) - cleaned up") if let webView = lookupWebViewHandlersByRecognizerTask.removeValue(forKey: task)?.webViewHandler.webView { DispatchQueue.main.async { webView.removeFromSuperview() From 62c5d00461f53fe5b0cd180eb106b13430e43c03 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Mon, 17 Feb 2025 18:40:21 +0200 Subject: [PATCH 05/33] Create parent for item that has its metadata retrieved --- .../ShareViewController.swift | 4 +- ZShare/ViewModels/ExtensionViewModel.swift | 5 +- Zotero/Controllers/Controllers.swift | 4 +- .../CreateTranslatedItemsDbRequest.swift | 10 ++- .../IdentifierLookupController.swift | 2 +- Zotero/Controllers/RecognizerController.swift | 66 +++++++++++++++++-- .../Items/Views/ItemsViewController.swift | 7 +- 7 files changed, 82 insertions(+), 16 deletions(-) diff --git a/ZShare/View Controllers/ShareViewController.swift b/ZShare/View Controllers/ShareViewController.swift index 4ce868b6b..127e7aadc 100644 --- a/ZShare/View Controllers/ShareViewController.swift +++ b/ZShare/View Controllers/ShareViewController.swift @@ -749,7 +749,9 @@ final class ShareViewController: UIViewController { pdfWorkerController: pdfWorkerController, apiClient: apiClient, translatorsController: translatorsController, - schemaController: schemaController + schemaController: schemaController, + dbStorage: dbStorage, + dateParser: dateParser ) recognizerController.webViewProvider = self diff --git a/ZShare/ViewModels/ExtensionViewModel.swift b/ZShare/ViewModels/ExtensionViewModel.swift index a8bc802a2..45cb3e1f9 100644 --- a/ZShare/ViewModels/ExtensionViewModel.swift +++ b/ZShare/ViewModels/ExtensionViewModel.swift @@ -538,7 +538,7 @@ final class ExtensionViewModel { } func recognize(file: FileData) { - recognizerController.queue(task: RecognizerController.RecognizerTask(file: file)) { [weak self] observable in + recognizerController.queue(task: RecognizerController.RecognizerTask(file: file, kind: .simple)) { [weak self] observable in guard let self else { return } observable?.subscribe { [weak self] update in guard let self else { return } @@ -561,6 +561,9 @@ final class ExtensionViewModel { // attachment is only used to get an optional URL, so we can safely pass an empty dictionary state.processedAttachment = .itemWithAttachment(item: item, attachment: [:], attachmentFile: file) self.state = state + + case .createdParent: + break } } .disposed(by: disposeBag) diff --git a/Zotero/Controllers/Controllers.swift b/Zotero/Controllers/Controllers.swift index 639bdd8db..e4918e41c 100644 --- a/Zotero/Controllers/Controllers.swift +++ b/Zotero/Controllers/Controllers.swift @@ -404,7 +404,9 @@ final class UserControllers { pdfWorkerController: pdfWorkerController, apiClient: controllers.apiClient, translatorsController: controllers.translatorsAndStylesController, - schemaController: controllers.schemaController + schemaController: controllers.schemaController, + dbStorage: dbStorage, + dateParser: controllers.dateParser ) self.webSocketController = webSocketController self.fileCleanupController = fileCleanupController diff --git a/Zotero/Controllers/Database/Requests/CreateTranslatedItemsDbRequest.swift b/Zotero/Controllers/Database/Requests/CreateTranslatedItemsDbRequest.swift index b3687eadd..1007c3efb 100644 --- a/Zotero/Controllers/Database/Requests/CreateTranslatedItemsDbRequest.swift +++ b/Zotero/Controllers/Database/Requests/CreateTranslatedItemsDbRequest.swift @@ -11,14 +11,17 @@ import Foundation import CocoaLumberjackSwift import RealmSwift -struct CreateTranslatedItemsDbRequest: DbRequest { +struct CreateTranslatedItemsDbRequest: DbResponseRequest { + typealias Response = [RItem] + let responses: [ItemResponse] unowned let schemaController: SchemaController unowned let dateParser: DateParser var needsWrite: Bool { return true } - func process(in database: Realm) throws { + func process(in database: Realm) throws -> [RItem] { + var items: [RItem] = [] for response in self.responses { let (item, _) = try StoreItemDbRequest( response: response, @@ -48,6 +51,9 @@ struct CreateTranslatedItemsDbRequest: DbRequest { changes.insert(.tags) } item.changes.append(RObjectChange.create(changes: changes)) + + items.append(item) } + return items } } diff --git a/Zotero/Controllers/IdentifierLookupController.swift b/Zotero/Controllers/IdentifierLookupController.swift index ed86bea9e..88a085201 100644 --- a/Zotero/Controllers/IdentifierLookupController.swift +++ b/Zotero/Controllers/IdentifierLookupController.swift @@ -459,7 +459,7 @@ final class IdentifierLookupController { func storeDataAndDownloadAttachmentIfNecessary(identifier: String, response: ItemResponse, attachments: [(Attachment, URL)]) throws { let request = CreateTranslatedItemsDbRequest(responses: [response], schemaController: schemaController, dateParser: dateParser) - try dbStorage.perform(request: request, on: backgroundQueue) + _ = try dbStorage.perform(request: request, on: backgroundQueue) changeLookup( for: identifier, to: .translated(.init(response: response, attachments: attachments, libraryId: libraryId, collectionKeys: collectionKeys)) diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index 22879d454..6f9fab788 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -73,7 +73,13 @@ final class RecognizerController { } struct RecognizerTask: Hashable { + enum Kind: Hashable { + case simple + case createParentForItem(libraryId: LibraryIdentifier, key: String) + } + let file: FileData + let kind: Kind } enum Error: Swift.Error { @@ -83,6 +89,7 @@ final class RecognizerController { case remoteRecognizerFailed case noRemainingIdentifiersForLookup case unexpectedState + case cantCreateParentForItem } struct Update { @@ -93,6 +100,7 @@ final class RecognizerController { case remoteRecognitionInProgress(data: [String: Any]) case identifierLookupInProgress(response: RemoteRecognizerResponse, identifier: String) case translated(item: ItemResponse) + case createdParent(key: String) } let task: RecognizerTask @@ -111,9 +119,12 @@ final class RecognizerController { private unowned let apiClient: ApiClient private unowned let translatorsController: TranslatorsAndStylesController private unowned let schemaController: SchemaController + private unowned let dbStorage: DbStorage + private unowned let dateParser: DateParser private let dispatchSpecificKey: DispatchSpecificKey private let accessQueueLabel: String private let accessQueue: DispatchQueue + private let backgroundQueue: DispatchQueue private let disposeBag: DisposeBag internal weak var webViewProvider: WebViewProvider? @@ -124,15 +135,26 @@ final class RecognizerController { private var lookupWebViewHandlersByRecognizerTask: [RecognizerTask: LookupWebViewHandler] = [:] // MARK: Object Lifecycle - init(pdfWorkerController: PDFWorkerController, apiClient: ApiClient, translatorsController: TranslatorsAndStylesController, schemaController: SchemaController) { + init( + pdfWorkerController: PDFWorkerController, + apiClient: ApiClient, + translatorsController: TranslatorsAndStylesController, + schemaController: SchemaController, + dbStorage: DbStorage, + dateParser: DateParser + ) { self.pdfWorkerController = pdfWorkerController self.apiClient = apiClient self.translatorsController = translatorsController self.schemaController = schemaController + self.dbStorage = dbStorage + self.dateParser = dateParser dispatchSpecificKey = DispatchSpecificKey() accessQueueLabel = "org.zotero.RecognizerController.accessQueue" accessQueue = DispatchQueue(label: accessQueueLabel, qos: .userInteractive, attributes: .concurrent) accessQueue.setSpecific(key: dispatchSpecificKey, value: accessQueueLabel) + backgroundQueue = DispatchQueue(label: "org.zotero.RecognizerController.backgroundQueue", qos: .userInitiated) + disposeBag = DisposeBag() } @@ -412,9 +434,7 @@ final class RecognizerController { if copyTagsAsAutomatic, !itemResponse.tags.isEmpty { itemResponse = itemResponse.copyWithAutomaticTags } - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .translated(item: itemResponse)))) - } + createParentIfNeeded(for: task, with: itemResponse, schemaController: schemaController, dateParser: dateParser) } case .failure(let error): @@ -470,8 +490,42 @@ final class RecognizerController { rects: nil, paths: nil ) - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .translated(item: itemResponse)))) + createParentIfNeeded(for: task, with: itemResponse, schemaController: schemaController, dateParser: dateParser) + } + + func createParentIfNeeded(for task: RecognizerTask, with itemResponse: ItemResponse, schemaController: SchemaController, dateParser: DateParser) { + switch task.kind { + case .simple: + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .translated(item: itemResponse)))) + } + + case .createParentForItem(let libraryId, let key): + backgroundQueue.async { [weak self] in + guard let self else { return } + let response = itemResponse.copy(libraryId: libraryId, collectionKeys: [], tags: []) + var update: Update? + do { + try dbStorage.perform(on: backgroundQueue) { coordinator in + let items = try coordinator.perform(request: CreateTranslatedItemsDbRequest(responses: [response], schemaController: schemaController, dateParser: dateParser)) + guard let parent = items.first else { + update = Update(task: task, kind: .failed(.cantCreateParentForItem)) + return + } + try coordinator.perform(request: MoveItemsToParentDbRequest(itemKeys: [key], parentKey: parent.key, libraryId: libraryId)) + update = Update(task: task, kind: .createdParent(key: parent.key)) + coordinator.invalidate() + } + } catch let error { + DDLogError("RecognizerController: can't create parent for item - \(error)") + update = Update(task: task, kind: .failed(error as! Error)) + } + cleanupTask(for: task) { observable in + if let update { + observable?.on(.next(update)) + } + } + } } } } diff --git a/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift b/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift index 4f48d1b1f..8c00133fd 100644 --- a/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift +++ b/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift @@ -171,11 +171,10 @@ final class ItemsViewController: BaseItemsViewController { file.mimeType == "application/pdf", let recognizerController = controllers.userControllers?.recognizerController else { return } - recognizerController.queue(task: RecognizerController.RecognizerTask(file: file)) { [weak self] observable in + recognizerController.queue(task: RecognizerController.RecognizerTask(file: file, kind: .createParentForItem(libraryId: library.identifier, key: key))) { [weak self] observable in guard let self else { return } - observable?.subscribe { update in - // TODO: Implement - print("Recognizer controller update: \(update)") + observable?.subscribe { _ in + // TODO: Implement update of UI according to updates } .disposed(by: disposeBag) } From 17f9e48d29a1d39ecf01d1c7f4090882807fe4f5 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Mon, 17 Feb 2025 19:29:10 +0200 Subject: [PATCH 06/33] Retrieve metadata and create parent if needed for added files --- .../Requests/CreateAttachmentsDbRequest.swift | 16 +++++++++------- Zotero/Scenes/Detail/DetailCoordinator.swift | 3 ++- .../ViewModels/ItemDetailActionHandler.swift | 2 +- .../Items/ViewModels/ItemsActionHandler.swift | 19 +++++++++++++++++-- .../ViewModels/NoteEditorActionHandler.swift | 2 +- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Zotero/Controllers/Database/Requests/CreateAttachmentsDbRequest.swift b/Zotero/Controllers/Database/Requests/CreateAttachmentsDbRequest.swift index 0506c8173..9a789e76a 100644 --- a/Zotero/Controllers/Database/Requests/CreateAttachmentsDbRequest.swift +++ b/Zotero/Controllers/Database/Requests/CreateAttachmentsDbRequest.swift @@ -12,7 +12,7 @@ import CocoaLumberjackSwift import RealmSwift struct CreateAttachmentsDbRequest: DbResponseRequest { - typealias Response = [(String, String)] + typealias Response = (succeeded: [Attachment], failed: [(String, String)]) let attachments: [Attachment] let parentKey: String? @@ -21,8 +21,8 @@ struct CreateAttachmentsDbRequest: DbResponseRequest { var needsWrite: Bool { return true } - func process(in database: Realm) throws -> [(String, String)] { - guard let libraryId = attachments.first?.libraryId else { return [] } + func process(in database: Realm) throws -> (succeeded: [Attachment], failed: [(String, String)]) { + guard let libraryId = attachments.first?.libraryId else { return ([], []) } let parent = parentKey.flatMap({ database.objects(RItem.self).uniqueObject(key: $0, libraryId: libraryId) }) if let parent = parent { @@ -30,11 +30,12 @@ struct CreateAttachmentsDbRequest: DbResponseRequest { parent.version = parent.version } + var succeeded: [Attachment] = [] var failed: [(String, String)] = [] for attachment in attachments { do { - let attachment = try CreateAttachmentDbRequest( + let item = try CreateAttachmentDbRequest( attachment: attachment, parentKey: nil, localizedType: localizedType, @@ -43,15 +44,16 @@ struct CreateAttachmentsDbRequest: DbResponseRequest { tags: [] ).process(in: database) if let parent = parent { - attachment.parent = parent - attachment.changes.append(RObjectChange.create(changes: RItemChanges.parent)) + item.parent = parent + item.changes.append(RObjectChange.create(changes: RItemChanges.parent)) } + succeeded.append(attachment) } catch let error { DDLogError("CreateAttachmentsDbRequest: could not create attachment - \(error)") failed.append((attachment.key, attachment.title)) } } - return failed + return (succeeded, failed) } } diff --git a/Zotero/Scenes/Detail/DetailCoordinator.swift b/Zotero/Scenes/Detail/DetailCoordinator.swift index 0dfbe5899..84dc12dfc 100644 --- a/Zotero/Scenes/Detail/DetailCoordinator.swift +++ b/Zotero/Scenes/Detail/DetailCoordinator.swift @@ -192,7 +192,8 @@ final class DetailCoordinator: Coordinator { citationController: userControllers.citationController, fileCleanupController: userControllers.fileCleanupController, syncScheduler: userControllers.syncScheduler, - htmlAttributedStringConverter: controllers.htmlAttributedStringConverter + htmlAttributedStringConverter: controllers.htmlAttributedStringConverter, + recognizerController: userControllers.recognizerController ) let controller = ItemsViewController(viewModel: ViewModel(initialState: state, handler: handler), controllers: controllers, coordinatorDelegate: self) controller.tagFilterDelegate = itemsTagFilterDelegate diff --git a/Zotero/Scenes/Detail/ItemDetail/ViewModels/ItemDetailActionHandler.swift b/Zotero/Scenes/Detail/ItemDetail/ViewModels/ItemDetailActionHandler.swift index 7875b5d49..b2eb40a09 100644 --- a/Zotero/Scenes/Detail/ItemDetail/ViewModels/ItemDetailActionHandler.swift +++ b/Zotero/Scenes/Detail/ItemDetail/ViewModels/ItemDetailActionHandler.swift @@ -528,7 +528,7 @@ final class ItemDetailActionHandler: ViewModelActionHandler, BackgroundDbProcess state.error = .cantAddAttachments(.allFailedCreation) state.attachments.removeAll(where: { attachment in return attachments.contains(where: { $0.key == attachment.key }) }) - case .success(let failed): + case .success(let (_, failed)): guard !failed.isEmpty else { return } state.error = .cantAddAttachments(.someFailedCreation(failed.map({ $0.1 }))) state.attachments.removeAll(where: { attachment in return failed.contains(where: { $0.0 == attachment.key }) }) diff --git a/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift b/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift index 956284f62..b617758fe 100644 --- a/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift +++ b/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift @@ -26,6 +26,7 @@ final class ItemsActionHandler: BaseItemsActionHandler, ViewModelActionHandler { private unowned let fileCleanupController: AttachmentFileCleanupController private unowned let syncScheduler: SynchronizationScheduler private unowned let htmlAttributedStringConverter: HtmlAttributedStringConverter + private unowned let recognizerController: RecognizerController private let disposeBag: DisposeBag init( @@ -37,7 +38,8 @@ final class ItemsActionHandler: BaseItemsActionHandler, ViewModelActionHandler { citationController: CitationController, fileCleanupController: AttachmentFileCleanupController, syncScheduler: SynchronizationScheduler, - htmlAttributedStringConverter: HtmlAttributedStringConverter + htmlAttributedStringConverter: HtmlAttributedStringConverter, + recognizerController: RecognizerController ) { self.fileStorage = fileStorage self.schemaController = schemaController @@ -47,6 +49,7 @@ final class ItemsActionHandler: BaseItemsActionHandler, ViewModelActionHandler { self.fileCleanupController = fileCleanupController self.syncScheduler = syncScheduler self.htmlAttributedStringConverter = htmlAttributedStringConverter + self.recognizerController = recognizerController self.disposeBag = DisposeBag() super.init(dbStorage: dbStorage) } @@ -496,7 +499,19 @@ final class ItemsActionHandler: BaseItemsActionHandler, ViewModelActionHandler { guard let self, let viewModel else { return } switch result { - case .success(let failed): + case .success(let (succeeded, failed)): + for attachment in succeeded { + if let file = attachment.file as? FileData, file.mimeType == "application/pdf" { + let task = RecognizerController.RecognizerTask(file: file, kind: .createParentForItem(libraryId: libraryId, key: attachment.key)) + recognizerController.queue(task: task) { [weak self] observable in + guard let self else { return } + observable?.subscribe { _ in + // TODO: Implement update of UI according to updates + } + .disposed(by: disposeBag) + } + } + } guard !failed.isEmpty else { return } update(viewModel: viewModel) { state in state.error = .attachmentAdding(.someFailed(failed.map({ $0.1 }))) diff --git a/Zotero/Scenes/General/ViewModels/NoteEditorActionHandler.swift b/Zotero/Scenes/General/ViewModels/NoteEditorActionHandler.swift index f3a88df7c..f0f68ecda 100644 --- a/Zotero/Scenes/General/ViewModels/NoteEditorActionHandler.swift +++ b/Zotero/Scenes/General/ViewModels/NoteEditorActionHandler.swift @@ -117,7 +117,7 @@ struct NoteEditorActionHandler: ViewModelActionHandler, BackgroundDbProcessingAc DDLogInfo("NoteEditorActionHandler: submit \(images.count) images") do { - let failedKeys = try dbStorage.perform(request: request, on: backgroundQueue).map({ $0.0 }) + let failedKeys = try dbStorage.perform(request: request, on: backgroundQueue).1.map({ $0.0 }) let successfulImages = images.filter({ !failedKeys.contains($0.1.key) }).map({ NoteEditorState.CreatedImage(nodeId: $0.0, key: $0.1.key) }) DDLogInfo("NoteEditorActionHandler: successfully created \(successfulImages)") update(viewModel: viewModel) { state in From 7b0eff7ea16367a87779eb32a77d98bed66ed8c4 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Tue, 18 Feb 2025 17:48:28 +0200 Subject: [PATCH 07/33] Improve RecognizerController --- Zotero/Controllers/RecognizerController.swift | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index 6f9fab788..cfa29342c 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -99,8 +99,8 @@ final class RecognizerController { case recognitionInProgress case remoteRecognitionInProgress(data: [String: Any]) case identifierLookupInProgress(response: RemoteRecognizerResponse, identifier: String) - case translated(item: ItemResponse) - case createdParent(key: String) + case translated(itemResponse: ItemResponse) + case createdParent(item: RItem) } let task: RecognizerTask @@ -125,6 +125,10 @@ final class RecognizerController { private let accessQueueLabel: String private let accessQueue: DispatchQueue private let backgroundQueue: DispatchQueue + private let updatesSubject: PublishSubject + var updates: Observable { + updatesSubject.asObservable() + } private let disposeBag: DisposeBag internal weak var webViewProvider: WebViewProvider? @@ -154,25 +158,28 @@ final class RecognizerController { accessQueue = DispatchQueue(label: accessQueueLabel, qos: .userInteractive, attributes: .concurrent) accessQueue.setSpecific(key: dispatchSpecificKey, value: accessQueueLabel) backgroundQueue = DispatchQueue(label: "org.zotero.RecognizerController.backgroundQueue", qos: .userInitiated) - + updatesSubject = PublishSubject() disposeBag = DisposeBag() } // MARK: Actions - func queue(task: RecognizerTask, completion: @escaping (_ observable: PublishSubject?) -> Void) { + func queue(task: RecognizerTask, completion: @escaping (_ observable: Observable?) -> Void) { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { completion(nil) return } if let (_, observable) = queue[task] { - completion(observable) + completion(observable.asObservable()) return } let state: RecognizerTaskState = .enqueued let observable: PublishSubject = PublishSubject() queue[task] = (state, observable) - completion(observable) + completion(observable.asObservable()) + observable.subscribe(onNext: { [weak self] update in + self?.updatesSubject.on(.next(update)) + }).disposed(by: disposeBag) startRecognitionIfNeeded() } @@ -497,7 +504,7 @@ final class RecognizerController { switch task.kind { case .simple: cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .translated(item: itemResponse)))) + observable?.on(.next(Update(task: task, kind: .translated(itemResponse: itemResponse)))) } case .createParentForItem(let libraryId, let key): @@ -513,7 +520,7 @@ final class RecognizerController { return } try coordinator.perform(request: MoveItemsToParentDbRequest(itemKeys: [key], parentKey: parent.key, libraryId: libraryId)) - update = Update(task: task, kind: .createdParent(key: parent.key)) + update = Update(task: task, kind: .createdParent(item: parent)) coordinator.invalidate() } } catch let error { From 9a039e8fd100f31bb896d0841c9e986181af9af5 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Wed, 19 Feb 2025 12:56:52 +0200 Subject: [PATCH 08/33] Indicate when an item is retrieving metadata --- ZShare/ViewModels/ExtensionViewModel.swift | 4 +- Zotero/Assets/en.lproj/Localizable.strings | 1 + Zotero/Controllers/RecognizerController.swift | 38 +++- Zotero/Extensions/Localizable.swift | 2 + .../Detail/Items/Models/ItemCellModel.swift | 50 +++++- .../Detail/Items/Models/ItemsAction.swift | 1 + .../Detail/Items/Models/ItemsState.swift | 16 +- .../Items/ViewModels/ItemsActionHandler.swift | 18 +- .../Scenes/Detail/Items/Views/ItemCell.swift | 64 ++++++- .../Items/Views/ItemsTableViewHandler.swift | 5 + .../Items/Views/ItemsViewController.swift | 170 ++++++++++-------- .../Views/RItemsTableViewDataSource.swift | 6 +- .../Views/TrashTableViewDataSource.swift | 2 +- .../Trash/Views/TrashViewController.swift | 2 +- 14 files changed, 273 insertions(+), 106 deletions(-) diff --git a/ZShare/ViewModels/ExtensionViewModel.swift b/ZShare/ViewModels/ExtensionViewModel.swift index 45cb3e1f9..d2eabc32e 100644 --- a/ZShare/ViewModels/ExtensionViewModel.swift +++ b/ZShare/ViewModels/ExtensionViewModel.swift @@ -553,7 +553,7 @@ final class ExtensionViewModel { case .recognitionInProgress, .remoteRecognitionInProgress, .identifierLookupInProgress: updateState(with: .decoding) - case .translated(item: let item): + case .translated(let item): var state = self.state state.expectedItem = item state.expectedAttachment = (filename, tmpFile) @@ -562,7 +562,7 @@ final class ExtensionViewModel { state.processedAttachment = .itemWithAttachment(item: item, attachment: [:], attachmentFile: file) self.state = state - case .createdParent: + case .enqueued, .createdParent: break } } diff --git a/Zotero/Assets/en.lproj/Localizable.strings b/Zotero/Assets/en.lproj/Localizable.strings index 0d7ca29d0..e5f246106 100644 --- a/Zotero/Assets/en.lproj/Localizable.strings +++ b/Zotero/Assets/en.lproj/Localizable.strings @@ -148,6 +148,7 @@ "items.generating_bib" = "Generating Bibliography"; "items.creator_summary.and" = "%@ and %@"; "items.creator_summary.etal" = "%@ et al."; +"items.retrieving_metadata" = "Retrieving Metadata"; "lookup.title" = "Enter ISBNs, DOls, PMIDs, arXiv IDs, or ADS Bibcodes to add to your library:"; diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index cfa29342c..c3a3521f8 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -96,6 +96,7 @@ final class RecognizerController { enum Kind { case failed(Error) case cancelled + case enqueued case recognitionInProgress case remoteRecognitionInProgress(data: [String: Any]) case identifierLookupInProgress(response: RemoteRecognizerResponse, identifier: String) @@ -136,6 +137,7 @@ final class RecognizerController { // Accessed only via accessQueue private static let maxConcurrentRecognizerTasks: Int = 1 private var queue: OrderedDictionary)> = [:] + private var latestUpdates: [LibraryIdentifier: [String: Update.Kind]] = [:] private var lookupWebViewHandlersByRecognizerTask: [RecognizerTask: LookupWebViewHandler] = [:] // MARK: Object Lifecycle @@ -163,28 +165,39 @@ final class RecognizerController { } // MARK: Actions - func queue(task: RecognizerTask, completion: @escaping (_ observable: Observable?) -> Void) { + func queue(task: RecognizerTask, completion: ((_ observable: Observable?) -> Void)? = nil) { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { - completion(nil) + completion?(nil) return } if let (_, observable) = queue[task] { - completion(observable.asObservable()) + completion?(observable.asObservable()) return } let state: RecognizerTaskState = .enqueued let observable: PublishSubject = PublishSubject() queue[task] = (state, observable) - completion(observable.asObservable()) + completion?(observable.asObservable()) observable.subscribe(onNext: { [weak self] update in self?.updatesSubject.on(.next(update)) }).disposed(by: disposeBag) + emmitUpdate(for: task, observable: observable, kind: .enqueued) startRecognitionIfNeeded() } } + private func emmitUpdate(for task: RecognizerTask, observable: PublishSubject, kind: Update.Kind) { + let update = Update(task: task, kind: kind) + if case .createParentForItem(let libraryId, let key) = task.kind { + var libraryLatestUpdates = latestUpdates[libraryId, default: [:]] + libraryLatestUpdates[key] = kind + latestUpdates[libraryId] = libraryLatestUpdates + } + observable.on(.next(update)) + } + private func startRecognitionIfNeeded() { let runningRecognizerTasksCount = queue.filter({ switch $0.value.state { @@ -212,7 +225,7 @@ final class RecognizerController { func start(task: RecognizerTask, observable: PublishSubject) { queue[task] = (.recognitionInProgress, observable) - observable.on(.next(Update(task: task, kind: .recognitionInProgress))) + emmitUpdate(for: task, observable: observable, kind: .recognitionInProgress) pdfWorkerController.queue(work: PDFWorkerController.PDFWork(file: task.file, kind: .recognizer)) { [weak self] pdfWorkerObservable in guard let self else { return } @@ -266,7 +279,7 @@ final class RecognizerController { return } queue[task] = (.remoteRecognitionInProgress(data: data), observable) - observable.on(.next(Update(task: task, kind: .remoteRecognitionInProgress(data: data)))) + emmitUpdate(for: task, observable: observable, kind: .remoteRecognitionInProgress(data: data)) apiClient.send(request: RecognizerRequest(parameters: data)).subscribe( onSuccess: { (response: (RemoteRecognizerResponse, HTTPURLResponse)) in @@ -378,7 +391,7 @@ final class RecognizerController { enqueueNextIdentifierLookup(for: task) return } - observable.on(.next(Update(task: task, kind: .identifierLookupInProgress(response: response, identifier: identifier)))) + emmitUpdate(for: task, observable: observable, kind: .identifierLookupInProgress(response: response, identifier: identifier)) lookupWebViewHandler.lookUp(identifier: identifier) func getLookupWebViewHandler(for task: RecognizerTask) -> LookupWebViewHandler? { @@ -548,6 +561,10 @@ final class RecognizerController { func cleanup(for task: RecognizerTask, completion: @escaping (_ observable: PublishSubject?) -> Void) { let observable = queue.removeValue(forKey: task).flatMap({ $0.observable }) + if case .createParentForItem(let libraryId, let key) = task.kind, var libraryLatestUpdates = latestUpdates[libraryId] { + libraryLatestUpdates[key] = nil + latestUpdates[libraryId] = libraryLatestUpdates + } DDLogInfo("RecognizerController: \(task) - cleaned up") if let webView = lookupWebViewHandlersByRecognizerTask.removeValue(forKey: task)?.webViewHandler.webView { DispatchQueue.main.async { @@ -558,4 +575,11 @@ final class RecognizerController { startRecognitionIfNeeded() } } + + func latestUpdate(for key: String, libraryId: LibraryIdentifier) -> Update.Kind? { + return accessQueue.sync { [weak self] in + guard let self, let libraryLatestUpdates = latestUpdates[libraryId] else { return nil } + return libraryLatestUpdates[key] + } + } } diff --git a/Zotero/Extensions/Localizable.swift b/Zotero/Extensions/Localizable.swift index ae64ee23a..0bb0f1c5f 100644 --- a/Zotero/Extensions/Localizable.swift +++ b/Zotero/Extensions/Localizable.swift @@ -841,6 +841,8 @@ internal enum L10n { } /// Remove from Collection internal static let removeFromCollectionTitle = L10n.tr("Localizable", "items.remove_from_collection_title", fallback: "Remove from Collection") + /// Retrieving Metadata + internal static let retrievingMetadata = L10n.tr("Localizable", "items.retrieving_metadata", fallback: "Retrieving Metadata") /// Search Items internal static let searchTitle = L10n.tr("Localizable", "items.search_title", fallback: "Search Items") /// Select All diff --git a/Zotero/Scenes/Detail/Items/Models/ItemCellModel.swift b/Zotero/Scenes/Detail/Items/Models/ItemCellModel.swift index ce4d68fc8..ea86f895c 100644 --- a/Zotero/Scenes/Detail/Items/Models/ItemCellModel.swift +++ b/Zotero/Scenes/Detail/Items/Models/ItemCellModel.swift @@ -17,25 +17,30 @@ struct ItemCellModel { case url } + struct Subtitle { + let text: String + let animated: Bool + } + let key: String let typeIconName: String let iconRenderingMode: UIImage.RenderingMode let typeName: String let title: NSAttributedString - let subtitle: String + let subtitle: Subtitle? let hasNote: Bool let tagColors: [UIColor] let tagEmojis: [String] let accessory: Accessory? let hasDetailButton: Bool - init(item: RItem, typeName: String, title: NSAttributedString, accessory: Accessory?) { + init(item: RItem, typeName: String, title: NSAttributedString, subtitle: Subtitle?, accessory: Accessory?) { key = item.key typeIconName = Self.typeIconName(for: item) iconRenderingMode = .alwaysOriginal self.typeName = typeName self.title = title - subtitle = Self.creatorSummary(for: item) + self.subtitle = subtitle hasNote = Self.hasNote(item: item) self.accessory = accessory let (colors, emojis) = Self.tagData(item: item) @@ -44,8 +49,14 @@ struct ItemCellModel { hasDetailButton = true } - init(item: RItem, typeName: String, title: NSAttributedString, accessory: ItemAccessory?, fileDownloader: AttachmentDownloader?) { - self.init(item: item, typeName: typeName, title: title, accessory: Self.createAccessory(from: accessory, fileDownloader: fileDownloader)) + init(item: RItem, typeName: String, title: NSAttributedString, accessory: ItemAccessory?, fileDownloader: AttachmentDownloader?, recognizerController: RecognizerController?) { + self.init( + item: item, + typeName: typeName, + title: title, + subtitle: Self.createSubtitle(for: item, recognizerController: recognizerController), + accessory: Self.createAccessory(from: accessory, fileDownloader: fileDownloader) + ) } init(collectionWithKey key: String, title: NSAttributedString) { @@ -54,7 +65,7 @@ struct ItemCellModel { accessory = nil typeIconName = Asset.Images.Cells.collection.name iconRenderingMode = .alwaysTemplate - subtitle = "" + subtitle = nil hasNote = false tagColors = [] tagEmojis = [] @@ -118,4 +129,31 @@ struct ItemCellModel { } return result } + + static func createSubtitle(for item: RItem, update: RecognizerController.Update.Kind?) -> Subtitle? { + guard item.parent == nil, let update else { + return Subtitle(text: creatorSummary(for: item), animated: false) + } + + let text: String + let animated: Bool + switch update { + case .failed, .cancelled, .createdParent: + text = creatorSummary(for: item) + animated = false + + case .enqueued, .recognitionInProgress, .remoteRecognitionInProgress, .identifierLookupInProgress, .translated: + text = L10n.Items.retrievingMetadata + animated = true + } + return Subtitle(text: text, animated: animated) + } + + static func createSubtitle(for item: RItem, recognizerController: RecognizerController?) -> Subtitle? { + guard item.parent == nil, let recognizerController else { + return Subtitle(text: creatorSummary(for: item), animated: false) + } + let update = recognizerController.latestUpdate(for: item.key, libraryId: item.libraryIdentifier) + return createSubtitle(for: item, update: update) + } } diff --git a/Zotero/Scenes/Detail/Items/Models/ItemsAction.swift b/Zotero/Scenes/Detail/Items/Models/ItemsAction.swift index 461b20b79..001295d78 100644 --- a/Zotero/Scenes/Detail/Items/Models/ItemsAction.swift +++ b/Zotero/Scenes/Detail/Items/Models/ItemsAction.swift @@ -39,6 +39,7 @@ enum ItemsAction { case updateDownload(update: AttachmentDownloader.Update, batchData: ItemsState.DownloadBatchData?) case updateIdentifierLookup(update: IdentifierLookupController.Update, batchData: ItemsState.IdentifierLookupBatchData) case updateRemoteDownload(update: RemoteAttachmentDownloader.Update, batchData: ItemsState.DownloadBatchData?) + case updateMetadataRetrieval(itemKey: String, update: RecognizerController.Update.Kind) case openAttachment(attachment: Attachment, parentKey: String?) case attachmentOpened(String) case updateKeys(items: Results, deletions: [Int], insertions: [Int], modifications: [Int]) diff --git a/Zotero/Scenes/Detail/Items/Models/ItemsState.swift b/Zotero/Scenes/Detail/Items/Models/ItemsState.swift index 5c45b1656..4bf5207b5 100644 --- a/Zotero/Scenes/Detail/Items/Models/ItemsState.swift +++ b/Zotero/Scenes/Detail/Items/Models/ItemsState.swift @@ -83,6 +83,16 @@ struct ItemsState: ViewModelState { } } + struct ItemUpdate { + enum Kind { + case accessory + case subtitle(ItemCellModel.Subtitle?) + } + + let key: String + let kind: Kind + } + let collection: Collection var library: Library @@ -102,8 +112,8 @@ struct ItemsState: ViewModelState { var changes: Changes var error: ItemsError? var itemKeyToDuplicate: String? - // Used to indicate which row should update it's attachment view. The update is done directly to cell instead of tableView reload. - var updateItemKey: String? + // Used to indicate which row should update directly a cell element (e.g. attachment view). The update is done directly to cell instead of tableView reload. + var updateItem: ItemUpdate? var attachmentToOpen: String? var downloadBatchData: DownloadBatchData? var remoteDownloadBatchData: DownloadBatchData? @@ -152,6 +162,6 @@ struct ItemsState: ViewModelState { error = nil changes = [] itemKeyToDuplicate = nil - updateItemKey = nil + updateItem = nil } } diff --git a/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift b/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift index b617758fe..fd520b564 100644 --- a/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift +++ b/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift @@ -146,6 +146,9 @@ final class ItemsActionHandler: BaseItemsActionHandler, ViewModelActionHandler { case .updateRemoteDownload(let update, let batchData): self.process(remoteDownloadUpdate: update, batchData: batchData, in: viewModel) + case .updateMetadataRetrieval(let itemKey, let update): + process(metadataRetrievalUpdate: update, itemKey: itemKey, in: viewModel) + case .openAttachment(let attachment, let parentKey): self.open(attachment: attachment, parentKey: parentKey, in: viewModel) @@ -266,7 +269,7 @@ final class ItemsActionHandler: BaseItemsActionHandler, ViewModelActionHandler { let updatedAccessory = accessory.updatedAttachment(update: { attachment in attachment.changed(location: .remote, condition: { $0 == .local }) }) else { return } self.update(viewModel: viewModel) { state in state.itemAccessories[updateKey] = updatedAccessory - state.updateItemKey = updateKey + state.updateItem = ItemsState.ItemUpdate(key: updateKey, kind: .accessory) } } } @@ -310,7 +313,7 @@ final class ItemsActionHandler: BaseItemsActionHandler, ViewModelActionHandler { guard let updatedAttachment = attachment.changed(location: .local, compressed: compressed) else { return } updateViewModel { state in state.itemAccessories[updateKey] = .attachment(attachment: updatedAttachment, parentKey: downloadUpdate.parentKey) - state.updateItemKey = updateKey + state.updateItem = ItemsState.ItemUpdate(key: updateKey, kind: .accessory) } case .progress: @@ -319,13 +322,13 @@ final class ItemsActionHandler: BaseItemsActionHandler, ViewModelActionHandler { guard let currentProgress = fileDownloader.data(for: downloadUpdate.key, parentKey: downloadUpdate.parentKey, libraryId: downloadUpdate.libraryId).progress, currentProgress < 1 else { return } updateViewModel { state in - state.updateItemKey = updateKey + state.updateItem = ItemsState.ItemUpdate(key: updateKey, kind: .accessory) } case .cancelled, .failed: DDLogInfo("ItemsActionHandler: download update \(attachment.key); \(attachment.libraryId); kind \(downloadUpdate.kind)") updateViewModel { state in - state.updateItemKey = updateKey + state.updateItem = ItemsState.ItemUpdate(key: updateKey, kind: .accessory) } } @@ -359,6 +362,13 @@ final class ItemsActionHandler: BaseItemsActionHandler, ViewModelActionHandler { } } + private func process(metadataRetrievalUpdate: RecognizerController.Update.Kind, itemKey: String, in viewModel: ViewModel) { + guard let item = viewModel.state.results?.filter("key == %@", itemKey).first else { return } + update(viewModel: viewModel) { state in + state.updateItem = ItemsState.ItemUpdate(key: itemKey, kind: .subtitle(ItemCellModel.createSubtitle(for: item, update: metadataRetrievalUpdate))) + } + } + private func cacheItemAccessory(for item: RItem, in viewModel: ViewModel) { // Create cached accessory only if there is nothing in cache yet. guard viewModel.state.itemAccessories[item.key] == nil, let accessory = ItemAccessory.create(from: item, fileStorage: fileStorage, urlDetector: urlDetector) else { return } diff --git a/Zotero/Scenes/Detail/Items/Views/ItemCell.swift b/Zotero/Scenes/Detail/Items/Views/ItemCell.swift index 105917907..1b059783c 100644 --- a/Zotero/Scenes/Detail/Items/Views/ItemCell.swift +++ b/Zotero/Scenes/Detail/Items/Views/ItemCell.swift @@ -33,9 +33,16 @@ final class ItemCell: UITableViewCell { self.selectedBackgroundView?.backgroundColor } + private var subtitleAnimator: UIViewPropertyAnimator? + private var subtitlePrefix: String = "" + private var subtitleAnimationSuffixDotCount = 0 + override func prepareForReuse() { super.prepareForReuse() self.key = "" + subtitleAnimator = nil + subtitlePrefix = "" + subtitleAnimationSuffixDotCount = 0 } override func awakeFromNib() { @@ -103,12 +110,10 @@ final class ItemCell: UITableViewCell { self.titleLabel.attributedText = item.title } self.titleLabel.accessibilityLabel = self.titleAccessibilityLabel(for: item) - self.subtitleLabel.text = item.subtitle.isEmpty ? " " : item.subtitle - self.subtitleLabel.accessibilityLabel = item.subtitle + set(subtitle: item.subtitle) // The label adds extra horizontal spacing so there is a negative right inset so that the label ends where the text ends exactly. // The note icon is rectangular and has 1px white space on each side, so it needs an extra negative pixel when there are no tags. self.subtitleLabel.rightInset = item.tagColors.isEmpty ? -2 : -1 - self.subtitleLabel.isHidden = item.subtitle.isEmpty && (item.hasNote || !item.tagColors.isEmpty) self.noteIcon.isHidden = !item.hasNote self.noteIcon.isAccessibilityElement = false @@ -150,4 +155,57 @@ final class ItemCell: UITableViewCell { let title = item.title.string.isEmpty ? L10n.Accessibility.untitled : item.title.string return item.typeName + ", " + title } + + func set(subtitle: ItemCellModel.Subtitle?) { + let text = subtitle?.text ?? "" + let animated = subtitle?.animated ?? false + subtitlePrefix = text + if let subtitleAnimator, subtitleAnimator.isRunning { + // Animator is already running. + if !animated { + // Stop animating subtitle, and the new subtitle prefix will be set in the label. + stopAnimatingSubtitle() + } + // Otherwise do nothing as the animation will use the new subtitle prefix. + } else { + // Animator is not running. First set new text. + subtitleLabel.text = text.isEmpty ? " " : text + subtitleLabel.accessibilityLabel = text + if !text.isEmpty, animated { + // Start animating if needed. + startAnimatingSubtitle() + } + } + subtitleLabel.isHidden = text.isEmpty && (!noteIcon.isHidden || !tagCircles.isHidden) + } + + private func startAnimatingSubtitle() { + subtitleAnimator = UIViewPropertyAnimator(duration: 0.5, curve: .linear) { [weak self] in + guard let self else { return } + // Reduce subtitle label opacity to create a fade effect. + subtitleLabel.alpha = 0.9 + } + + subtitleAnimator?.addCompletion { [weak self] _ in + guard let self else { return } + subtitleAnimationSuffixDotCount = (subtitleAnimationSuffixDotCount + 1) % 3 + subtitleLabel.text = subtitlePrefix + String(repeating: ".", count: subtitleAnimationSuffixDotCount + 1) + " " + subtitleLabel.accessibilityLabel = subtitlePrefix + // Restore opacity. + subtitleLabel.alpha = 1 + // Repeat animation. + startAnimatingSubtitle() + } + + subtitleAnimator?.startAnimation() + } + + private func stopAnimatingSubtitle() { + subtitleAnimator?.stopAnimation(true) + subtitleAnimator = nil + subtitleAnimationSuffixDotCount = 0 + subtitleLabel.text = subtitlePrefix.isEmpty ? " " : subtitlePrefix + subtitleLabel.accessibilityLabel = subtitlePrefix + subtitleLabel.alpha = 1 + } } diff --git a/Zotero/Scenes/Detail/Items/Views/ItemsTableViewHandler.swift b/Zotero/Scenes/Detail/Items/Views/ItemsTableViewHandler.swift index 2ec89c874..edaf6d14f 100644 --- a/Zotero/Scenes/Detail/Items/Views/ItemsTableViewHandler.swift +++ b/Zotero/Scenes/Detail/Items/Views/ItemsTableViewHandler.swift @@ -186,6 +186,11 @@ final class ItemsTableViewHandler: NSObject { cell.set(accessory: accessory) } + func updateCell(key: String, withSubtitle subtitle: ItemCellModel.Subtitle?) { + guard let cell = tableView.visibleCells.first(where: { ($0 as? ItemCell)?.key == key }) as? ItemCell else { return } + cell.set(subtitle: subtitle) + } + func performTapAction(forIndexPath indexPath: IndexPath) { guard let action = dataSource.tapAction(for: indexPath) else { tableView.deselectRow(at: indexPath, animated: true) diff --git a/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift b/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift index 8c00133fd..9aba4f822 100644 --- a/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift +++ b/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift @@ -44,11 +44,17 @@ final class ItemsViewController: BaseItemsViewController { override func viewDidLoad() { super.viewDidLoad() - dataSource = RItemsTableViewDataSource(viewModel: viewModel, fileDownloader: controllers.userControllers?.fileDownloader, schemaController: controllers.schemaController) + dataSource = RItemsTableViewDataSource( + viewModel: viewModel, + fileDownloader: controllers.userControllers?.fileDownloader, + recognizerController: controllers.userControllers?.recognizerController, + schemaController: controllers.schemaController + ) handler = ItemsTableViewHandler(tableView: tableView, delegate: self, dataSource: dataSource, dragDropController: controllers.dragDropController) toolbarController = ItemsToolbarController(viewController: self, data: toolbarData, collection: collection, library: library, delegate: self) setupRightBarButtonItems(expectedItems: rightBarButtonItemTypes(for: viewModel.state)) setupFileObservers() + setupRecognizerObserver() setupAppStateObserver() if let term = viewModel.state.searchTerm, !term.isEmpty { @@ -65,6 +71,82 @@ final class ItemsViewController: BaseItemsViewController { self?.update(state: state) }) .disposed(by: disposeBag) + + func setupFileObservers() { + NotificationCenter.default + .rx + .notification(.attachmentFileDeleted) + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] notification in + if let notification = notification.object as? AttachmentFileDeletedNotification { + self?.viewModel.process(action: .updateAttachments(notification)) + } + }) + .disposed(by: disposeBag) + + let downloader = controllers.userControllers?.fileDownloader + downloader?.observable + .observe(on: MainScheduler.asyncInstance) + .subscribe(onNext: { [weak self, weak downloader] update in + guard let self else { return } + process( + downloadUpdate: update, + toOpen: viewModel.state.attachmentToOpen, + downloader: downloader, + dataUpdate: { batchData in + self.viewModel.process(action: .updateDownload(update: update, batchData: batchData)) + }, + attachmentWillOpen: { update in + self.viewModel.process(action: .attachmentOpened(update.key)) + } + ) + }) + .disposed(by: disposeBag) + + let identifierLookupController = controllers.userControllers?.identifierLookupController + identifierLookupController?.observable + .observe(on: MainScheduler.asyncInstance) + .subscribe(onNext: { [weak self, weak identifierLookupController] update in + guard let self, let identifierLookupController else { return } + let batchData = ItemsState.IdentifierLookupBatchData(batchData: identifierLookupController.batchData) + viewModel.process(action: .updateIdentifierLookup(update: update, batchData: batchData)) + }) + .disposed(by: disposeBag) + + let remoteDownloader = controllers.userControllers?.remoteFileDownloader + remoteDownloader?.observable + .observe(on: MainScheduler.asyncInstance) + .subscribe(onNext: { [weak self, weak remoteDownloader] update in + guard let self, let remoteDownloader else { return } + let batchData = ItemsState.DownloadBatchData(batchData: remoteDownloader.batchData) + viewModel.process(action: .updateRemoteDownload(update: update, batchData: batchData)) + }) + .disposed(by: disposeBag) + } + + func setupRecognizerObserver() { + let recognizerController = controllers.userControllers?.recognizerController + recognizerController?.updates + .observe(on: MainScheduler.asyncInstance) + .subscribe(onNext: { [weak viewModel] update in + guard let viewModel, case .createParentForItem(let libraryId, let key) = update.task.kind, viewModel.state.library.identifier == libraryId else { return } + viewModel.process(action: .updateMetadataRetrieval(itemKey: key, update: update.kind)) + }) + .disposed(by: disposeBag) + } + + func setupAppStateObserver() { + NotificationCenter.default + .rx + .notification(UIContentSizeCategory.didChangeNotification) + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + viewModel.process(action: .clearTitleCache) + handler?.reloadAll() + }) + .disposed(by: disposeBag) + } } deinit { @@ -78,9 +160,15 @@ final class ItemsViewController: BaseItemsViewController { self.startObserving(results: results) } else if state.changes.contains(.attachmentsRemoved) { handler?.attachmentAccessoriesChanged() - } else if let key = state.updateItemKey { - let accessory = state.itemAccessories[key].flatMap({ ItemCellModel.createAccessory(from: $0, fileDownloader: controllers.userControllers?.fileDownloader) }) - handler?.updateCell(key: key, withAccessory: accessory) + } else if let itemUpdate = state.updateItem { + switch itemUpdate.kind { + case .accessory: + let accessory = state.itemAccessories[itemUpdate.key].flatMap({ ItemCellModel.createAccessory(from: $0, fileDownloader: controllers.userControllers?.fileDownloader) }) + handler?.updateCell(key: itemUpdate.key, withAccessory: accessory) + + case .subtitle(let subtitle): + handler?.updateCell(key: itemUpdate.key, withSubtitle: subtitle) + } } if state.changes.contains(.editing) { @@ -171,13 +259,7 @@ final class ItemsViewController: BaseItemsViewController { file.mimeType == "application/pdf", let recognizerController = controllers.userControllers?.recognizerController else { return } - recognizerController.queue(task: RecognizerController.RecognizerTask(file: file, kind: .createParentForItem(libraryId: library.identifier, key: key))) { [weak self] observable in - guard let self else { return } - observable?.subscribe { _ in - // TODO: Implement update of UI according to updates - } - .disposed(by: disposeBag) - } + recognizerController.queue(task: RecognizerController.RecognizerTask(file: file, kind: .createParentForItem(libraryId: library.identifier, key: key))) case .duplicate: guard let key = selectedKeys.first else { return } @@ -308,72 +390,6 @@ final class ItemsViewController: BaseItemsViewController { ) } - // MARK: - Setups - - private func setupAppStateObserver() { - NotificationCenter.default - .rx - .notification(UIContentSizeCategory.didChangeNotification) - .observe(on: MainScheduler.instance) - .subscribe(onNext: { [weak self] _ in - self?.viewModel.process(action: .clearTitleCache) - self?.handler?.reloadAll() - }) - .disposed(by: disposeBag) - } - - private func setupFileObservers() { - NotificationCenter.default - .rx - .notification(.attachmentFileDeleted) - .observe(on: MainScheduler.instance) - .subscribe(onNext: { [weak self] notification in - if let notification = notification.object as? AttachmentFileDeletedNotification { - self?.viewModel.process(action: .updateAttachments(notification)) - } - }) - .disposed(by: self.disposeBag) - - let downloader = controllers.userControllers?.fileDownloader - downloader?.observable - .observe(on: MainScheduler.asyncInstance) - .subscribe(onNext: { [weak self, weak downloader] update in - guard let self else { return } - process( - downloadUpdate: update, - toOpen: viewModel.state.attachmentToOpen, - downloader: downloader, - dataUpdate: { batchData in - self.viewModel.process(action: .updateDownload(update: update, batchData: batchData)) - }, - attachmentWillOpen: { update in - self.viewModel.process(action: .attachmentOpened(update.key)) - } - ) - }) - .disposed(by: disposeBag) - - let identifierLookupController = controllers.userControllers?.identifierLookupController - identifierLookupController?.observable - .observe(on: MainScheduler.asyncInstance) - .subscribe(onNext: { [weak self, weak identifierLookupController] update in - guard let self, let identifierLookupController else { return } - let batchData = ItemsState.IdentifierLookupBatchData(batchData: identifierLookupController.batchData) - viewModel.process(action: .updateIdentifierLookup(update: update, batchData: batchData)) - }) - .disposed(by: self.disposeBag) - - let remoteDownloader = controllers.userControllers?.remoteFileDownloader - remoteDownloader?.observable - .observe(on: MainScheduler.asyncInstance) - .subscribe(onNext: { [weak self, weak remoteDownloader] update in - guard let self, let remoteDownloader else { return } - let batchData = ItemsState.DownloadBatchData(batchData: remoteDownloader.batchData) - viewModel.process(action: .updateRemoteDownload(update: update, batchData: batchData)) - }) - .disposed(by: disposeBag) - } - private func rightBarButtonItemTypes(for state: ItemsState) -> [RightBarButtonItem] { let selectItems = rightBarButtonSelectItemTypes(for: state) return state.library.metadataEditable ? [.add] + selectItems : selectItems diff --git a/Zotero/Scenes/Detail/Items/Views/RItemsTableViewDataSource.swift b/Zotero/Scenes/Detail/Items/Views/RItemsTableViewDataSource.swift index 859c4ca9a..9ab7bf30e 100644 --- a/Zotero/Scenes/Detail/Items/Views/RItemsTableViewDataSource.swift +++ b/Zotero/Scenes/Detail/Items/Views/RItemsTableViewDataSource.swift @@ -41,13 +41,15 @@ final class RItemsTableViewDataSource: NSObject { private unowned let viewModel: ViewModel private unowned let schemaController: SchemaController private unowned let fileDownloader: AttachmentDownloader? + private unowned let recognizerController: RecognizerController? private var snapshot: Results? weak var handler: ItemsTableViewHandler? - init(viewModel: ViewModel, fileDownloader: AttachmentDownloader?, schemaController: SchemaController) { + init(viewModel: ViewModel, fileDownloader: AttachmentDownloader?, recognizerController: RecognizerController?, schemaController: SchemaController) { self.viewModel = viewModel self.fileDownloader = fileDownloader + self.recognizerController = recognizerController self.schemaController = schemaController } @@ -229,7 +231,7 @@ extension RItemsTableViewDataSource { let title = createTitleIfNeeded() let accessory = accessory(forKey: item.key) let typeName = schemaController.localized(itemType: item.rawType) ?? item.rawType - return ItemCellModel(item: item, typeName: typeName, title: title, accessory: accessory, fileDownloader: fileDownloader) + return ItemCellModel(item: item, typeName: typeName, title: title, accessory: accessory, fileDownloader: fileDownloader, recognizerController: recognizerController) func createTitleIfNeeded() -> NSAttributedString { if let title = viewModel.state.itemTitles[item.key] { diff --git a/Zotero/Scenes/Detail/Trash/Views/TrashTableViewDataSource.swift b/Zotero/Scenes/Detail/Trash/Views/TrashTableViewDataSource.swift index 3840d19d6..6313c580d 100644 --- a/Zotero/Scenes/Detail/Trash/Views/TrashTableViewDataSource.swift +++ b/Zotero/Scenes/Detail/Trash/Views/TrashTableViewDataSource.swift @@ -155,7 +155,7 @@ extension TrashTableViewDataSource { let data = viewModel.state.itemDataCache[key] if let item = object as? RItem { let typeName = schemaController.localized(itemType: item.rawType) ?? item.rawType - return ItemCellModel(item: item, typeName: typeName, title: data?.title ?? NSAttributedString(), accessory: data?.accessory, fileDownloader: fileDownloader) + return ItemCellModel(item: item, typeName: typeName, title: data?.title ?? NSAttributedString(), accessory: data?.accessory, fileDownloader: fileDownloader, recognizerController: nil) } else { return ItemCellModel(collectionWithKey: object.key, title: data?.title ?? NSAttributedString()) } diff --git a/Zotero/Scenes/Detail/Trash/Views/TrashViewController.swift b/Zotero/Scenes/Detail/Trash/Views/TrashViewController.swift index 87cad44a8..384cb29f4 100644 --- a/Zotero/Scenes/Detail/Trash/Views/TrashViewController.swift +++ b/Zotero/Scenes/Detail/Trash/Views/TrashViewController.swift @@ -82,7 +82,7 @@ final class TrashViewController: BaseItemsViewController { self?.viewModel.process(action: .updateAttachments(notification)) } }) - .disposed(by: self.disposeBag) + .disposed(by: disposeBag) } } From 1160d584bdda4ff94fb8290ec277d5eed276eaf9 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Fri, 21 Feb 2025 14:32:40 +0200 Subject: [PATCH 09/33] Improve PDFWorkerController --- Zotero/Controllers/PDFWorkerController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index e073d610f..4a3b7b807 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -66,20 +66,20 @@ final class PDFWorkerController { } // MARK: Actions - func queue(work: PDFWork, completion: @escaping (_ observable: PublishSubject?) -> Void) { + func queue(work: PDFWork, completion: @escaping (_ observable: Observable?) -> Void) { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { completion(nil) return } if let (_, observable) = queue[work] { - completion(observable) + completion(observable.asObservable()) return } let state: PDFWorkState = .enqueued let observable: PublishSubject = PublishSubject() queue[work] = (state, observable) - completion(observable) + completion(observable.asObservable()) startWorkIfNeeded() } From daf1203ff2dbc1b025d96d42c5fae9614659b070 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Fri, 21 Feb 2025 15:26:45 +0200 Subject: [PATCH 10/33] Add PDFWorkerController tests --- Zotero.xcodeproj/project.pbxproj | 12 + ZoteroTests/JSONs/bitcoin_pdf_full_text.json | 5 + .../JSONs/bitcoin_pdf_recognizer_data.json | 41629 ++++++++++++++++ ZoteroTests/PDFWorkerControllerSpec.swift | 146 + 4 files changed, 41792 insertions(+) create mode 100644 ZoteroTests/JSONs/bitcoin_pdf_full_text.json create mode 100644 ZoteroTests/JSONs/bitcoin_pdf_recognizer_data.json create mode 100644 ZoteroTests/PDFWorkerControllerSpec.swift diff --git a/Zotero.xcodeproj/project.pbxproj b/Zotero.xcodeproj/project.pbxproj index a0d87dc5f..17d5a0917 100644 --- a/Zotero.xcodeproj/project.pbxproj +++ b/Zotero.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 6102D2B22D68ABF100505E6A /* bitcoin_pdf_recognizer_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 6102D2B12D68ABD900505E6A /* bitcoin_pdf_recognizer_data.json */; }; + 6102D2B62D68B5E800505E6A /* bitcoin_pdf_full_text.json in Resources */ = {isa = PBXBuildFile; fileRef = 6102D2B52D68B5DE00505E6A /* bitcoin_pdf_full_text.json */; }; 61099E6E2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m in Sources */ = {isa = PBXBuildFile; fileRef = 61099E6D2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m */; }; 61099E6F2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m in Sources */ = {isa = PBXBuildFile; fileRef = 61099E6D2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m */; }; 61168F612D50D4B6005495E8 /* PDFWorkerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61168F602D50D4B6005495E8 /* PDFWorkerController.swift */; }; @@ -49,6 +51,7 @@ 618D83E82BAAC88C00E7966B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 618D83E62BAAC88C00E7966B /* PrivacyInfo.xcprivacy */; }; 61A0C8472B8F669C0048FF92 /* PSPDFKitUI+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A0C8462B8F669B0048FF92 /* PSPDFKitUI+Extensions.swift */; }; 61ABA7512A6137D1002A4219 /* ShareableImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ABA7502A6137D1002A4219 /* ShareableImage.swift */; }; + 61AD977D2D67F42A000FDF45 /* PDFWorkerControllerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61AD977C2D67F42A000FDF45 /* PDFWorkerControllerSpec.swift */; }; 61BD13952A5831EF008A0704 /* TextKit1TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61BD13942A5831EF008A0704 /* TextKit1TextView.swift */; }; 61C817F22A49B5D30085B1E6 /* CollectionResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3B1EDEE250242E700D8BC1E /* CollectionResponseSpec.swift */; }; 61DE64D52C5BD0250096776C /* FormattedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DE64D42C5BD0250096776C /* FormattedTextView.swift */; }; @@ -1303,6 +1306,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 6102D2B12D68ABD900505E6A /* bitcoin_pdf_recognizer_data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitcoin_pdf_recognizer_data.json; sourceTree = ""; }; + 6102D2B52D68B5DE00505E6A /* bitcoin_pdf_full_text.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitcoin_pdf_full_text.json; sourceTree = ""; }; 61099E6A2C91BAF300EDD92C /* Zotero-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Zotero-Bridging-Header.h"; sourceTree = ""; }; 61099E6B2C91BAF300EDD92C /* ZShare-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ZShare-Bridging-Header.h"; sourceTree = ""; }; 61099E6C2C91BAF300EDD92C /* NSDecimalNumber+Rounding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDecimalNumber+Rounding.h"; sourceTree = ""; }; @@ -1325,6 +1330,7 @@ 618D83E62BAAC88C00E7966B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 61A0C8462B8F669B0048FF92 /* PSPDFKitUI+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PSPDFKitUI+Extensions.swift"; sourceTree = ""; }; 61ABA7502A6137D1002A4219 /* ShareableImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareableImage.swift; sourceTree = ""; }; + 61AD977C2D67F42A000FDF45 /* PDFWorkerControllerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFWorkerControllerSpec.swift; sourceTree = ""; }; 61BD13942A5831EF008A0704 /* TextKit1TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextKit1TextView.swift; sourceTree = ""; }; 61DE64D42C5BD0250096776C /* FormattedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedTextView.swift; sourceTree = ""; }; 61E24DCB2ABB385E00D75F50 /* OpenItemsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenItemsController.swift; sourceTree = ""; }; @@ -2936,6 +2942,7 @@ B3F4E4EB2A4DAA7D00820718 /* UpdatableObjectSpec.swift */, B3202C6B271048FF00485BE4 /* WebDavControllerSpec.swift */, B31DDAA02729A7DC002CFA05 /* WebDavCredentials.swift */, + 61AD977C2D67F42A000FDF45 /* PDFWorkerControllerSpec.swift */, ); path = ZoteroTests; sourceTree = ""; @@ -3056,6 +3063,8 @@ B32A3C83248008A2009E2C5D /* JSONs */ = { isa = PBXGroup; children = ( + 6102D2B12D68ABD900505E6A /* bitcoin_pdf_recognizer_data.json */, + 6102D2B52D68B5DE00505E6A /* bitcoin_pdf_full_text.json */, B3B1EDFB2502498100D8BC1E /* collectionresponse_knownfields.json */, B3B1EDFC2502498100D8BC1E /* collectionresponse_unknownfields.json */, B3B1EDF32502456000D8BC1E /* itemresponse_knownfields.json */, @@ -4600,6 +4609,7 @@ files = ( B32A3C90248008A2009E2C5D /* translators.xml in Resources */, B3B1C5892665258400883597 /* bitcoin.pdf in Resources */, + 6102D2B22D68ABF100505E6A /* bitcoin_pdf_recognizer_data.json in Resources */, B32A3C8C248008A2009E2C5D /* test_collection.json in Resources */, B32A3C8D248008A2009E2C5D /* test_search.json in Resources */, B3B1EDFD2502498100D8BC1E /* collectionresponse_knownfields.json in Resources */, @@ -4617,6 +4627,7 @@ B3AAABD82502A40900031065 /* searchresponse_knownfields.json in Resources */, B3B1EDF52502456000D8BC1E /* itemresponse_knownfields.json in Resources */, B36B8A542A4C2E930038BA1C /* test_annotation_dictionary_position.json in Resources */, + 6102D2B62D68B5E800505E6A /* bitcoin_pdf_full_text.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5640,6 +5651,7 @@ 6144B5D82A4ADDC400914B3C /* ItemTitleFormatterSpec.swift in Sources */, 6144B5DF2A4AE48F00914B3C /* SyncControllerSpec.swift in Sources */, B3243BC82A5EB2740033A7D6 /* HtmlAttributedStringConverterSpec.swift in Sources */, + 61AD977D2D67F42A000FDF45 /* PDFWorkerControllerSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ZoteroTests/JSONs/bitcoin_pdf_full_text.json b/ZoteroTests/JSONs/bitcoin_pdf_full_text.json new file mode 100644 index 000000000..b0cb4a7eb --- /dev/null +++ b/ZoteroTests/JSONs/bitcoin_pdf_full_text.json @@ -0,0 +1,5 @@ +{ + "totalPages" : 9, + "extractedPages" : 9, + "text" : "Bitcoin: A Peer-to-Peer Electronic Cash System\nSatoshi Nakamoto satoshin@gmx.com www.bitcoin.org\nAbstract. A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution. Digital signatures provide part of the solution, but the main benefits are lost if a trusted third party is still required to prevent double-spending. We propose a solution to the double-spending problem using a peer-to-peer network. The network timestamps transactions by hashing them into an ongoing chain of hash-based proof-of-work, forming a record that cannot be changed without redoing the proof-of-work. The longest chain not only serves as proof of the sequence of events witnessed, but proof that it came from the largest pool of CPU power. As long as a majority of CPU power is controlled by nodes that are not cooperating to attack the network, they'll generate the longest chain and outpace attackers. The network itself requires minimal structure. Messages are broadcast on a best effort basis, and nodes can leave and rejoin the network at will, accepting the longest proof-of-work chain as proof of what happened while they were gone.\n1. Introduction\nCommerce on the Internet has come to rely almost exclusively on financial institutions serving as trusted third parties to process electronic payments. While the system works well enough for most transactions, it still suffers from the inherent weaknesses of the trust based model. Completely non-reversible transactions are not really possible, since financial institutions cannot avoid mediating disputes. The cost of mediation increases transaction costs, limiting the minimum practical transaction size and cutting off the possibility for small casual transactions, and there is a broader cost in the loss of ability to make non-reversible payments for nonreversible services. With the possibility of reversal, the need for trust spreads. Merchants must be wary of their customers, hassling them for more information than they would otherwise need. A certain percentage of fraud is accepted as unavoidable. These costs and payment uncertainties can be avoided in person by using physical currency, but no mechanism exists to make payments over a communications channel without a trusted party. What is needed is an electronic payment system based on cryptographic proof instead of trust, allowing any two willing parties to transact directly with each other without the need for a trusted third party. Transactions that are computationally impractical to reverse would protect sellers from fraud, and routine escrow mechanisms could easily be implemented to protect buyers. In this paper, we propose a solution to the double-spending problem using a peer-to-peer distributed timestamp server to generate computational proof of the chronological order of transactions. The system is secure as long as honest nodes collectively control more CPU power than any cooperating group of attacker nodes.\n1\n\n\n\f2. Transactions\nWe define an electronic coin as a chain of digital signatures. Each owner transfers the coin to the next by digitally signing a hash of the previous transaction and the public key of the next owner and adding these to the end of the coin. A payee can verify the signatures to verify the chain of ownership.\nThe problem of course is the payee can't verify that one of the owners did not double-spend the coin. A common solution is to introduce a trusted central authority, or mint, that checks every transaction for double spending. After each transaction, the coin must be returned to the mint to issue a new coin, and only coins issued directly from the mint are trusted not to be double-spent. The problem with this solution is that the fate of the entire money system depends on the company running the mint, with every transaction having to go through them, just like a bank. We need a way for the payee to know that the previous owners did not sign any earlier transactions. For our purposes, the earliest transaction is the one that counts, so we don't care about later attempts to double-spend. The only way to confirm the absence of a transaction is to be aware of all transactions. In the mint based model, the mint was aware of all transactions and decided which arrived first. To accomplish this without a trusted party, transactions must be publicly announced [1], and we need a system for participants to agree on a single history of the order in which they were received. The payee needs proof that at the time of each transaction, the majority of nodes agreed it was the first received.\n3. Timestamp Server\nThe solution we propose begins with a timestamp server. A timestamp server works by taking a hash of a block of items to be timestamped and widely publishing the hash, such as in a newspaper or Usenet post [2-5]. The timestamp proves that the data must have existed at the time, obviously, in order to get into the hash. Each timestamp includes the previous timestamp in its hash, forming a chain, with each additional timestamp reinforcing the ones before it.\n2\nBlock\nItem Item ...\nHash\nBlock\nItem Item ...\nHash\nTransaction\nOwner 1's\nPublic Key\nOwner 0's\nSignature\nHash\nTransaction\nOwner 2's\nPublic Key\nOwner 1's\nSignature\nHash\nV e r i f y\nTransaction\nOwner 3's\nPublic Key\nOwner 2's\nSignature\nHash\nV e r i f y\nOwner 2's\nPrivate Key\nOwner 1's\nPrivate Key\nSign\nSign\nOwner 3's\nPrivate Key\n\n\n\f4. Proof-of-Work\nTo implement a distributed timestamp server on a peer-to-peer basis, we will need to use a proofof-work system similar to Adam Back's Hashcash [6], rather than newspaper or Usenet posts. The proof-of-work involves scanning for a value that when hashed, such as with SHA-256, the hash begins with a number of zero bits. The average work required is exponential in the number of zero bits required and can be verified by executing a single hash. For our timestamp network, we implement the proof-of-work by incrementing a nonce in the block until a value is found that gives the block's hash the required zero bits. Once the CPU effort has been expended to make it satisfy the proof-of-work, the block cannot be changed without redoing the work. As later blocks are chained after it, the work to change the block would include redoing all the blocks after it.\nThe proof-of-work also solves the problem of determining representation in majority decision making. If the majority were based on one-IP-address-one-vote, it could be subverted by anyone able to allocate many IPs. Proof-of-work is essentially one-CPU-one-vote. The majority decision is represented by the longest chain, which has the greatest proof-of-work effort invested in it. If a majority of CPU power is controlled by honest nodes, the honest chain will grow the fastest and outpace any competing chains. To modify a past block, an attacker would have to redo the proof-of-work of the block and all blocks after it and then catch up with and surpass the work of the honest nodes. We will show later that the probability of a slower attacker catching up diminishes exponentially as subsequent blocks are added. To compensate for increasing hardware speed and varying interest in running nodes over time, the proof-of-work difficulty is determined by a moving average targeting an average number of blocks per hour. If they're generated too fast, the difficulty increases.\n5. Network\nThe steps to run the network are as follows:\n1) New transactions are broadcast to all nodes. 2) Each node collects new transactions into a block. 3) Each node works on finding a difficult proof-of-work for its block. 4) When a node finds a proof-of-work, it broadcasts the block to all nodes. 5) Nodes accept the block only if all transactions in it are valid and not already spent. 6) Nodes express their acceptance of the block by working on creating the next block in the chain, using the hash of the accepted block as the previous hash.\nNodes always consider the longest chain to be the correct one and will keep working on extending it. If two nodes broadcast different versions of the next block simultaneously, some nodes may receive one or the other first. In that case, they work on the first one they received, but save the other branch in case it becomes longer. The tie will be broken when the next proofof-work is found and one branch becomes longer; the nodes that were working on the other branch will then switch to the longer one.\n3\nBlock\nPrev Hash Nonce\nTx Tx ...\nBlock\nPrev Hash Nonce\nTx Tx ...\n\n\n\fNew transaction broadcasts do not necessarily need to reach all nodes. As long as they reach many nodes, they will get into a block before long. Block broadcasts are also tolerant of dropped messages. If a node does not receive a block, it will request it when it receives the next block and realizes it missed one.\n6. Incentive\nBy convention, the first transaction in a block is a special transaction that starts a new coin owned by the creator of the block. This adds an incentive for nodes to support the network, and provides a way to initially distribute coins into circulation, since there is no central authority to issue them. The steady addition of a constant of amount of new coins is analogous to gold miners expending resources to add gold to circulation. In our case, it is CPU time and electricity that is expended. The incentive can also be funded with transaction fees. If the output value of a transaction is less than its input value, the difference is a transaction fee that is added to the incentive value of the block containing the transaction. Once a predetermined number of coins have entered circulation, the incentive can transition entirely to transaction fees and be completely inflation free. The incentive may help encourage nodes to stay honest. If a greedy attacker is able to assemble more CPU power than all the honest nodes, he would have to choose between using it to defraud people by stealing back his payments, or using it to generate new coins. He ought to find it more profitable to play by the rules, such rules that favour him with more new coins than everyone else combined, than to undermine the system and the validity of his own wealth.\n7. Reclaiming Disk Space\nOnce the latest transaction in a coin is buried under enough blocks, the spent transactions before it can be discarded to save disk space. To facilitate this without breaking the block's hash, transactions are hashed in a Merkle Tree [7][2][5], with only the root included in the block's hash. Old blocks can then be compacted by stubbing off branches of the tree. The interior hashes do not need to be stored.\nA block header with no transactions would be about 80 bytes. If we suppose blocks are generated every 10 minutes, 80 bytes * 6 * 24 * 365 = 4.2MB per year. With computer systems typically selling with 2GB of RAM as of 2008, and Moore's Law predicting current growth of 1.2GB per year, storage should not be a problem even if the block headers must be kept in memory.\n4\nBlock Block Header (Block Hash) Block\nPrev Hash Nonce\nHash01\nHash0 Hash1 Hash2 Hash3\nHash23\nRoot Hash\nHash01\nHash2\nTx3\nHash23\nBlock Header (Block Hash)\nRoot Hash\nTransactions Hashed in a Merkle Tree After Pruning Tx0-2 from the Block\nPrev Hash Nonce\nHash3\nTx0 Tx1 Tx2 Tx3\n\n\n\f8. Simplified Payment Verification\nIt is possible to verify payments without running a full network node. A user only needs to keep a copy of the block headers of the longest proof-of-work chain, which he can get by querying network nodes until he's convinced he has the longest chain, and obtain the Merkle branch linking the transaction to the block it's timestamped in. He can't check the transaction for himself, but by linking it to a place in the chain, he can see that a network node has accepted it, and blocks added after it further confirm the network has accepted it.\nAs such, the verification is reliable as long as honest nodes control the network, but is more vulnerable if the network is overpowered by an attacker. While network nodes can verify transactions for themselves, the simplified method can be fooled by an attacker's fabricated transactions for as long as the attacker can continue to overpower the network. One strategy to protect against this would be to accept alerts from network nodes when they detect an invalid block, prompting the user's software to download the full block and alerted transactions to confirm the inconsistency. Businesses that receive frequent payments will probably still want to run their own nodes for more independent security and quicker verification.\n9. Combining and Splitting Value\nAlthough it would be possible to handle coins individually, it would be unwieldy to make a separate transaction for every cent in a transfer. To allow value to be split and combined, transactions contain multiple inputs and outputs. Normally there will be either a single input from a larger previous transaction or multiple inputs combining smaller amounts, and at most two outputs: one for the payment, and one returning the change, if any, back to the sender.\nIt should be noted that fan-out, where a transaction depends on several transactions, and those transactions depend on many more, is not a problem here. There is never the need to extract a complete standalone copy of a transaction's history.\n5\nTransaction\nIn\n...\nIn Out\n...\nHash01\nHash2 Hash3\nHash23\nBlock Header\nMerkle Root\nPrev Hash Nonce\nBlock Header\nMerkle Root\nPrev Hash Nonce\nBlock Header\nMerkle Root\nPrev Hash Nonce\nMerkle Branch for Tx3\nLongest Proof-of-Work Chain\nTx3\n\n\n\f10. Privacy\nThe traditional banking model achieves a level of privacy by limiting access to information to the parties involved and the trusted third party. The necessity to announce all transactions publicly precludes this method, but privacy can still be maintained by breaking the flow of information in another place: by keeping public keys anonymous. The public can see that someone is sending an amount to someone else, but without information linking the transaction to anyone. This is similar to the level of information released by stock exchanges, where the time and size of individual trades, the \"tape\", is made public, but without telling who the parties were.\nAs an additional firewall, a new key pair should be used for each transaction to keep them from being linked to a common owner. Some linking is still unavoidable with multi-input transactions, which necessarily reveal that their inputs were owned by the same owner. The risk is that if the owner of a key is revealed, linking could reveal other transactions that belonged to the same owner.\n11. Calculations\nWe consider the scenario of an attacker trying to generate an alternate chain faster than the honest chain. Even if this is accomplished, it does not throw the system open to arbitrary changes, such as creating value out of thin air or taking money that never belonged to the attacker. Nodes are not going to accept an invalid transaction as payment, and honest nodes will never accept a block containing them. An attacker can only try to change one of his own transactions to take back money he recently spent. The race between the honest chain and an attacker chain can be characterized as a Binomial Random Walk. The success event is the honest chain being extended by one block, increasing its lead by +1, and the failure event is the attacker's chain being extended by one block, reducing the gap by -1. The probability of an attacker catching up from a given deficit is analogous to a Gambler's Ruin problem. Suppose a gambler with unlimited credit starts at a deficit and plays potentially an infinite number of trials to try to reach breakeven. We can calculate the probability he ever reaches breakeven, or that an attacker ever catches up with the honest chain, as follows [8]:\np = probability an honest node finds the next block q = probability the attacker finds the next block qz = probability the attacker will ever catch up from z blocks behind\nq z={ 1 if p≤q\nq\/ pz if pq}\n6\nIdentities Transactions Trusted\nThird Party Counterparty Public\nIdentities Transactions Public\nNew Privacy Model\nTraditional Privacy Model\n\n\n\fGiven our assumption that p > q, the probability drops exponentially as the number of blocks the attacker has to catch up with increases. With the odds against him, if he doesn't make a lucky lunge forward early on, his chances become vanishingly small as he falls further behind. We now consider how long the recipient of a new transaction needs to wait before being sufficiently certain the sender can't change the transaction. We assume the sender is an attacker who wants to make the recipient believe he paid him for a while, then switch it to pay back to himself after some time has passed. The receiver will be alerted when that happens, but the sender hopes it will be too late. The receiver generates a new key pair and gives the public key to the sender shortly before signing. This prevents the sender from preparing a chain of blocks ahead of time by working on it continuously until he is lucky enough to get far enough ahead, then executing the transaction at that moment. Once the transaction is sent, the dishonest sender starts working in secret on a parallel chain containing an alternate version of his transaction. The recipient waits until the transaction has been added to a block and z blocks have been linked after it. He doesn't know the exact amount of progress the attacker has made, but assuming the honest blocks took the average expected time per block, the attacker's potential progress will be a Poisson distribution with expected value:\n=z q\np\nTo get the probability the attacker could still catch up now, we multiply the Poisson density for each amount of progress he could have made by the probability he could catch up from that point:\n∑\nk =0\n∞ k e−\nk ! ⋅{q \/ pz−k if k ≤ z\n1 if k z}\nRearranging to avoid summing the infinite tail of the distribution...\n1− k∑=0\nz k e−\nk ! 1−q \/ pz−k\nConverting to C code...\n#include double AttackerSuccessProbability(double q, int z)\n{\ndouble p = 1.0 - q;\ndouble lambda = z * (q \/ p); double sum = 1.0;\nint i, k; for (k = 0; k <= z; k++)\n{\ndouble poisson = exp(-lambda);\nfor (i = 1; i <= k; i++) poisson *= lambda \/ i;\nsum -= poisson * (1 - pow(q \/ p, z - k));\nr}eturn sum; }\n7\n\n\n\fRunning some results, we can see the probability drop off exponentially with z.\nq=0.1\nz=0 P=1.0000000 z=1 P=0.2045873\nz=2 P=0.0509779 z=3 P=0.0131722\nz=4 P=0.0034552 z=5 P=0.0009137\nz=6 P=0.0002428 z=7 P=0.0000647\nz=8 P=0.0000173 z=9 P=0.0000046\nz=10 P=0.0000012\nq=0.3\nz=0 P=1.0000000 z=5 P=0.1773523\nz=10 P=0.0416605 z=15 P=0.0101008\nz=20 P=0.0024804 z=25 P=0.0006132\nz=30 P=0.0001522 z=35 P=0.0000379\nz=40 P=0.0000095 z=45 P=0.0000024\nz=50 P=0.0000006\nSolving for P less than 0.1%...\nP < 0.001 q=0.10 z=5\nq=0.15 z=8 q=0.20 z=11\nq=0.25 z=15 q=0.30 z=24\nq=0.35 z=41 q=0.40 z=89\nq=0.45 z=340\n12. Conclusion\nWe have proposed a system for electronic transactions without relying on trust. We started with the usual framework of coins made from digital signatures, which provides strong control of ownership, but is incomplete without a way to prevent double-spending. To solve this, we proposed a peer-to-peer network using proof-of-work to record a public history of transactions that quickly becomes computationally impractical for an attacker to change if honest nodes control a majority of CPU power. The network is robust in its unstructured simplicity. Nodes work all at once with little coordination. They do not need to be identified, since messages are not routed to any particular place and only need to be delivered on a best effort basis. Nodes can leave and rejoin the network at will, accepting the proof-of-work chain as proof of what happened while they were gone. They vote with their CPU power, expressing their acceptance of valid blocks by working on extending them and rejecting invalid blocks by refusing to work on them. Any needed rules and incentives can be enforced with this consensus mechanism.\n8\n\n\n\fReferences\n[1] W. Dai, \"b-money,\" http:\/\/www.weidai.com\/bmoney.txt, 1998.\n[2] H. Massias, X.S. Avila, and J.-J. Quisquater, \"Design of a secure timestamping service with minimal trust requirements,\" In 20th Symposium on Information Theory in the Benelux, May 1999.\n[3] S. Haber, W.S. Stornetta, \"How to time-stamp a digital document,\" In Journal of Cryptology, vol 3, no 2, pages 99-111, 1991.\n[4] D. Bayer, S. Haber, W.S. Stornetta, \"Improving the efficiency and reliability of digital time-stamping,\" In Sequences II: Methods in Communication, Security and Computer Science, pages 329-334, 1993.\n[5] S. Haber, W.S. Stornetta, \"Secure names for bit-strings,\" In Proceedings of the 4th ACM Conference on Computer and Communications Security, pages 28-35, April 1997.\n[6] A. Back, \"Hashcash - a denial of service counter-measure,\" http:\/\/www.hashcash.org\/papers\/hashcash.pdf, 2002.\n[7] R.C. Merkle, \"Protocols for public key cryptosystems,\" In Proc. 1980 Symposium on Security and Privacy, IEEE Computer Society, pages 122-133, April 1980.\n[8] W. Feller, \"An introduction to probability theory and its applications,\" 1957.\n9" +} diff --git a/ZoteroTests/JSONs/bitcoin_pdf_recognizer_data.json b/ZoteroTests/JSONs/bitcoin_pdf_recognizer_data.json new file mode 100644 index 000000000..f8675f323 --- /dev/null +++ b/ZoteroTests/JSONs/bitcoin_pdf_recognizer_data.json @@ -0,0 +1,41629 @@ +{ + "pages" : [ + [ + 612, + 792, + [ + [ + [ + [ + 0, + 0, + 0, + 0, + [ + [ + [ + [ + 134.30000000000001, + 93.798199999999994, + 187.47200000000001, + 110.6283, + 14, + 0, + 107.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "Bitcoin" + ], + [ + 187.48599999999999, + 93.798199999999994, + 191.364, + 110.6283, + 14, + 1, + 107.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + ":" + ], + [ + 195.39599999999999, + 93.798199999999994, + 206.00800000000001, + 110.6283, + 14, + 1, + 107.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "A" + ], + [ + 209.99799999999999, + 93.798199999999994, + 301.75400000000002, + 110.6283, + 14, + 1, + 107.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "Peer-to-Peer" + ], + [ + 305.786, + 93.798199999999994, + 381.45600000000002, + 110.6283, + 14, + 1, + 107.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "Electronic" + ], + [ + 385.47399999999999, + 93.798199999999994, + 421.56599999999997, + 110.6283, + 14, + 1, + 107.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "Cash" + ], + [ + 425.584, + 93.798199999999994, + 477.846, + 110.6283, + 14, + 0, + 107.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "System" + ] + ] + ], + [ + [ + [ + 269, + 134.3998, + 298.69400000000002, + 145.5847, + 10.1, + 1, + 143.40000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "Satoshi" + ], + [ + 301.28969999999998, + 134.3998, + 343.24509999999998, + 145.5847, + 10.1, + 0, + 143.40000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "Nakamoto" + ] + ] + ], + [ + [ + [ + 266, + 145.99979999999999, + 326.34750000000003, + 157.18469999999999, + 10.1, + 0, + 155, + 0, + 0, + 0, + 0, + 0, + 1, + "satoshin@gmx" + ], + [ + 326.39800000000002, + 145.99979999999999, + 328.923, + 157.18469999999999, + 10.1, + 0, + 155, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 328.90280000000001, + 145.99979999999999, + 346.25459999999998, + 157.18469999999999, + 10.1, + 0, + 155, + 0, + 0, + 0, + 0, + 0, + 1, + "com" + ] + ] + ], + [ + [ + [ + 272.30000000000001, + 157.69980000000001, + 294.1968, + 168.88470000000001, + 10.1, + 0, + 166.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "www" + ], + [ + 293.49990000000003, + 157.69980000000001, + 296.0249, + 168.88470000000001, + 10.1, + 0, + 166.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 296.00470000000001, + 157.69980000000001, + 324.05239999999998, + 168.88470000000001, + 10.1, + 0, + 166.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "bitcoin" + ], + [ + 324.10289999999998, + 157.69980000000001, + 326.62790000000001, + 168.88470000000001, + 10.1, + 0, + 166.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 326.60770000000002, + 157.69980000000001, + 339.85890000000001, + 168.88470000000001, + 10.1, + 0, + 166.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "org" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 204.71260000000001, + 180.88669999999999, + 215.01169999999999, + 9.3000000000000007, + 0, + 213, + 0, + 0, + 0, + 0, + 0, + 2, + "Abstract" + ], + [ + 180.88669999999999, + 204.71260000000001, + 183.21170000000001, + 215.01169999999999, + 9.3000000000000007, + 1, + 213, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 191.50489999999999, + 204.71260000000001, + 198.21950000000001, + 215.01169999999999, + 9.3000000000000007, + 1, + 213, + 0, + 0, + 0, + 0, + 0, + 1, + "A" + ], + [ + 202.00460000000001, + 204.71260000000001, + 225.84979999999999, + 215.01169999999999, + 9.3000000000000007, + 1, + 213, + 0, + 0, + 0, + 0, + 0, + 1, + "purely" + ], + [ + 230.2022, + 204.71260000000001, + 275.41879999999998, + 215.01169999999999, + 9.3000000000000007, + 1, + 213, + 0, + 0, + 0, + 0, + 0, + 1, + "peer-to-peer" + ], + [ + 279.827, + 204.71260000000001, + 307.17829999999998, + 215.01169999999999, + 9.3000000000000007, + 1, + 213, + 0, + 0, + 0, + 0, + 0, + 1, + "version" + ], + [ + 311.53070000000002, + 204.71260000000001, + 319.23110000000003, + 215.01169999999999, + 9.3000000000000007, + 1, + 213, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 323.74160000000001, + 204.71260000000001, + 360.37430000000001, + 215.01169999999999, + 9.3000000000000007, + 1, + 213, + 0, + 0, + 0, + 0, + 0, + 1, + "electronic" + ], + [ + 364.75459999999998, + 204.71260000000001, + 381.2063, + 215.01169999999999, + 9.3000000000000007, + 1, + 213, + 0, + 0, + 0, + 0, + 0, + 1, + "cash" + ], + [ + 385.661, + 204.71260000000001, + 408.80869999999999, + 215.01169999999999, + 9.3000000000000007, + 1, + 213, + 0, + 0, + 0, + 0, + 0, + 1, + "would" + ], + [ + 413.26339999999999, + 204.71260000000001, + 433.89080000000001, + 215.01169999999999, + 9.3000000000000007, + 1, + 213, + 0, + 0, + 0, + 0, + 0, + 1, + "allow" + ], + [ + 438.28039999999999, + 204.71260000000001, + 461.60480000000001, + 215.01169999999999, + 9.3000000000000007, + 0, + 213, + 0, + 0, + 0, + 0, + 0, + 1, + "online" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 216.21260000000001, + 181.92830000000001, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "payments" + ], + [ + 185.90870000000001, + 216.21260000000001, + 193.1627, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 197.11519999999999, + 216.21260000000001, + 205.83860000000001, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 209.81899999999999, + 216.21260000000001, + 224.792, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "sent" + ], + [ + 228.81890000000001, + 216.21260000000001, + 257.27690000000001, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "directly" + ], + [ + 261.2294, + 216.21260000000001, + 279.25279999999998, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "from" + ], + [ + 283.22390000000001, + 216.21260000000001, + 296.6438, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "one" + ], + [ + 300.62419999999997, + 216.21260000000001, + 319.77289999999999, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "party" + ], + [ + 323.72539999999998, + 216.21260000000001, + 330.9794, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 334.93189999999998, + 216.21260000000001, + 362.83190000000002, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "another" + ], + [ + 366.83089999999999, + 216.21260000000001, + 395.20519999999999, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "without" + ], + [ + 399.2321, + 216.21260000000001, + 420.38959999999997, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "going" + ], + [ + 424.33280000000002, + 216.21260000000001, + 453.28370000000001, + 226.51169999999999, + 9.3000000000000007, + 1, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "through" + ], + [ + 457.2269, + 216.21260000000001, + 461.34679999999997, + 226.51169999999999, + 9.3000000000000007, + 0, + 224.5, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 227.71260000000001, + 178.7756, + 238.01169999999999, + 9.3000000000000007, + 1, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "financial" + ], + [ + 182.40260000000001, + 227.71260000000001, + 220.0676, + 238.01169999999999, + 9.3000000000000007, + 0, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "institution" + ], + [ + 220.11410000000001, + 227.71260000000001, + 222.4391, + 238.01169999999999, + 9.3000000000000007, + 1, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 229.61869999999999, + 227.71260000000001, + 255.4076, + 238.01169999999999, + 9.3000000000000007, + 1, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "Digital" + ], + [ + 259.03460000000001, + 227.71260000000001, + 296.65309999999999, + 238.01169999999999, + 9.3000000000000007, + 1, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "signatures" + ], + [ + 300.23360000000002, + 227.71260000000001, + 328.66370000000001, + 238.01169999999999, + 9.3000000000000007, + 1, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "provide" + ], + [ + 332.24419999999998, + 227.71260000000001, + 346.71499999999997, + 238.01169999999999, + 9.3000000000000007, + 1, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "part" + ], + [ + 350.34199999999998, + 227.71260000000001, + 358.04239999999999, + 238.01169999999999, + 9.3000000000000007, + 1, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 361.6508, + 227.71260000000001, + 372.96890000000002, + 238.01169999999999, + 9.3000000000000007, + 1, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 376.54939999999999, + 227.71260000000001, + 406.51400000000001, + 238.01169999999999, + 9.3000000000000007, + 0, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "solution" + ], + [ + 406.56049999999999, + 227.71260000000001, + 408.88549999999998, + 238.01169999999999, + 9.3000000000000007, + 1, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 412.46600000000001, + 227.71260000000001, + 424.34210000000002, + 238.01169999999999, + 9.3000000000000007, + 1, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "but" + ], + [ + 427.86680000000001, + 227.71260000000001, + 439.28719999999998, + 238.01169999999999, + 9.3000000000000007, + 1, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 442.86770000000001, + 227.71260000000001, + 461.4212, + 238.01169999999999, + 9.3000000000000007, + 0, + 236, + 0, + 0, + 0, + 0, + 0, + 1, + "main" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 239.21260000000001, + 175.7252, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "benefits" + ], + [ + 178.7012, + 239.21260000000001, + 190.01929999999999, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "are" + ], + [ + 192.99529999999999, + 239.21260000000001, + 206.37799999999999, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "lost" + ], + [ + 209.40049999999999, + 239.21260000000001, + 215.10140000000001, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "if" + ], + [ + 218.1053, + 239.21260000000001, + 222.2252, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 225.2012, + 239.21260000000001, + 250.46000000000001, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "trusted" + ], + [ + 253.5104, + 239.21260000000001, + 270.9665, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "third" + ], + [ + 274.01690000000002, + 239.21260000000001, + 293.16559999999998, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "party" + ], + [ + 296.11369999999999, + 239.21260000000001, + 302.33539999999999, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 305.31139999999999, + 239.21260000000001, + 319.19630000000001, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "still" + ], + [ + 322.21879999999999, + 239.21260000000001, + 353.16919999999999, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "required" + ], + [ + 356.1266, + 239.21260000000001, + 363.38060000000002, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 366.43099999999998, + 239.21260000000001, + 394.21010000000001, + 249.51169999999999, + 9.3000000000000007, + 1, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "prevent" + ], + [ + 397.23259999999999, + 239.21260000000001, + 459.17989999999998, + 249.51169999999999, + 9.3000000000000007, + 0, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "double-spending" + ], + [ + 459.22640000000001, + 239.21260000000001, + 461.5514, + 249.51169999999999, + 9.3000000000000007, + 0, + 247.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 250.71260000000001, + 158.4179, + 261.01170000000002, + 9.3000000000000007, + 1, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "We" + ], + [ + 160.7987, + 250.71260000000001, + 190.22389999999999, + 261.01170000000002, + 9.3000000000000007, + 1, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "propose" + ], + [ + 192.50239999999999, + 250.71260000000001, + 196.6223, + 261.01170000000002, + 9.3000000000000007, + 1, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 199.00309999999999, + 250.71260000000001, + 228.86539999999999, + 261.01170000000002, + 9.3000000000000007, + 1, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "solution" + ], + [ + 231.31129999999999, + 250.71260000000001, + 238.56530000000001, + 261.01170000000002, + 9.3000000000000007, + 1, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 240.8159, + 250.71260000000001, + 252.2363, + 261.01170000000002, + 9.3000000000000007, + 1, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 254.51480000000001, + 250.71260000000001, + 316.46210000000002, + 261.01170000000002, + 9.3000000000000007, + 1, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "double-spending" + ], + [ + 318.8057, + 250.71260000000001, + 349.84910000000002, + 261.01170000000002, + 9.3000000000000007, + 1, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "problem" + ], + [ + 352.11829999999998, + 250.71260000000001, + 372.26209999999998, + 261.01170000000002, + 9.3000000000000007, + 1, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "using" + ], + [ + 374.60570000000001, + 250.71260000000001, + 378.72559999999999, + 261.01170000000002, + 9.3000000000000007, + 1, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 381.10640000000001, + 250.71260000000001, + 426.32299999999998, + 261.01170000000002, + 9.3000000000000007, + 1, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "peer-to-peer" + ], + [ + 428.62009999999998, + 250.71260000000001, + 459.17059999999998, + 261.01170000000002, + 9.3000000000000007, + 0, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 459.1241, + 250.71260000000001, + 461.44909999999999, + 261.01170000000002, + 9.3000000000000007, + 0, + 259, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 262.21260000000001, + 160.715, + 272.51170000000002, + 9.3000000000000007, + 1, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 164.79769999999999, + 262.21260000000001, + 195.24590000000001, + 272.51170000000002, + 9.3000000000000007, + 1, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 199.3937, + 262.21260000000001, + 241.71799999999999, + 272.51170000000002, + 9.3000000000000007, + 1, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamps" + ], + [ + 245.80070000000001, + 262.21260000000001, + 290.22680000000003, + 272.51170000000002, + 9.3000000000000007, + 1, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 294.30950000000001, + 262.21260000000001, + 303.65600000000001, + 272.51170000000002, + 9.3000000000000007, + 1, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 307.60849999999999, + 262.21260000000001, + 336.55009999999999, + 272.51170000000002, + 9.3000000000000007, + 1, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "hashing" + ], + [ + 340.59559999999999, + 262.21260000000001, + 359.2235, + 272.51170000000002, + 9.3000000000000007, + 1, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "them" + ], + [ + 363.29689999999999, + 262.21260000000001, + 377.75839999999999, + 272.51170000000002, + 9.3000000000000007, + 1, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "into" + ], + [ + 381.90620000000001, + 262.21260000000001, + 390.55520000000001, + 272.51170000000002, + 9.3000000000000007, + 1, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "an" + ], + [ + 394.70299999999997, + 262.21260000000001, + 425.15120000000002, + 272.51170000000002, + 9.3000000000000007, + 1, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "ongoing" + ], + [ + 429.29899999999998, + 262.21260000000001, + 449.34980000000002, + 272.51170000000002, + 9.3000000000000007, + 1, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 453.49759999999998, + 262.21260000000001, + 461.19799999999998, + 272.51170000000002, + 9.3000000000000007, + 0, + 270.5, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 273.71260000000001, + 187.5455, + 284.01170000000002, + 9.3000000000000007, + 1, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "hash-based" + ], + [ + 190.09370000000001, + 273.71260000000001, + 243.34549999999999, + 284.01170000000002, + 9.3000000000000007, + 0, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 243.29900000000001, + 273.71260000000001, + 245.624, + 284.01170000000002, + 9.3000000000000007, + 1, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 248.20009999999999, + 273.71260000000001, + 278.14609999999999, + 284.01170000000002, + 9.3000000000000007, + 1, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "forming" + ], + [ + 280.6943, + 273.71260000000001, + 284.81420000000003, + 284.01170000000002, + 9.3000000000000007, + 1, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 287.29730000000001, + 273.71260000000001, + 311.04950000000002, + 284.01170000000002, + 9.3000000000000007, + 1, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "record" + ], + [ + 313.59769999999997, + 273.71260000000001, + 327.47329999999999, + 284.01170000000002, + 9.3000000000000007, + 1, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 330.09589999999997, + 273.71260000000001, + 354.77809999999999, + 284.01170000000002, + 9.3000000000000007, + 1, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "cannot" + ], + [ + 357.40069999999997, + 273.71260000000001, + 366.1241, + 284.01170000000002, + 9.3000000000000007, + 1, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 368.70949999999999, + 273.71260000000001, + 399.55759999999998, + 284.01170000000002, + 9.3000000000000007, + 1, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "changed" + ], + [ + 402.2081, + 273.71260000000001, + 430.58240000000001, + 284.01170000000002, + 9.3000000000000007, + 1, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "without" + ], + [ + 433.10270000000003, + 273.71260000000001, + 461.5514, + 284.01170000000002, + 9.3000000000000007, + 0, + 282, + 0, + 0, + 0, + 0, + 0, + 1, + "redoing" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 285.21260000000001, + 157.6181, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 161.0033, + 285.21260000000001, + 214.14349999999999, + 295.51170000000002, + 9.3000000000000007, + 0, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 214.19, + 285.21260000000001, + 216.51499999999999, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 222.98779999999999, + 285.21260000000001, + 237.40280000000001, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 240.78800000000001, + 285.21260000000001, + 267.572, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "longest" + ], + [ + 270.99439999999998, + 285.21260000000001, + 291.04520000000002, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 294.39319999999998, + 285.21260000000001, + 306.26929999999999, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "not" + ], + [ + 309.58940000000001, + 285.21260000000001, + 326.2364, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "only" + ], + [ + 329.4821, + 285.21260000000001, + 352.70420000000001, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "serves" + ], + [ + 355.9871, + 285.21260000000001, + 363.70609999999999, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 367.09129999999999, + 285.21260000000001, + 387.1979, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "proof" + ], + [ + 390.60169999999999, + 285.21260000000001, + 398.3021, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 401.70589999999999, + 285.21260000000001, + 413.024, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 416.30689999999998, + 285.21260000000001, + 450.42860000000002, + 295.51170000000002, + 9.3000000000000007, + 1, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "sequence" + ], + [ + 453.7115, + 285.21260000000001, + 461.4119, + 295.51170000000002, + 9.3000000000000007, + 0, + 293.5, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 296.71260000000001, + 170.02430000000001, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "events" + ], + [ + 173.40950000000001, + 296.71260000000001, + 210.0608, + 307.01170000000002, + 9.3000000000000007, + 0, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "witnessed" + ], + [ + 210.10730000000001, + 296.71260000000001, + 212.4323, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 215.8082, + 296.71260000000001, + 227.68430000000001, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "but" + ], + [ + 231.0044, + 296.71260000000001, + 251.11099999999999, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "proof" + ], + [ + 254.61709999999999, + 296.71260000000001, + 268.49270000000001, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 271.9151, + 296.71260000000001, + 276.99290000000002, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 280.4153, + 296.71260000000001, + 300.03829999999999, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "came" + ], + [ + 303.42349999999999, + 296.71260000000001, + 321.44690000000003, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "from" + ], + [ + 324.92509999999999, + 296.71260000000001, + 336.2432, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 339.6284, + 296.71260000000001, + 364.20830000000001, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "largest" + ], + [ + 367.63069999999999, + 296.71260000000001, + 384.1103, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "pool" + ], + [ + 387.53269999999998, + 296.71260000000001, + 395.23309999999998, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 398.63690000000003, + 296.71260000000001, + 416.75330000000002, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "CPU" + ], + [ + 420.13850000000002, + 296.71260000000001, + 443.34199999999998, + 307.01170000000002, + 9.3000000000000007, + 0, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "power" + ], + [ + 442.84910000000002, + 296.71260000000001, + 445.17410000000001, + 307.01170000000002, + 9.3000000000000007, + 1, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 451.45159999999998, + 296.71260000000001, + 461.76530000000002, + 307.01170000000002, + 9.3000000000000007, + 0, + 305, + 0, + 0, + 0, + 0, + 0, + 1, + "As" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 308.21260000000001, + 162.85400000000001, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "long" + ], + [ + 165.70910000000001, + 308.21260000000001, + 173.4281, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 176.31110000000001, + 308.21260000000001, + 180.43100000000001, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 183.31399999999999, + 308.21260000000001, + 214.8689, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "majority" + ], + [ + 217.72399999999999, + 308.21260000000001, + 225.42439999999999, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 228.33529999999999, + 308.21260000000001, + 246.45169999999999, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "CPU" + ], + [ + 249.34399999999999, + 308.21260000000001, + 272.54750000000001, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "power" + ], + [ + 275.45839999999998, + 308.21260000000001, + 281.68009999999998, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 284.56310000000002, + 308.21260000000001, + 322.22809999999998, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "controlled" + ], + [ + 325.08319999999998, + 308.21260000000001, + 334.42970000000003, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 337.28480000000002, + 308.21260000000001, + 359.00029999999998, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 361.88330000000002, + 308.21260000000001, + 375.75889999999998, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 378.6884, + 308.21260000000001, + 390.00650000000002, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "are" + ], + [ + 392.8895, + 308.21260000000001, + 404.76560000000001, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "not" + ], + [ + 407.69510000000002, + 308.21260000000001, + 451.5539, + 318.51170000000002, + 9.3000000000000007, + 1, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "cooperating" + ], + [ + 454.50200000000001, + 308.21260000000001, + 461.75599999999997, + 318.51170000000002, + 9.3000000000000007, + 0, + 316.5, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 319.71260000000001, + 168.46190000000001, + 330.01170000000002, + 9.3000000000000007, + 1, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "attack" + ], + [ + 172.11680000000001, + 319.71260000000001, + 183.53720000000001, + 330.01170000000002, + 9.3000000000000007, + 1, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 187.22, + 319.71260000000001, + 217.66820000000001, + 330.01170000000002, + 9.3000000000000007, + 0, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 217.6217, + 319.71260000000001, + 219.94669999999999, + 330.01170000000002, + 9.3000000000000007, + 1, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 223.7225, + 319.71260000000001, + 246.50749999999999, + 330.01170000000002, + 9.3000000000000007, + 1, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "they'll" + ], + [ + 250.23679999999999, + 319.71260000000001, + 281.66149999999999, + 330.01170000000002, + 9.3000000000000007, + 1, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "generate" + ], + [ + 285.34429999999998, + 319.71260000000001, + 296.7647, + 330.01170000000002, + 9.3000000000000007, + 1, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 300.43819999999999, + 319.71260000000001, + 327.22219999999999, + 330.01170000000002, + 9.3000000000000007, + 1, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "longest" + ], + [ + 330.95150000000001, + 319.71260000000001, + 351.00229999999999, + 330.01170000000002, + 9.3000000000000007, + 1, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 354.7595, + 319.71260000000001, + 368.11430000000001, + 330.01170000000002, + 9.3000000000000007, + 1, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 371.87150000000003, + 319.71260000000001, + 400.80380000000002, + 330.01170000000002, + 9.3000000000000007, + 1, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "outpace" + ], + [ + 404.48660000000001, + 319.71260000000001, + 437.51089999999999, + 330.01170000000002, + 9.3000000000000007, + 0, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "attackers" + ], + [ + 437.4923, + 319.71260000000001, + 439.81729999999999, + 330.01170000000002, + 9.3000000000000007, + 1, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 446.99689999999998, + 319.71260000000001, + 461.51420000000002, + 330.01170000000002, + 9.3000000000000007, + 0, + 328, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 331.21260000000001, + 176.7482, + 341.51170000000002, + 9.3000000000000007, + 1, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 179.89160000000001, + 331.21260000000001, + 198.5009, + 341.51170000000002, + 9.3000000000000007, + 1, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "itself" + ], + [ + 201.70009999999999, + 331.21260000000001, + 231.6182, + 341.51170000000002, + 9.3000000000000007, + 1, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "requires" + ], + [ + 234.7988, + 331.21260000000001, + 265.77710000000002, + 341.51170000000002, + 9.3000000000000007, + 1, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "minimal" + ], + [ + 269.00420000000003, + 331.21260000000001, + 301.52629999999999, + 341.51170000000002, + 9.3000000000000007, + 0, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "structure" + ], + [ + 301.5077, + 331.21260000000001, + 303.83269999999999, + 341.51170000000002, + 9.3000000000000007, + 1, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 310.20319999999998, + 331.21260000000001, + 346.31509999999997, + 341.51170000000002, + 9.3000000000000007, + 1, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "Messages" + ], + [ + 349.4957, + 331.21260000000001, + 360.81380000000001, + 341.51170000000002, + 9.3000000000000007, + 1, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "are" + ], + [ + 363.99439999999998, + 331.21260000000001, + 399.57619999999997, + 341.51170000000002, + 9.3000000000000007, + 1, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "broadcast" + ], + [ + 402.79399999999998, + 331.21260000000001, + 412.04750000000001, + 341.51170000000002, + 9.3000000000000007, + 1, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 415.29320000000001, + 331.21260000000001, + 419.41309999999999, + 341.51170000000002, + 9.3000000000000007, + 1, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 422.59370000000001, + 331.21260000000001, + 437.57600000000002, + 341.51170000000002, + 9.3000000000000007, + 1, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "best" + ], + [ + 440.80309999999997, + 331.21260000000001, + 461.17939999999999, + 341.51170000000002, + 9.3000000000000007, + 0, + 339.5, + 0, + 0, + 0, + 0, + 0, + 1, + "effort" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 342.71260000000001, + 164.82560000000001, + 353.01170000000002, + 9.3000000000000007, + 0, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "basis" + ], + [ + 164.80699999999999, + 342.71260000000001, + 167.13200000000001, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 171.2054, + 342.71260000000001, + 184.56020000000001, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 188.708, + 342.71260000000001, + 210.3212, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 214.40389999999999, + 342.71260000000001, + 227.25649999999999, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "can" + ], + [ + 231.30199999999999, + 342.71260000000001, + 250.92500000000001, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "leave" + ], + [ + 254.90539999999999, + 342.71260000000001, + 268.35320000000002, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 272.39870000000002, + 342.71260000000001, + 294.04910000000001, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "rejoin" + ], + [ + 298.09460000000001, + 342.71260000000001, + 309.51499999999999, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 313.49540000000002, + 342.71260000000001, + 343.9436, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 347.98910000000001, + 342.71260000000001, + 354.66649999999998, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "at" + ], + [ + 358.6841, + 342.71260000000001, + 373.16419999999999, + 353.01170000000002, + 9.3000000000000007, + 0, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "will" + ], + [ + 373.19209999999998, + 342.71260000000001, + 375.51710000000003, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 379.48820000000001, + 342.71260000000001, + 415.15370000000001, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "accepting" + ], + [ + 419.19920000000002, + 342.71260000000001, + 430.51729999999998, + 353.01170000000002, + 9.3000000000000007, + 1, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 434.60000000000002, + 342.71260000000001, + 461.38400000000001, + 353.01170000000002, + 9.3000000000000007, + 0, + 351, + 0, + 0, + 0, + 0, + 0, + 1, + "longest" + ] + ] + ], + [ + [ + [ + 146.30000000000001, + 354.21260000000001, + 199.4402, + 364.51170000000002, + 9.3000000000000007, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 201.78380000000001, + 354.21260000000001, + 221.93690000000001, + 364.51170000000002, + 9.3000000000000007, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 224.28049999999999, + 354.21260000000001, + 231.99950000000001, + 364.51170000000002, + 9.3000000000000007, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 234.27799999999999, + 354.21260000000001, + 254.38460000000001, + 364.51170000000002, + 9.3000000000000007, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "proof" + ], + [ + 256.78399999999999, + 354.21260000000001, + 264.48439999999999, + 364.51170000000002, + 9.3000000000000007, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 266.88380000000001, + 354.21260000000001, + 284.95370000000003, + 364.51170000000002, + 9.3000000000000007, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "what" + ], + [ + 287.27870000000001, + 354.21260000000001, + 322.83260000000001, + 364.51170000000002, + 9.3000000000000007, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "happened" + ], + [ + 325.17619999999999, + 354.21260000000001, + 345.89659999999998, + 364.51170000000002, + 9.3000000000000007, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "while" + ], + [ + 348.17509999999999, + 354.21260000000001, + 364.2269, + 364.51170000000002, + 9.3000000000000007, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "they" + ], + [ + 366.57979999999998, + 354.21260000000001, + 384.59390000000002, + 364.51170000000002, + 9.3000000000000007, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "were" + ], + [ + 386.97469999999998, + 354.21260000000001, + 404.99810000000002, + 364.51170000000002, + 9.3000000000000007, + 0, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "gone" + ], + [ + 404.97949999999997, + 354.21260000000001, + 407.30450000000002, + 364.51170000000002, + 9.3000000000000007, + 0, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 385.6628, + 114.70099999999999, + 399.48750000000001, + 11.5, + 0, + 397, + 0, + 0, + 0, + 0, + 0, + 0, + "1" + ], + [ + 114.70099999999999, + 385.6628, + 117.8865, + 399.48750000000001, + 11.5, + 1, + 397, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 133.30000000000001, + 385.6628, + 210.18899999999999, + 399.48750000000001, + 11.5, + 0, + 397, + 0, + 0, + 0, + 0, + 0, + 0, + "Introduction" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 407.19979999999998, + 152.3784, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "Commerce" + ], + [ + 155.09530000000001, + 407.19979999999998, + 165.2458, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 167.892, + 407.19979999999998, + 180.1635, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 182.88040000000001, + 407.19979999999998, + 214.29140000000001, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "Internet" + ], + [ + 216.9881, + 407.19979999999998, + 230.52209999999999, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "has" + ], + [ + 233.08750000000001, + 407.19979999999998, + 254.9641, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "come" + ], + [ + 257.68099999999998, + 407.19979999999998, + 265.52870000000001, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 268.17489999999998, + 407.19979999999998, + 283.93090000000001, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "rely" + ], + [ + 286.57709999999997, + 407.19979999999998, + 313.47340000000003, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "almost" + ], + [ + 316.17009999999999, + 407.19979999999998, + 362.22609999999997, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "exclusively" + ], + [ + 364.8723, + 407.19979999999998, + 375.02280000000002, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 377.66899999999998, + 407.19979999999998, + 412.97859999999997, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "financial" + ], + [ + 415.67529999999999, + 407.19979999999998, + 460.58999999999997, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "institutions" + ], + [ + 463.25639999999999, + 407.19979999999998, + 492.91000000000003, + 418.38470000000001, + 10.1, + 1, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "serving" + ], + [ + 495.65719999999999, + 407.19979999999998, + 504.09070000000003, + 418.38470000000001, + 10.1, + 0, + 416.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 418.7998, + 135.55179999999999, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "trusted" + ], + [ + 139.5009, + 418.7998, + 158.54949999999999, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "third" + ], + [ + 162.49860000000001, + 418.7998, + 189.43530000000001, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "parties" + ], + [ + 193.31370000000001, + 418.7998, + 201.06039999999999, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 205.0095, + 418.7998, + 235.34989999999999, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "process" + ], + [ + 239.2182, + 418.7998, + 279.00209999999998, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "electronic" + ], + [ + 282.93099999999998, + 418.7998, + 321.66449999999998, + 429.98469999999998, + 10.1, + 0, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "payments" + ], + [ + 321.63420000000002, + 418.7998, + 324.1592, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 331.73419999999999, + 418.7998, + 356.4085, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "While" + ], + [ + 360.2364, + 418.7998, + 372.60890000000001, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 376.5378, + 418.7998, + 404.5754, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "system" + ], + [ + 408.43360000000001, + 418.7998, + 433.16849999999999, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "works" + ], + [ + 437.04689999999999, + 418.7998, + 454.34820000000002, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "well" + ], + [ + 458.25689999999997, + 418.7998, + 488.01150000000001, + 429.98469999999998, + 10.1, + 1, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "enough" + ], + [ + 491.8596, + 418.7998, + 503.62610000000001, + 429.98469999999998, + 10.1, + 0, + 427.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 430.49979999999999, + 127.694, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "most" + ], + [ + 133.60249999999999, + 430.49979999999999, + 181.84010000000001, + 441.68470000000002, + 10.1, + 0, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 181.8098, + 430.49979999999999, + 184.3348, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 190.21299999999999, + 430.49979999999999, + 195.80840000000001, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 201.71690000000001, + 430.49979999999999, + 216.80629999999999, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "still" + ], + [ + 222.6138, + 430.49979999999999, + 249.9545, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "suffers" + ], + [ + 255.82259999999999, + 430.49979999999999, + 275.47719999999998, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "from" + ], + [ + 281.33519999999999, + 430.49979999999999, + 293.60669999999999, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 299.43439999999998, + 430.49979999999999, + 332.54219999999998, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "inherent" + ], + [ + 338.34969999999998, + 430.49979999999999, + 385.49650000000003, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "weaknesses" + ], + [ + 391.3646, + 430.49979999999999, + 399.82839999999999, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 405.5652, + 430.49979999999999, + 417.93770000000001, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 423.7654, + 430.49979999999999, + 441.6626, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "trust" + ], + [ + 447.5711, + 430.49979999999999, + 470.52839999999998, + 441.68470000000002, + 10.1, + 1, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "based" + ], + [ + 476.37630000000001, + 430.49979999999999, + 501.67680000000001, + 441.68470000000002, + 10.1, + 0, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "model" + ], + [ + 501.67680000000001, + 430.49979999999999, + 504.20179999999999, + 441.68470000000002, + 10.1, + 0, + 439.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 442.09980000000002, + 155.24680000000001, + 453.28469999999999, + 10.1, + 1, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "Completely" + ], + [ + 158.297, + 442.09980000000002, + 216.58410000000001, + 453.28469999999999, + 10.1, + 1, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "non-reversible" + ], + [ + 219.70500000000001, + 442.09980000000002, + 267.94260000000003, + 453.28469999999999, + 10.1, + 1, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 270.91199999999998, + 442.09980000000002, + 283.2946, + 453.28469999999999, + 10.1, + 1, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "are" + ], + [ + 286.31450000000001, + 442.09980000000002, + 299.2122, + 453.28469999999999, + 10.1, + 1, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "not" + ], + [ + 302.21190000000001, + 442.09980000000002, + 325.27019999999999, + 453.28469999999999, + 10.1, + 1, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "really" + ], + [ + 328.32040000000001, + 442.09980000000002, + 361.38780000000003, + 453.28469999999999, + 10.1, + 0, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "possible" + ], + [ + 361.41809999999998, + 442.09980000000002, + 363.94310000000002, + 453.28469999999999, + 10.1, + 1, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 366.91250000000002, + 442.09980000000002, + 387.68819999999999, + 453.28469999999999, + 10.1, + 1, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "since" + ], + [ + 390.7081, + 442.09980000000002, + 426.01769999999999, + 453.28469999999999, + 10.1, + 1, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "financial" + ], + [ + 429.11840000000001, + 442.09980000000002, + 474.03309999999999, + 453.28469999999999, + 10.1, + 1, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "institutions" + ], + [ + 477.0025, + 442.09980000000002, + 503.90890000000002, + 453.28469999999999, + 10.1, + 0, + 451.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "cannot" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 453.7998, + 130.5523, + 464.98469999999998, + 10.1, + 1, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "avoid" + ], + [ + 136.19820000000001, + 453.7998, + 176.54769999999999, + 464.98469999999998, + 10.1, + 1, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "mediating" + ], + [ + 182.2946, + 453.7998, + 215.42259999999999, + 464.98469999999998, + 10.1, + 0, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "disputes" + ], + [ + 215.39230000000001, + 453.7998, + 217.91730000000001, + 464.98469999999998, + 10.1, + 1, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 229.08789999999999, + 453.7998, + 244.76310000000001, + 464.98469999999998, + 10.1, + 1, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 250.4898, + 453.7998, + 266.7912, + 464.98469999999998, + 10.1, + 1, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "cost" + ], + [ + 272.49770000000001, + 453.7998, + 280.9615, + 464.98469999999998, + 10.1, + 1, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 286.49630000000002, + 453.7998, + 326.8458, + 464.98469999999998, + 10.1, + 1, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "mediation" + ], + [ + 332.59269999999998, + 453.7998, + 369.6395, + 464.98469999999998, + 10.1, + 1, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "increases" + ], + [ + 375.31569999999999, + 453.7998, + 419.57389999999998, + 464.98469999999998, + 10.1, + 1, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 425.32080000000002, + 453.7998, + 445.55110000000002, + 464.98469999999998, + 10.1, + 0, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "costs" + ], + [ + 445.52080000000001, + 453.7998, + 448.04579999999999, + 464.98469999999998, + 10.1, + 1, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 453.72199999999998, + 453.7998, + 485.65820000000002, + 464.98469999999998, + 10.1, + 1, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "limiting" + ], + [ + 491.4051, + 453.7998, + 503.67660000000001, + 464.98469999999998, + 10.1, + 0, + 462.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 465.39980000000003, + 147.43950000000001, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "minimum" + ], + [ + 150.99469999999999, + 465.39980000000003, + 185.80940000000001, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "practical" + ], + [ + 189.4151, + 465.39980000000003, + 233.77430000000001, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 237.4204, + 465.39980000000003, + 253.09559999999999, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "size" + ], + [ + 256.72149999999999, + 465.39980000000003, + 271.2756, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 274.92169999999999, + 465.39980000000003, + 302.96940000000001, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "cutting" + ], + [ + 306.6155, + 465.39980000000003, + 318.28100000000001, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "off" + ], + [ + 321.81599999999997, + 465.39980000000003, + 334.18849999999998, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 337.81439999999998, + 465.39980000000003, + 379.95159999999998, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "possibility" + ], + [ + 383.49669999999998, + 465.39980000000003, + 395.26319999999998, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 399.00020000000001, + 465.39980000000003, + 420.79599999999999, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "small" + ], + [ + 424.5027, + 465.39980000000003, + 449.71230000000003, + 476.5847, + 10.1, + 1, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "casual" + ], + [ + 453.31799999999998, + 465.39980000000003, + 501.65660000000003, + 476.5847, + 10.1, + 0, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 501.62630000000001, + 465.39980000000003, + 504.15129999999999, + 476.5847, + 10.1, + 0, + 474.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 477.09980000000002, + 122.6541, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 127.4011, + 477.09980000000002, + 147.58090000000001, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "there" + ], + [ + 152.39859999999999, + 477.09980000000002, + 159.12520000000001, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 163.79140000000001, + 477.09980000000002, + 168.26570000000001, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 172.98240000000001, + 477.09980000000002, + 203.85810000000001, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "broader" + ], + [ + 208.595, + 477.09980000000002, + 224.8964, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "cost" + ], + [ + 229.59289999999999, + 477.09980000000002, + 237.44059999999999, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 242.1876, + 477.09980000000002, + 254.45910000000001, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 259.27679999999998, + 477.09980000000002, + 274.9015, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "loss" + ], + [ + 279.6687, + 477.09980000000002, + 288.13249999999999, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 292.76839999999999, + 477.09980000000002, + 318.61430000000001, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "ability" + ], + [ + 323.36130000000003, + 477.09980000000002, + 331.108, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 335.95600000000002, + 477.09980000000002, + 357.73160000000001, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "make" + ], + [ + 362.54930000000002, + 477.09980000000002, + 420.83640000000003, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "non-reversible" + ], + [ + 425.55309999999997, + 477.09980000000002, + 464.28660000000002, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "payments" + ], + [ + 469.05380000000002, + 477.09980000000002, + 480.82029999999997, + 488.28469999999999, + 10.1, + 1, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 485.55720000000002, + 477.09980000000002, + 504.01999999999998, + 488.28469999999999, + 10.1, + 0, + 486.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "non-" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 488.69979999999998, + 147.98490000000001, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "reversible" + ], + [ + 151.10579999999999, + 488.69979999999998, + 183.648, + 499.88470000000001, + 10.1, + 0, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "services" + ], + [ + 183.71870000000001, + 488.69979999999998, + 186.24369999999999, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 192.41480000000001, + 488.69979999999998, + 212.16030000000001, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "With" + ], + [ + 215.3115, + 488.69979999999998, + 227.684, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 230.9059, + 488.69979999999998, + 272.94209999999998, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "possibility" + ], + [ + 276.0933, + 488.69979999999998, + 284.55709999999999, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 287.68810000000002, + 488.69979999999998, + 319.70510000000002, + 499.88470000000001, + 10.1, + 0, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "reversal" + ], + [ + 319.70510000000002, + 488.69979999999998, + 322.23009999999999, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 325.4015, + 488.69979999999998, + 337.673, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 340.89490000000001, + 488.69979999999998, + 359.95359999999999, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "need" + ], + [ + 363.20580000000001, + 488.69979999999998, + 374.87130000000002, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 378.10329999999999, + 488.69979999999998, + 396.00049999999999, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "trust" + ], + [ + 399.3032, + 488.69979999999998, + 429.54259999999999, + 499.88470000000001, + 10.1, + 0, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "spreads" + ], + [ + 429.51229999999998, + 488.69979999999998, + 432.03730000000002, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 438.41039999999998, + 488.69979999999998, + 481.05259999999998, + 499.88470000000001, + 10.1, + 1, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "Merchants" + ], + [ + 484.32499999999999, + 488.69979999999998, + 503.91899999999998, + 499.88470000000001, + 10.1, + 0, + 497.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "must" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 500.39980000000003, + 117.6748, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 120.5937, + 500.39980000000003, + 140.7533, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "wary" + ], + [ + 143.70249999999999, + 500.39980000000003, + 152.16630000000001, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 155.0044, + 500.39980000000003, + 173.56819999999999, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "their" + ], + [ + 176.49719999999999, + 500.39980000000003, + 217.4325, + 511.5847, + 10.1, + 0, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "customers" + ], + [ + 217.40219999999999, + 500.39980000000003, + 219.9272, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 222.89660000000001, + 500.39980000000003, + 255.94380000000001, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "hassling" + ], + [ + 258.89299999999997, + 500.39980000000003, + 279.14350000000002, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "them" + ], + [ + 282.09269999999998, + 500.39980000000003, + 293.85919999999999, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 296.78820000000002, + 500.39980000000003, + 317.46289999999999, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "more" + ], + [ + 320.4828, + 500.39980000000003, + 368.13459999999998, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "information" + ], + [ + 371.0838, + 500.39980000000003, + 388.43560000000002, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "than" + ], + [ + 391.38479999999998, + 500.39980000000003, + 408.73660000000001, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "they" + ], + [ + 411.68579999999997, + 500.39980000000003, + 436.93579999999997, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "would" + ], + [ + 439.88499999999999, + 500.39980000000003, + 479.06290000000001, + 511.5847, + 10.1, + 1, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "otherwise" + ], + [ + 482.08280000000002, + 500.39980000000003, + 501.14150000000001, + 511.5847, + 10.1, + 0, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "need" + ], + [ + 501.19200000000001, + 500.39980000000003, + 503.71699999999998, + 511.5847, + 10.1, + 0, + 509.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 511.99979999999999, + 115.3922, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "A" + ], + [ + 117.69499999999999, + 511.99979999999999, + 145.15690000000001, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "certain" + ], + [ + 148.1061, + 511.99979999999999, + 191.7987, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "percentage" + ], + [ + 194.7176, + 511.99979999999999, + 203.1814, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 206.01949999999999, + 511.99979999999999, + 227.28, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "fraud" + ], + [ + 230.22919999999999, + 511.99979999999999, + 236.95580000000001, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 239.82419999999999, + 511.99979999999999, + 275.09339999999997, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "accepted" + ], + [ + 278.04259999999999, + 511.99979999999999, + 286.47609999999997, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 289.34449999999998, + 511.99979999999999, + 338.72340000000003, + 523.18470000000002, + 10.1, + 0, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "unavoidable" + ], + [ + 338.75369999999998, + 511.99979999999999, + 341.27870000000001, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 346.84379999999999, + 511.99979999999999, + 370.92219999999998, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "These" + ], + [ + 373.84109999999998, + 511.99979999999999, + 393.97039999999998, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "costs" + ], + [ + 396.83879999999999, + 511.99979999999999, + 411.3929, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 414.34210000000002, + 511.99979999999999, + 449.14670000000001, + 523.18470000000002, + 10.1, + 1, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "payment" + ], + [ + 452.04539999999997, + 511.99979999999999, + 503.58569999999997, + 523.18470000000002, + 10.1, + 0, + 521, + 0, + 0, + 0, + 0, + 0, + 1, + "uncertainties" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 523.69979999999998, + 122.0582, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "can" + ], + [ + 125.0074, + 523.69979999999998, + 134.4812, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 137.29910000000001, + 523.69979999999998, + 169.25550000000001, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "avoided" + ], + [ + 172.1037, + 523.69979999999998, + 179.95140000000001, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 182.7996, + 523.69979999999998, + 209.65549999999999, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "person" + ], + [ + 212.60470000000001, + 523.69979999999998, + 222.7552, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 225.50239999999999, + 523.69979999999998, + 247.34870000000001, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "using" + ], + [ + 250.1969, + 523.69979999999998, + 283.9006, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "physical" + ], + [ + 286.69830000000002, + 523.69979999999998, + 322.06849999999997, + 534.88469999999995, + 10.1, + 0, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "currency" + ], + [ + 321.4221, + 523.69979999999998, + 323.94709999999998, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 326.71449999999999, + 523.69979999999998, + 339.61219999999997, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "but" + ], + [ + 342.51089999999999, + 523.69979999999998, + 352.56040000000002, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "no" + ], + [ + 355.40859999999998, + 523.69979999999998, + 401.36360000000002, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "mechanism" + ], + [ + 404.21179999999998, + 523.69979999999998, + 427.2398, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "exists" + ], + [ + 430.10820000000001, + 523.69979999999998, + 437.85489999999999, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 440.70310000000001, + 523.69979999999998, + 462.5797, + 534.88469999999995, + 10.1, + 1, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "make" + ], + [ + 465.39760000000001, + 523.69979999999998, + 504.1311, + 534.88469999999995, + 10.1, + 0, + 532.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "payments" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 535.2998, + 126.06789999999999, + 546.48469999999998, + 10.1, + 1, + 544.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "over" + ], + [ + 128.6131, + 535.2998, + 133.0874, + 546.48469999999998, + 10.1, + 1, + 544.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 135.6225, + 535.2998, + 202.45419999999999, + 546.48469999999998, + 10.1, + 1, + 544.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "communications" + ], + [ + 204.92869999999999, + 535.2998, + 236.33969999999999, + 546.48469999999998, + 10.1, + 1, + 544.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "channel" + ], + [ + 238.84450000000001, + 535.2998, + 269.74040000000002, + 546.48469999999998, + 10.1, + 1, + 544.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "without" + ], + [ + 272.24520000000001, + 535.2998, + 276.71949999999998, + 546.48469999999998, + 10.1, + 1, + 544.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 279.25459999999998, + 535.2998, + 306.70639999999997, + 546.48469999999998, + 10.1, + 1, + 544.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "trusted" + ], + [ + 309.3526, + 535.2998, + 330.10809999999998, + 546.48469999999998, + 10.1, + 0, + 544.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "party" + ], + [ + 329.36070000000001, + 535.2998, + 331.88569999999999, + 546.48469999999998, + 10.1, + 0, + 544.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 546.99980000000005, + 144.40690000000001, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "What" + ], + [ + 147.0127, + 546.99980000000005, + 153.73929999999999, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 156.4057, + 546.99980000000005, + 184.96850000000001, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "needed" + ], + [ + 187.6147, + 546.99980000000005, + 194.34129999999999, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 197.0077, + 546.99980000000005, + 206.46129999999999, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "an" + ], + [ + 209.11760000000001, + 546.99980000000005, + 249.0025, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "electronic" + ], + [ + 251.6285, + 546.99980000000005, + 286.43310000000002, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "payment" + ], + [ + 289.02879999999999, + 546.99980000000005, + 317.07650000000001, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "system" + ], + [ + 319.72269999999997, + 546.99980000000005, + 342.68000000000001, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "based" + ], + [ + 345.32619999999997, + 546.99980000000005, + 355.47669999999999, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 358.03199999999998, + 546.99980000000005, + 414.1173, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "cryptographic" + ], + [ + 416.83420000000001, + 546.99980000000005, + 438.70069999999998, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "proof" + ], + [ + 441.33679999999998, + 546.99980000000005, + 469.8895, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "instead" + ], + [ + 472.53570000000002, + 546.99980000000005, + 480.99950000000001, + 558.18470000000002, + 10.1, + 1, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 483.63560000000001, + 546.99980000000005, + 501.53280000000001, + 558.18470000000002, + 10.1, + 0, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "trust" + ], + [ + 501.53280000000001, + 546.99980000000005, + 504.05779999999999, + 558.18470000000002, + 10.1, + 0, + 556, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 558.59979999999996, + 143.44999999999999, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "allowing" + ], + [ + 146.09620000000001, + 558.59979999999996, + 160.75129999999999, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "any" + ], + [ + 163.29650000000001, + 558.59979999999996, + 178.44649999999999, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "two" + ], + [ + 181.09270000000001, + 558.59979999999996, + 209.6353, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "willing" + ], + [ + 212.29159999999999, + 558.59979999999996, + 239.22829999999999, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "parties" + ], + [ + 241.8947, + 558.59979999999996, + 249.7424, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 252.3886, + 558.59979999999996, + 283.7996, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "transact" + ], + [ + 286.39530000000002, + 558.59979999999996, + 317.25080000000003, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "directly" + ], + [ + 319.89699999999999, + 558.59979999999996, + 337.84469999999999, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "with" + ], + [ + 340.49090000000001, + 558.59979999999996, + 358.95370000000003, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "each" + ], + [ + 361.59989999999999, + 558.59979999999996, + 382.3655, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "other" + ], + [ + 385.0016, + 558.59979999999996, + 415.89749999999998, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "without" + ], + [ + 418.4932, + 558.59979999999996, + 430.7647, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 433.48160000000001, + 558.59979999999996, + 452.4393, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "need" + ], + [ + 455.09559999999999, + 558.59979999999996, + 466.8621, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 469.5992, + 558.59979999999996, + 474.07350000000002, + 569.78470000000004, + 10.1, + 1, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 476.6995, + 558.59979999999996, + 504.15129999999999, + 569.78470000000004, + 10.1, + 0, + 567.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "trusted" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 570.2998, + 127.1486, + 581.48469999999998, + 10.1, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "third" + ], + [ + 131.1987, + 570.2998, + 152.05520000000001, + 581.48469999999998, + 10.1, + 0, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "party" + ], + [ + 151.30779999999999, + 570.2998, + 153.83279999999999, + 581.48469999999998, + 10.1, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 161.71080000000001, + 570.2998, + 213.04910000000001, + 581.48469999999998, + 10.1, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "Transactions" + ], + [ + 217.01840000000001, + 570.2998, + 232.21889999999999, + 581.48469999999998, + 10.1, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 236.2286, + 570.2998, + 248.5102, + 581.48469999999998, + 10.1, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "are" + ], + [ + 252.5401, + 570.2998, + 318.19009999999997, + 581.48469999999998, + 10.1, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "computationally" + ], + [ + 322.24020000000002, + 570.2998, + 367.75080000000003, + 581.48469999999998, + 10.1, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "impractical" + ], + [ + 371.76049999999998, + 570.2998, + 379.50720000000001, + 581.48469999999998, + 10.1, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 383.6583, + 570.2998, + 412.74630000000002, + 581.48469999999998, + 10.1, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "reverse" + ], + [ + 416.87720000000002, + 570.2998, + 442.02620000000002, + 581.48469999999998, + 10.1, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "would" + ], + [ + 446.0763, + 570.2998, + 474.18459999999999, + 581.48469999999998, + 10.1, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "protect" + ], + [ + 478.1943, + 570.2998, + 504.0301, + 581.48469999999998, + 10.1, + 0, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "sellers" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 581.89980000000003, + 127.7546, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "from" + ], + [ + 131.2088, + 581.89980000000003, + 152.5703, + 593.0847, + 10.1, + 0, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "fraud" + ], + [ + 152.6208, + 581.89980000000003, + 155.14580000000001, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 158.62020000000001, + 581.89980000000003, + 173.17429999999999, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 176.6285, + 581.89980000000003, + 205.20140000000001, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "routine" + ], + [ + 208.72630000000001, + 581.89980000000003, + 237.3295, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "escrow" + ], + [ + 240.83420000000001, + 581.89980000000003, + 290.76859999999999, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "mechanisms" + ], + [ + 294.24299999999999, + 581.89980000000003, + 316.69529999999997, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "could" + ], + [ + 320.14949999999999, + 581.89980000000003, + 343.70269999999999, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "easily" + ], + [ + 347.25790000000001, + 581.89980000000003, + 356.73169999999999, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 360.25659999999999, + 581.89980000000003, + 412.90789999999998, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "implemented" + ], + [ + 416.45299999999997, + 581.89980000000003, + 424.30070000000001, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 427.75490000000002, + 581.89980000000003, + 455.86320000000001, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "protect" + ], + [ + 459.36790000000002, + 581.89980000000003, + 486.30459999999999, + 593.0847, + 10.1, + 0, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "buyers" + ], + [ + 486.27429999999998, + 581.89980000000003, + 488.79930000000002, + 593.0847, + 10.1, + 1, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 495.77839999999998, + 581.89980000000003, + 504.1311, + 593.0847, + 10.1, + 0, + 590.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "In" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 593.59979999999996, + 122.7248, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "this" + ], + [ + 125.4922, + 593.59979999999996, + 147.86369999999999, + 604.78470000000004, + 10.1, + 0, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "paper" + ], + [ + 147.5001, + 593.59979999999996, + 150.02510000000001, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 152.79249999999999, + 593.59979999999996, + 164.56909999999999, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "we" + ], + [ + 167.387, + 593.59979999999996, + 199.36359999999999, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "propose" + ], + [ + 202.0805, + 593.59979999999996, + 206.5548, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 209.37270000000001, + 593.59979999999996, + 241.91489999999999, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "solution" + ], + [ + 244.66210000000001, + 593.59979999999996, + 252.50980000000001, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 255.358, + 593.59979999999996, + 267.62950000000001, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 270.44740000000002, + 593.59979999999996, + 337.70330000000001, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "double-spending" + ], + [ + 340.55149999999998, + 593.59979999999996, + 374.2047, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "problem" + ], + [ + 376.95190000000002, + 593.59979999999996, + 398.79820000000001, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "using" + ], + [ + 401.64640000000003, + 593.59979999999996, + 406.1207, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 408.93860000000001, + 593.59979999999996, + 458.02460000000002, + 604.78470000000004, + 10.1, + 1, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "peer-to-peer" + ], + [ + 460.86270000000002, + 593.59979999999996, + 504.00990000000002, + 604.78470000000004, + 10.1, + 0, + 602.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "distributed" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 605.19979999999998, + 150.1463, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamp" + ], + [ + 152.89349999999999, + 605.19979999999998, + 177.56780000000001, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "server" + ], + [ + 180.3049, + 605.19979999999998, + 188.05160000000001, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 190.7988, + 605.19979999999998, + 224.9873, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "generate" + ], + [ + 227.71430000000001, + 605.19979999999998, + 285.51659999999998, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "computational" + ], + [ + 288.1123, + 605.19979999999998, + 310.07979999999998, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "proof" + ], + [ + 312.71589999999998, + 605.19979999999998, + 321.17970000000003, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 323.81580000000002, + 605.19979999999998, + 336.08730000000003, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 338.80419999999998, + 605.19979999999998, + 394.31380000000001, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "chronological" + ], + [ + 397.01049999999998, + 605.19979999999998, + 418.28109999999998, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "order" + ], + [ + 421.01819999999998, + 605.19979999999998, + 429.48200000000003, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 432.11810000000003, + 605.19979999999998, + 480.35570000000001, + 616.38469999999995, + 10.1, + 0, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 480.3254, + 605.19979999999998, + 482.85039999999998, + 616.38469999999995, + 10.1, + 1, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 488.12259999999998, + 605.19979999999998, + 503.7978, + 616.38469999999995, + 10.1, + 0, + 614.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 616.89980000000003, + 136.14769999999999, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "system" + ], + [ + 141.70269999999999, + 616.89980000000003, + 148.42930000000001, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 154.00450000000001, + 616.89980000000003, + 179.78980000000001, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "secure" + ], + [ + 185.31450000000001, + 616.89980000000003, + 193.74799999999999, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 199.32320000000001, + 616.89980000000003, + 217.16990000000001, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "long" + ], + [ + 222.7148, + 616.89980000000003, + 231.14830000000001, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 236.7235, + 616.89980000000003, + 263.02390000000003, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "honest" + ], + [ + 268.62939999999998, + 616.89980000000003, + 292.16239999999999, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 297.73759999999999, + 616.89980000000003, + 344.79349999999999, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "collectively" + ], + [ + 350.33839999999998, + 616.89980000000003, + 378.94159999999999, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "control" + ], + [ + 384.5471, + 616.89980000000003, + 405.22179999999997, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "more" + ], + [ + 410.74650000000003, + 616.89980000000003, + 430.3304, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "CPU" + ], + [ + 435.9359, + 616.89980000000003, + 461.20609999999999, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "power" + ], + [ + 466.74090000000001, + 616.89980000000003, + 484.09269999999998, + 628.0847, + 10.1, + 1, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "than" + ], + [ + 489.63760000000002, + 616.89980000000003, + 504.19170000000003, + 628.0847, + 10.1, + 0, + 625.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "any" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 628.49980000000005, + 155.7619, + 639.68470000000002, + 10.1, + 1, + 637.5, + 0, + 0, + 0, + 0, + 0, + 1, + "cooperating" + ], + [ + 158.31720000000001, + 628.49980000000005, + 181.87039999999999, + 639.68470000000002, + 10.1, + 1, + 637.5, + 0, + 0, + 0, + 0, + 0, + 1, + "group" + ], + [ + 184.42570000000001, + 628.49980000000005, + 192.8895, + 639.68470000000002, + 10.1, + 1, + 637.5, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 195.4246, + 628.49980000000005, + 227.30019999999999, + 639.68470000000002, + 10.1, + 1, + 637.5, + 0, + 0, + 0, + 0, + 0, + 1, + "attacker" + ], + [ + 229.93629999999999, + 628.49980000000005, + 253.4693, + 639.68470000000002, + 10.1, + 0, + 637.5, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 253.43899999999999, + 628.49980000000005, + 255.964, + 639.68470000000002, + 10.1, + 0, + 637.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 310.80000000000001, + 708.99980000000005, + 315.85000000000002, + 720.18470000000002, + 10.1, + 0, + 718, + 0, + 0, + 0, + 0, + 0, + 1, + "1" + ] + ] + ] + ] + ] + ] + ] + ] + ], + [ + 612, + 792, + [ + [ + [ + [ + 0, + 0, + 0, + 0, + [ + [ + [ + [ + 108.09999999999999, + 82.162800000000004, + 114.70099999999999, + 95.987499999999997, + 11.5, + 0, + 93.5, + 0, + 0, + 0, + 0, + 0, + 0, + "2" + ], + [ + 114.70099999999999, + 82.162800000000004, + 117.8865, + 95.987499999999997, + 11.5, + 1, + 93.5, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 133.30000000000001, + 82.162800000000004, + 211.46549999999999, + 95.987499999999997, + 11.5, + 0, + 93.5, + 0, + 0, + 0, + 0, + 0, + 0, + "Transactions" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 103.5998, + 121.2704, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "We" + ], + [ + 123.9873, + 103.5998, + 149.26759999999999, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "define" + ], + [ + 151.89359999999999, + 103.5998, + 161.44820000000001, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "an" + ], + [ + 164.09440000000001, + 103.5998, + 203.97929999999999, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "electronic" + ], + [ + 206.6053, + 103.5998, + 223.9571, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "coin" + ], + [ + 226.70429999999999, + 103.5998, + 235.1378, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 237.80420000000001, + 103.5998, + 242.27850000000001, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 245.00550000000001, + 103.5998, + 266.76089999999999, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 269.50810000000001, + 103.5998, + 277.97190000000001, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 280.608, + 103.5998, + 306.40339999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "digital" + ], + [ + 309.1001, + 103.5998, + 350.03539999999998, + 114.7847, + 10.1, + 0, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "signatures" + ], + [ + 350.00510000000003, + 103.5998, + 352.5301, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 357.89319999999998, + 103.5998, + 378.05279999999999, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "Each" + ], + [ + 380.80000000000001, + 103.5998, + 405.9692, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "owner" + ], + [ + 408.7063, + 103.5998, + 443.55130000000003, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "transfers" + ], + [ + 446.21769999999998, + 103.5998, + 458.48919999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 461.20609999999999, + 103.5998, + 478.55790000000002, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "coin" + ], + [ + 481.30509999999998, + 103.5998, + 489.05180000000001, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 491.79899999999998, + 103.5998, + 504.07049999999998, + 114.7847, + 10.1, + 0, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 115.2998, + 125.50230000000001, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "next" + ], + [ + 128.50200000000001, + 115.2998, + 138.6525, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 141.70269999999999, + 115.2998, + 175.3458, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "digitally" + ], + [ + 178.39599999999999, + 115.2998, + 208.0395, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "signing" + ], + [ + 211.08969999999999, + 115.2998, + 215.56399999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 218.6849, + 115.2998, + 237.13759999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "hash" + ], + [ + 240.18780000000001, + 115.2998, + 248.6516, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 251.5806, + 115.2998, + 263.95310000000001, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 266.97300000000001, + 115.2998, + 301.80790000000002, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "previous" + ], + [ + 304.77730000000003, + 115.2998, + 349.03550000000001, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 352.18669999999997, + 115.2998, + 366.74079999999998, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 369.791, + 115.2998, + 382.0625, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 385.08240000000001, + 115.2998, + 410.3526, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "public" + ], + [ + 413.3725, + 115.2998, + 428.02760000000001, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "key" + ], + [ + 430.97680000000003, + 115.2998, + 439.44060000000002, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 442.47059999999999, + 115.2998, + 454.74209999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 457.762, + 115.2998, + 475.16430000000003, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "next" + ], + [ + 478.26499999999999, + 115.2998, + 503.43419999999998, + 126.4847, + 10.1, + 0, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "owner" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 126.8998, + 122.6541, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 125.8053, + 126.8998, + 153.25710000000001, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "adding" + ], + [ + 156.4083, + 126.8998, + 177.083, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "these" + ], + [ + 180.2039, + 126.8998, + 187.95060000000001, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 191.1018, + 126.8998, + 203.4743, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 206.49420000000001, + 126.8998, + 221.04830000000001, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "end" + ], + [ + 224.1995, + 126.8998, + 232.66329999999999, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 235.69329999999999, + 126.8998, + 247.9648, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 251.0857, + 126.8998, + 268.4375, + 138.0847, + 10.1, + 0, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "coin" + ], + [ + 268.488, + 126.8998, + 271.01299999999998, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 276.58819999999997, + 126.8998, + 283.88040000000001, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "A" + ], + [ + 286.3852, + 126.8998, + 309.96870000000001, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "payee" + ], + [ + 312.98860000000002, + 126.8998, + 327.0478, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "can" + ], + [ + 330.09800000000001, + 126.8998, + 354.25720000000001, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "verify" + ], + [ + 357.30739999999997, + 126.8998, + 369.67989999999998, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 372.80079999999998, + 126.8998, + 413.73610000000002, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "signatures" + ], + [ + 416.80650000000003, + 126.8998, + 424.6542, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 427.70440000000002, + 126.8998, + 451.86360000000002, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "verify" + ], + [ + 454.91379999999998, + 126.8998, + 467.28629999999998, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 470.30619999999999, + 126.8998, + 492.1626, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 495.31380000000001, + 126.8998, + 503.77760000000001, + 138.0847, + 10.1, + 0, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 138.59979999999999, + 150.15639999999999, + 149.78469999999999, + 10.1, + 0, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "ownership" + ], + [ + 150.20689999999999, + 138.59979999999999, + 152.7319, + 149.78469999999999, + 10.1, + 0, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 323.99979999999999, + 138.17519999999999, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 141.49809999999999, + 323.99979999999999, + 175.15129999999999, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "problem" + ], + [ + 178.30250000000001, + 323.99979999999999, + 186.7663, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 189.9983, + 323.99979999999999, + 216.27850000000001, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "course" + ], + [ + 219.60140000000001, + 323.99979999999999, + 226.328, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 229.49940000000001, + 323.99979999999999, + 241.87190000000001, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 245.09379999999999, + 323.99979999999999, + 268.6773, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "payee" + ], + [ + 271.89920000000001, + 323.99979999999999, + 290.5034, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "can't" + ], + [ + 293.80610000000001, + 323.99979999999999, + 317.86430000000001, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "verify" + ], + [ + 321.11649999999997, + 323.99979999999999, + 336.21600000000001, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 339.51870000000002, + 323.99979999999999, + 354.09300000000002, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "one" + ], + [ + 357.31490000000002, + 323.99979999999999, + 365.77870000000001, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 368.90969999999999, + 323.99979999999999, + 381.28219999999999, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 384.50409999999999, + 323.99979999999999, + 413.64260000000002, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "owners" + ], + [ + 416.91500000000002, + 323.99979999999999, + 429.76220000000001, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "did" + ], + [ + 433.01440000000002, + 323.99979999999999, + 445.91210000000001, + 335.18470000000002, + 10.1, + 1, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "not" + ], + [ + 449.21480000000003, + 323.99979999999999, + 503.57299999999998, + 335.18470000000002, + 10.1, + 0, + 333, + 0, + 0, + 0, + 0, + 0, + 1, + "double-spend" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 335.69979999999998, + 120.4725, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 123.0985, + 335.69979999999998, + 140.4503, + 346.88470000000001, + 10.1, + 0, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "coin" + ], + [ + 140.5008, + 335.69979999999998, + 143.0258, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 147.69200000000001, + 335.69979999999998, + 154.98419999999999, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "A" + ], + [ + 157.08500000000001, + 335.69979999999998, + 192.334, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "common" + ], + [ + 194.99029999999999, + 335.69979999999998, + 227.5325, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "solution" + ], + [ + 230.17869999999999, + 335.69979999999998, + 236.90530000000001, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 239.57169999999999, + 335.69979999999998, + 247.3184, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 250.06559999999999, + 335.69979999999998, + 288.14260000000002, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "introduce" + ], + [ + 290.76859999999999, + 335.69979999999998, + 295.24290000000002, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 297.95979999999997, + 335.69979999999998, + 325.41160000000002, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "trusted" + ], + [ + 328.05779999999999, + 335.69979999999998, + 355.5702, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "central" + ], + [ + 358.16590000000002, + 335.69979999999998, + 394.61680000000001, + 346.88470000000001, + 10.1, + 0, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "authority" + ], + [ + 393.97039999999998, + 335.69979999999998, + 396.49540000000002, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 399.16180000000003, + 335.69979999999998, + 407.52460000000002, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "or" + ], + [ + 410.16070000000002, + 335.69979999999998, + 428.65379999999999, + 346.88470000000001, + 10.1, + 0, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "mint" + ], + [ + 428.65379999999999, + 335.69979999999998, + 431.17880000000002, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 433.84519999999998, + 335.69979999999998, + 448.94470000000001, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 451.5505, + 335.69979999999998, + 479.09320000000002, + 346.88470000000001, + 10.1, + 1, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "checks" + ], + [ + 481.75959999999998, + 335.69979999999998, + 504.12099999999998, + 346.88470000000001, + 10.1, + 0, + 344.69999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "every" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 347.2998, + 152.45920000000001, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 155.4084, + 347.2998, + 167.17490000000001, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 170.20490000000001, + 347.2998, + 197.67689999999999, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "double" + ], + [ + 200.6968, + 347.2998, + 237.04669999999999, + 358.48469999999998, + 10.1, + 0, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "spending" + ], + [ + 237.09719999999999, + 347.2998, + 239.62219999999999, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 244.99539999999999, + 347.2998, + 266.36700000000002, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "After" + ], + [ + 269.29599999999999, + 347.2998, + 287.75880000000001, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "each" + ], + [ + 290.80900000000003, + 347.2998, + 335.06720000000001, + 358.48469999999998, + 10.1, + 0, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 335.11770000000001, + 347.2998, + 337.64269999999999, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 340.6121, + 347.2998, + 352.9846, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 356.00450000000001, + 347.2998, + 373.25529999999998, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "coin" + ], + [ + 376.30549999999999, + 347.2998, + 395.89949999999999, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "must" + ], + [ + 398.89920000000001, + 347.2998, + 408.47399999999999, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 411.4939, + 347.2998, + 445.05619999999999, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "returned" + ], + [ + 448.10640000000001, + 347.2998, + 455.85309999999998, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 458.9033, + 347.2998, + 471.2758, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 474.19470000000001, + 347.2998, + 492.78879999999998, + 358.48469999999998, + 10.1, + 1, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "mint" + ], + [ + 495.6875, + 347.2998, + 503.53519999999997, + 358.48469999999998, + 10.1, + 0, + 356.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 358.99979999999999, + 128.2697, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "issue" + ], + [ + 131.18860000000001, + 358.99979999999999, + 135.66290000000001, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 138.58179999999999, + 358.99979999999999, + 155.37809999999999, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "new" + ], + [ + 158.2869, + 358.99979999999999, + 175.6387, + 370.18470000000002, + 10.1, + 0, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "coin" + ], + [ + 175.6892, + 358.99979999999999, + 178.21420000000001, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 181.08260000000001, + 358.99979999999999, + 195.63669999999999, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 198.48490000000001, + 358.99979999999999, + 216.43260000000001, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "only" + ], + [ + 219.3818, + 358.99979999999999, + 240.61199999999999, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "coins" + ], + [ + 243.5814, + 358.99979999999999, + 268.73039999999997, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "issued" + ], + [ + 271.67959999999999, + 358.99979999999999, + 302.5351, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "directly" + ], + [ + 305.38330000000002, + 358.99979999999999, + 325.03789999999998, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "from" + ], + [ + 327.8861, + 358.99979999999999, + 340.2586, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 343.17750000000001, + 358.99979999999999, + 361.67059999999998, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "mint" + ], + [ + 364.5693, + 358.99979999999999, + 376.85090000000002, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "are" + ], + [ + 379.76979999999998, + 358.99979999999999, + 407.22160000000002, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "trusted" + ], + [ + 410.06979999999999, + 358.99979999999999, + 422.96749999999997, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "not" + ], + [ + 425.86619999999999, + 358.99979999999999, + 433.71390000000002, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 436.66309999999999, + 358.99979999999999, + 446.13690000000003, + 370.18470000000002, + 10.1, + 1, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 449.05579999999998, + 358.99979999999999, + 501.1617, + 370.18470000000002, + 10.1, + 0, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "double-spent" + ], + [ + 501.1617, + 358.99979999999999, + 503.68669999999997, + 370.18470000000002, + 10.1, + 0, + 368, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 370.59980000000002, + 123.7752, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 128.69390000000001, + 370.59980000000002, + 162.34710000000001, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "problem" + ], + [ + 167.0941, + 370.59980000000002, + 185.04179999999999, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "with" + ], + [ + 189.88980000000001, + 370.59980000000002, + 204.5146, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "this" + ], + [ + 209.3828, + 370.59980000000002, + 241.82400000000001, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "solution" + ], + [ + 246.672, + 370.59980000000002, + 253.39859999999999, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 258.26679999999999, + 370.59980000000002, + 273.36630000000002, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 278.26479999999998, + 370.59980000000002, + 290.53629999999998, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 295.35399999999998, + 370.59980000000002, + 310.52420000000001, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "fate" + ], + [ + 315.34190000000001, + 370.59980000000002, + 323.8057, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 328.64359999999999, + 370.59980000000002, + 340.9151, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 345.7328, + 370.59980000000002, + 368.71030000000002, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "entire" + ], + [ + 373.52800000000002, + 370.59980000000002, + 401.08080000000001, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "money" + ], + [ + 405.92880000000002, + 370.59980000000002, + 433.96640000000002, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "system" + ], + [ + 438.81439999999998, + 370.59980000000002, + 471.85149999999999, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "depends" + ], + [ + 476.71969999999999, + 370.59980000000002, + 486.76920000000001, + 381.78469999999999, + 10.1, + 1, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 491.61720000000003, + 370.59980000000002, + 503.88869999999997, + 381.78469999999999, + 10.1, + 0, + 379.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 382.2998, + 145.15690000000001, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "company" + ], + [ + 147.7122, + 382.2998, + 179.0626, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "running" + ], + [ + 181.61789999999999, + 382.2998, + 193.99039999999999, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 196.52549999999999, + 382.2998, + 215.01859999999999, + 393.48469999999998, + 10.1, + 0, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "mint" + ], + [ + 215.01859999999999, + 382.2998, + 217.5436, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 220.1191, + 382.2998, + 237.9658, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "with" + ], + [ + 240.61199999999999, + 382.2998, + 262.97340000000003, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "every" + ], + [ + 265.52870000000001, + 382.2998, + 309.7869, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 312.43310000000002, + 382.2998, + 339.88490000000002, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "having" + ], + [ + 342.4402, + 382.2998, + 350.18689999999998, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 352.8331, + 382.2998, + 362.88260000000002, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "go" + ], + [ + 365.43790000000001, + 382.2998, + 396.78829999999999, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "through" + ], + [ + 399.34359999999998, + 382.2998, + 419.59410000000003, + 393.48469999999998, + 10.1, + 0, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "them" + ], + [ + 419.54360000000003, + 382.2998, + 422.0686, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 424.64409999999998, + 382.2998, + 439.13760000000002, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "just" + ], + [ + 441.74340000000001, + 382.2998, + 456.91359999999997, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "like" + ], + [ + 459.44869999999997, + 382.2998, + 463.923, + 393.48469999999998, + 10.1, + 1, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 466.4581, + 382.2998, + 486.01170000000002, + 393.48469999999998, + 10.1, + 0, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "bank" + ], + [ + 486.06220000000002, + 382.2998, + 488.5872, + 393.48469999999998, + 10.1, + 0, + 391.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 393.89980000000003, + 135.6704, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "We" + ], + [ + 140.20529999999999, + 393.89980000000003, + 159.16300000000001, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "need" + ], + [ + 163.71809999999999, + 393.89980000000003, + 168.19239999999999, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 172.62629999999999, + 393.89980000000003, + 189.38220000000001, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "way" + ], + [ + 193.83629999999999, + 393.89980000000003, + 205.6028, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 210.04679999999999, + 393.89980000000003, + 222.41929999999999, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 226.85319999999999, + 393.89980000000003, + 250.3357, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "payee" + ], + [ + 254.8706, + 393.89980000000003, + 262.6173, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 267.07139999999998, + 393.89980000000003, + 289.5641, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "know" + ], + [ + 293.9778, + 393.89980000000003, + 309.17829999999998, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 313.58190000000002, + 393.89980000000003, + 325.85340000000002, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 330.38830000000002, + 393.89980000000003, + 365.12220000000002, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "previous" + ], + [ + 369.59649999999999, + 393.89980000000003, + 398.73500000000001, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "owners" + ], + [ + 403.20929999999998, + 393.89980000000003, + 416.05650000000003, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "did" + ], + [ + 420.51060000000001, + 393.89980000000003, + 433.4083, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "not" + ], + [ + 437.81189999999998, + 393.89980000000003, + 454.65870000000001, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "sign" + ], + [ + 459.11279999999999, + 393.89980000000003, + 473.7679, + 405.0847, + 10.1, + 1, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "any" + ], + [ + 478.12099999999998, + 393.89980000000003, + 503.89620000000002, + 405.0847, + 10.1, + 0, + 402.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "earlier" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 405.59980000000002, + 156.43860000000001, + 416.78469999999999, + 10.1, + 0, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 156.4083, + 405.59980000000002, + 158.9333, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 166.20529999999999, + 405.59980000000002, + 180.17359999999999, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "For" + ], + [ + 183.80959999999999, + 405.59980000000002, + 197.27289999999999, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "our" + ], + [ + 200.90889999999999, + 405.59980000000002, + 236.84469999999999, + 416.78469999999999, + 10.1, + 0, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "purposes" + ], + [ + 236.81440000000001, + 405.59980000000002, + 239.33940000000001, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 243.01580000000001, + 405.59980000000002, + 255.28729999999999, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 258.91320000000002, + 405.59980000000002, + 288.12240000000003, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "earliest" + ], + [ + 291.72809999999998, + 405.59980000000002, + 336.08730000000003, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 339.73340000000002, + 405.59980000000002, + 346.45999999999998, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 350.13639999999998, + 405.59980000000002, + 362.40789999999998, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 366.03379999999999, + 405.59980000000002, + 380.60809999999998, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "one" + ], + [ + 384.23399999999998, + 405.59980000000002, + 399.43450000000001, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 403.04020000000003, + 405.59980000000002, + 429.47190000000001, + 416.78469999999999, + 10.1, + 0, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "counts" + ], + [ + 429.44159999999999, + 405.59980000000002, + 431.96660000000003, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 435.54199999999997, + 405.59980000000002, + 444.49059999999997, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "so" + ], + [ + 448.13670000000002, + 405.59980000000002, + 459.91329999999999, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "we" + ], + [ + 463.53919999999999, + 405.59980000000002, + 483.33519999999999, + 416.78469999999999, + 10.1, + 1, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "don't" + ], + [ + 486.9409, + 405.59980000000002, + 503.82810000000001, + 416.78469999999999, + 10.1, + 0, + 414.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "care" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 417.19979999999998, + 130.6028, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "about" + ], + [ + 133.50149999999999, + 417.19979999999998, + 151.36840000000001, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "later" + ], + [ + 154.39840000000001, + 417.19979999999998, + 188.62729999999999, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "attempts" + ], + [ + 191.4957, + 417.19979999999998, + 199.3434, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 202.29259999999999, + 417.19979999999998, + 256.6508, + 428.38470000000001, + 10.1, + 0, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "double-spend" + ], + [ + 256.7013, + 417.19979999999998, + 259.22629999999998, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 264.90249999999997, + 417.19979999999998, + 280.57769999999999, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 283.5976, + 417.19979999999998, + 301.5453, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "only" + ], + [ + 304.39350000000002, + 417.19979999999998, + 321.25040000000001, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "way" + ], + [ + 324.19959999999998, + 417.19979999999998, + 331.94630000000001, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 334.99650000000003, + 417.19979999999998, + 366.9529, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "confirm" + ], + [ + 369.90210000000002, + 417.19979999999998, + 382.17360000000002, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 385.09249999999997, + 417.19979999999998, + 417.07920000000001, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "absence" + ], + [ + 419.99810000000002, + 417.19979999999998, + 428.46190000000001, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 431.39089999999999, + 417.19979999999998, + 435.86520000000002, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 438.78410000000002, + 417.19979999999998, + 483.04230000000001, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 485.99149999999997, + 417.19979999999998, + 492.71809999999999, + 428.38470000000001, + 10.1, + 1, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 495.6875, + 417.19979999999998, + 503.53519999999997, + 428.38470000000001, + 10.1, + 0, + 426.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 428.89980000000003, + 117.6748, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 120.4927, + 428.89980000000003, + 144.5812, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "aware" + ], + [ + 147.3991, + 428.89980000000003, + 155.8629, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 158.70099999999999, + 428.89980000000003, + 168.80099999999999, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "all" + ], + [ + 171.59870000000001, + 428.89980000000003, + 219.83629999999999, + 440.0847, + 10.1, + 0, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 219.80600000000001, + 428.89980000000003, + 222.33099999999999, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 227.99709999999999, + 428.89980000000003, + 236.45079999999999, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "In" + ], + [ + 239.29900000000001, + 428.89980000000003, + 251.57050000000001, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 254.38839999999999, + 428.89980000000003, + 272.88150000000002, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "mint" + ], + [ + 275.78019999999998, + 428.89980000000003, + 298.73750000000001, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "based" + ], + [ + 301.58569999999997, + 428.89980000000003, + 326.78519999999997, + 440.0847, + 10.1, + 0, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "model" + ], + [ + 326.78519999999997, + 428.89980000000003, + 329.31020000000001, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 332.17860000000002, + 428.89980000000003, + 344.45010000000002, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 347.36900000000003, + 428.89980000000003, + 365.8621, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "mint" + ], + [ + 368.65980000000002, + 428.89980000000003, + 384.3956, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "was" + ], + [ + 387.26400000000001, + 428.89980000000003, + 411.35250000000002, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "aware" + ], + [ + 414.17039999999997, + 428.89980000000003, + 422.63420000000002, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 425.37130000000002, + 428.89980000000003, + 435.47129999999999, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "all" + ], + [ + 438.37, + 428.89980000000003, + 486.60759999999999, + 440.0847, + 10.1, + 1, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 489.375, + 428.89980000000003, + 503.92910000000001, + 440.0847, + 10.1, + 0, + 437.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 440.49979999999999, + 139.4605, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "decided" + ], + [ + 143.71260000000001, + 440.49979999999999, + 168.36670000000001, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "which" + ], + [ + 172.61879999999999, + 440.49979999999999, + 201.1816, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "arrived" + ], + [ + 205.43369999999999, + 440.49979999999999, + 221.63409999999999, + 451.68470000000002, + 10.1, + 0, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "first" + ], + [ + 221.63409999999999, + 440.49979999999999, + 224.1591, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 232.34010000000001, + 440.49979999999999, + 242.8946, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "To" + ], + [ + 247.04570000000001, + 440.49979999999999, + 293.00069999999999, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "accomplish" + ], + [ + 297.25279999999998, + 440.49979999999999, + 311.87759999999997, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "this" + ], + [ + 316.0489, + 440.49979999999999, + 346.84379999999999, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "without" + ], + [ + 351.04539999999997, + 440.49979999999999, + 355.5197, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 359.7516, + 440.49979999999999, + 387.20339999999999, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "trusted" + ], + [ + 391.35449999999997, + 440.49979999999999, + 412.21100000000001, + 451.68470000000002, + 10.1, + 0, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "party" + ], + [ + 411.46359999999999, + 440.49979999999999, + 413.98860000000002, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 418.17000000000002, + 440.49979999999999, + 466.5086, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 470.67989999999998, + 440.49979999999999, + 490.27390000000003, + 451.68470000000002, + 10.1, + 1, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "must" + ], + [ + 494.47550000000001, + 440.49979999999999, + 504.05029999999999, + 451.68470000000002, + 10.1, + 0, + 449.5, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 452.19979999999998, + 141.2482, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "publicly" + ], + [ + 144.19739999999999, + 452.19979999999998, + 187.9607, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "announced" + ], + [ + 190.90989999999999, + 452.19979999999998, + 194.2732, + 463.38470000000001, + 10.1, + 0, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "[" + ], + [ + 194.31360000000001, + 452.19979999999998, + 199.36359999999999, + 463.38470000000001, + 10.1, + 0, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "1" + ], + [ + 199.31309999999999, + 452.19979999999998, + 202.6764, + 463.38470000000001, + 10.1, + 0, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "]" + ], + [ + 202.71680000000001, + 452.19979999999998, + 205.24180000000001, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 208.21119999999999, + 452.19979999999998, + 222.7653, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 225.81549999999999, + 452.19979999999998, + 237.59209999999999, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "we" + ], + [ + 240.511, + 452.19979999999998, + 259.56970000000001, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "need" + ], + [ + 262.61989999999997, + 452.19979999999998, + 267.0942, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 270.01310000000001, + 452.19979999999998, + 298.06079999999997, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "system" + ], + [ + 301.11099999999999, + 452.19979999999998, + 312.8775, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 315.80650000000003, + 452.19979999999998, + 362.94319999999999, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "participants" + ], + [ + 365.9126, + 452.19979999999998, + 373.76029999999997, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 376.81049999999999, + 452.19979999999998, + 398.59620000000001, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "agree" + ], + [ + 401.61610000000002, + 452.19979999999998, + 411.76659999999998, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 414.7158, + 452.19979999999998, + 419.19009999999997, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 422.10899999999998, + 452.19979999999998, + 446.2783, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "single" + ], + [ + 449.29820000000001, + 452.19979999999998, + 477.34589999999997, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "history" + ], + [ + 480.29509999999999, + 452.19979999999998, + 488.75889999999998, + 463.38470000000001, + 10.1, + 1, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 491.69799999999998, + 452.19979999999998, + 503.96949999999998, + 463.38470000000001, + 10.1, + 0, + 461.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 463.7998, + 129.3706, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "order" + ], + [ + 132.0067, + 463.7998, + 139.7534, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 142.39959999999999, + 463.7998, + 166.95269999999999, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "which" + ], + [ + 169.59889999999999, + 463.7998, + 186.95070000000001, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "they" + ], + [ + 189.506, + 463.7998, + 209.0899, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "were" + ], + [ + 211.625, + 463.7998, + 245.79329999999999, + 474.98469999999998, + 10.1, + 0, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "received" + ], + [ + 245.84379999999999, + 463.7998, + 248.36879999999999, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 253.24709999999999, + 463.7998, + 268.92230000000001, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 271.54829999999998, + 463.7998, + 295.0308, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "payee" + ], + [ + 297.5659, + 463.7998, + 320.60399999999998, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "needs" + ], + [ + 323.1694, + 463.7998, + 345.03590000000003, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "proof" + ], + [ + 347.57100000000003, + 463.7998, + 362.6705, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 365.17529999999999, + 463.7998, + 372.4776, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "at" + ], + [ + 375.07330000000002, + 463.7998, + 387.34480000000002, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 389.87990000000002, + 463.7998, + 407.74680000000001, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "time" + ], + [ + 410.37279999999998, + 463.7998, + 418.83659999999998, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 421.2808, + 463.7998, + 439.74360000000001, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "each" + ], + [ + 442.38979999999998, + 463.7998, + 486.64800000000002, + 474.98469999999998, + 10.1, + 0, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 486.69850000000002, + 463.7998, + 489.2235, + 474.98469999999998, + 10.1, + 1, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 491.7081, + 463.7998, + 504.0806, + 474.98469999999998, + 10.1, + 0, + 472.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 475.49979999999999, + 142.34909999999999, + 486.68470000000002, + 10.1, + 1, + 484.5, + 0, + 0, + 0, + 0, + 0, + 1, + "majority" + ], + [ + 144.90440000000001, + 475.49979999999999, + 153.3682, + 486.68470000000002, + 10.1, + 1, + 484.5, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 155.8124, + 475.49979999999999, + 179.44640000000001, + 486.68470000000002, + 10.1, + 1, + 484.5, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 181.92089999999999, + 475.49979999999999, + 208.7869, + 486.68470000000002, + 10.1, + 1, + 484.5, + 0, + 0, + 0, + 0, + 0, + 1, + "agreed" + ], + [ + 211.4331, + 475.49979999999999, + 217.02850000000001, + 486.68470000000002, + 10.1, + 1, + 484.5, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 219.5333, + 475.49979999999999, + 235.26910000000001, + 486.68470000000002, + 10.1, + 1, + 484.5, + 0, + 0, + 0, + 0, + 0, + 1, + "was" + ], + [ + 237.74359999999999, + 475.49979999999999, + 250.11609999999999, + 486.68470000000002, + 10.1, + 1, + 484.5, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 252.65119999999999, + 475.49979999999999, + 268.85160000000002, + 486.68470000000002, + 10.1, + 1, + 484.5, + 0, + 0, + 0, + 0, + 0, + 1, + "first" + ], + [ + 271.45740000000001, + 475.49979999999999, + 305.62569999999999, + 486.68470000000002, + 10.1, + 0, + 484.5, + 0, + 0, + 0, + 0, + 0, + 1, + "received" + ], + [ + 305.67619999999999, + 475.49979999999999, + 308.20119999999997, + 486.68470000000002, + 10.1, + 0, + 484.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 501.76280000000003, + 114.70099999999999, + 515.58749999999998, + 11.5, + 0, + 513.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "3" + ], + [ + 114.70099999999999, + 501.76280000000003, + 117.8865, + 515.58749999999998, + 11.5, + 1, + 513.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 133.30000000000001, + 501.76280000000003, + 199.95400000000001, + 515.58749999999998, + 11.5, + 1, + 513.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "Timestamp" + ], + [ + 203.3005, + 501.76280000000003, + 243.07900000000001, + 515.58749999999998, + 11.5, + 0, + 513.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "Server" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 523.2998, + 123.7752, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 126.8961, + 523.2998, + 159.3373, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "solution" + ], + [ + 162.38749999999999, + 523.2998, + 174.16409999999999, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "we" + ], + [ + 177.184, + 523.2998, + 209.16059999999999, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "propose" + ], + [ + 212.18049999999999, + 523.2998, + 238.6122, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "begins" + ], + [ + 241.58160000000001, + 523.2998, + 259.52929999999998, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "with" + ], + [ + 262.5795, + 523.2998, + 267.05380000000002, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 270.07369999999997, + 523.2998, + 312.12, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamp" + ], + [ + 315.27120000000002, + 523.2998, + 339.94549999999998, + 534.48469999999998, + 10.1, + 0, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "server" + ], + [ + 339.37990000000002, + 523.2998, + 341.9049, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 347.37909999999999, + 523.2998, + 354.67129999999997, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "A" + ], + [ + 357.17610000000002, + 523.2998, + 399.12139999999999, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamp" + ], + [ + 402.27260000000001, + 523.2998, + 426.94690000000003, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "server" + ], + [ + 429.9769, + 523.2998, + 454.61079999999998, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "works" + ], + [ + 457.68119999999999, + 523.2998, + 467.83170000000001, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 470.78089999999997, + 523.2998, + 496.03089999999997, + 534.48469999999998, + 10.1, + 1, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "taking" + ], + [ + 499.08109999999999, + 523.2998, + 503.55540000000002, + 534.48469999999998, + 10.1, + 0, + 532.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 534.89980000000003, + 126.5527, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "hash" + ], + [ + 131.7037, + 534.89980000000003, + 140.16749999999999, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 145.10640000000001, + 534.89980000000003, + 149.58070000000001, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 154.60040000000001, + 534.89980000000003, + 177.05269999999999, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 182.1027, + 534.89980000000003, + 190.56649999999999, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 195.49529999999999, + 534.89980000000003, + 217.42240000000001, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "items" + ], + [ + 222.39160000000001, + 534.89980000000003, + 230.23929999999999, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 235.2893, + 534.89980000000003, + 244.86410000000001, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 249.88380000000001, + 534.89980000000003, + 301.42410000000001, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamped" + ], + [ + 306.47410000000002, + 534.89980000000003, + 321.02820000000003, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 326.07819999999998, + 534.89980000000003, + 353.63099999999997, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "widely" + ], + [ + 358.68099999999998, + 534.89980000000003, + 401.22219999999999, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "publishing" + ], + [ + 406.2722, + 534.89980000000003, + 418.6447, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 423.6644, + 534.89980000000003, + 442.11709999999999, + 546.0847, + 10.1, + 0, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "hash" + ], + [ + 442.16759999999999, + 534.89980000000003, + 444.69260000000003, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 449.76280000000003, + 534.89980000000003, + 468.21550000000002, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "such" + ], + [ + 473.26549999999997, + 534.89980000000003, + 481.69900000000001, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 486.76920000000001, + 534.89980000000003, + 494.51589999999999, + 546.0847, + 10.1, + 1, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 499.5659, + 534.89980000000003, + 504.04020000000003, + 546.0847, + 10.1, + 0, + 543.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 546.59979999999996, + 151.2775, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "newspaper" + ], + [ + 155.1155, + 546.59979999999996, + 163.47829999999999, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "or" + ], + [ + 167.31630000000001, + 546.59979999999996, + 195.3236, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "Usenet" + ], + [ + 199.13130000000001, + 546.59979999999996, + 215.92760000000001, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "post" + ], + [ + 219.7353, + 546.59979999999996, + 223.0986, + 557.78470000000004, + 10.1, + 0, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "[" + ], + [ + 223.13900000000001, + 546.59979999999996, + 236.59219999999999, + 557.78470000000004, + 10.1, + 0, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "2-5" + ], + [ + 236.64269999999999, + 546.59979999999996, + 240.006, + 557.78470000000004, + 10.1, + 0, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "]" + ], + [ + 240.04640000000001, + 546.59979999999996, + 242.57140000000001, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 249.8434, + 546.59979999999996, + 265.51859999999999, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 269.34649999999999, + 546.59979999999996, + 311.3827, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamp" + ], + [ + 315.23079999999999, + 546.59979999999996, + 342.16750000000002, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "proves" + ], + [ + 345.94490000000002, + 546.59979999999996, + 361.0444, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 364.85210000000001, + 546.59979999999996, + 377.12360000000001, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 380.95150000000001, + 546.59979999999996, + 397.7276, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "data" + ], + [ + 401.55549999999999, + 546.59979999999996, + 421.14949999999999, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "must" + ], + [ + 424.9572, + 546.59979999999996, + 444.03609999999998, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "have" + ], + [ + 447.86399999999998, + 546.59979999999996, + 476.41669999999999, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "existed" + ], + [ + 480.26479999999998, + 546.59979999999996, + 487.56709999999998, + 557.78470000000004, + 10.1, + 1, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "at" + ], + [ + 491.27379999999999, + 546.59979999999996, + 503.6463, + 557.78470000000004, + 10.1, + 0, + 555.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 558.19979999999998, + 126.06789999999999, + 569.38469999999995, + 10.1, + 0, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "time" + ], + [ + 126.09820000000001, + 558.19979999999998, + 128.6232, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 131.1987, + 558.19979999999998, + 171.04320000000001, + 569.38469999999995, + 10.1, + 0, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "obviously" + ], + [ + 170.39680000000001, + 558.19979999999998, + 172.92179999999999, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 175.5882, + 558.19979999999998, + 183.3349, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 186.0821, + 558.19979999999998, + 207.3527, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "order" + ], + [ + 209.9888, + 558.19979999999998, + 217.8365, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 220.48269999999999, + 558.19979999999998, + 232.78450000000001, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "get" + ], + [ + 235.4812, + 558.19979999999998, + 251.12610000000001, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "into" + ], + [ + 253.7723, + 558.19979999999998, + 266.04379999999998, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 268.76069999999999, + 558.19979999999998, + 287.21339999999998, + 569.38469999999995, + 10.1, + 0, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "hash" + ], + [ + 287.26389999999998, + 558.19979999999998, + 289.78890000000001, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 295.05099999999999, + 558.19979999999998, + 315.1096, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "Each" + ], + [ + 317.85680000000002, + 558.19979999999998, + 359.89299999999997, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamp" + ], + [ + 362.53919999999999, + 558.19979999999998, + 396.17219999999998, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "includes" + ], + [ + 398.83859999999999, + 558.19979999999998, + 411.11009999999999, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 413.73610000000002, + 558.19979999999998, + 448.57100000000003, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "previous" + ], + [ + 451.23739999999998, + 558.19979999999998, + 493.18270000000001, + 569.38469999999995, + 10.1, + 1, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamp" + ], + [ + 495.92989999999998, + 558.19979999999998, + 503.67660000000001, + 569.38469999999995, + 10.1, + 0, + 567.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 569.89980000000003, + 117.62430000000001, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "its" + ], + [ + 120.1998, + 569.89980000000003, + 138.6525, + 581.0847, + 10.1, + 0, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "hash" + ], + [ + 138.703, + 569.89980000000003, + 141.22800000000001, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 143.80350000000001, + 569.89980000000003, + 176.25479999999999, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "forming" + ], + [ + 178.81010000000001, + 569.89980000000003, + 183.28440000000001, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 185.91040000000001, + 569.89980000000003, + 207.66579999999999, + 581.0847, + 10.1, + 0, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 207.71629999999999, + 569.89980000000003, + 210.2413, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 212.8168, + 569.89980000000003, + 230.6635, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "with" + ], + [ + 233.30969999999999, + 569.89980000000003, + 251.77250000000001, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "each" + ], + [ + 254.3278, + 569.89980000000003, + 294.7278, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "additional" + ], + [ + 297.23259999999999, + 569.89980000000003, + 339.2688, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamp" + ], + [ + 341.82409999999999, + 569.89980000000003, + 386.68830000000003, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "reinforcing" + ], + [ + 389.24360000000001, + 569.89980000000003, + 401.61610000000002, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 404.15120000000002, + 569.89980000000003, + 422.68470000000002, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "ones" + ], + [ + 425.1592, + 569.89980000000003, + 450.94450000000001, + 581.0847, + 10.1, + 1, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "before" + ], + [ + 453.57049999999998, + 569.89980000000003, + 459.16590000000002, + 581.0847, + 10.1, + 0, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 459.16590000000002, + 569.89980000000003, + 461.6909, + 581.0847, + 10.1, + 0, + 578.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 310.80000000000001, + 708.99980000000005, + 315.85000000000002, + 720.18470000000002, + 10.1, + 0, + 718, + 0, + 0, + 0, + 0, + 0, + 1, + "2" + ] + ] + ], + [ + [ + [ + 212.19999999999999, + 619.58199999999999, + 229.6446, + 627.62580000000003, + 7.2000000000000002, + 0, + 626.10000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ] + ] + ], + [ + [ + [ + 217, + 635.18200000000002, + 230.80109999999999, + 643.22580000000005, + 7.2000000000000002, + 1, + 641.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "Item" + ], + [ + 245.30000000000001, + 635.18200000000002, + 259.2004, + 643.22580000000005, + 7.2000000000000002, + 1, + 641.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "Item" + ], + [ + 277.80000000000001, + 635.18200000000002, + 279.76350000000002, + 643.22580000000005, + 7.2000000000000002, + 0, + 641.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 279.7989, + 635.18200000000002, + 281.76240000000001, + 643.22580000000005, + 7.2000000000000002, + 0, + 641.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 281.79790000000003, + 635.18200000000002, + 283.76130000000001, + 643.22580000000005, + 7.2000000000000002, + 0, + 641.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ] + ] + ], + [ + [ + [ + 340.39999999999998, + 597.98199999999997, + 357.03649999999999, + 606.0258, + 7.2000000000000002, + 0, + 604.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ] + ] + ], + [ + [ + [ + 320.30000000000001, + 619.58199999999999, + 337.74459999999999, + 627.62580000000003, + 7.2000000000000002, + 0, + 626.10000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ] + ] + ], + [ + [ + [ + 324.5, + 635.18200000000002, + 338.30110000000002, + 643.22580000000005, + 7.2000000000000002, + 1, + 641.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "Item" + ], + [ + 352.80000000000001, + 635.18200000000002, + 366.7004, + 643.22580000000005, + 7.2000000000000002, + 1, + 641.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "Item" + ], + [ + 385.30000000000001, + 635.18200000000002, + 387.26350000000002, + 643.22580000000005, + 7.2000000000000002, + 0, + 641.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 387.2989, + 635.18200000000002, + 389.26240000000001, + 643.22580000000005, + 7.2000000000000002, + 0, + 641.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 389.29790000000003, + 635.18200000000002, + 391.26130000000001, + 643.22580000000005, + 7.2000000000000002, + 0, + 641.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ] + ] + ], + [ + [ + [ + 232.30000000000001, + 597.98199999999997, + 248.9365, + 606.0258, + 7.2000000000000002, + 0, + 604.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ] + ] + ], + [ + [ + [ + 199.90000000000001, + 165.3631, + 236.18680000000001, + 173.18340000000001, + 7, + 0, + 171.69999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Transaction" + ] + ] + ], + [ + [ + [ + 209.59999999999999, + 182.26310000000001, + 230.1294, + 190.08340000000001, + 7, + 1, + 188.59999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Owner" + ], + [ + 232.00460000000001, + 182.26310000000001, + 240.70189999999999, + 190.08340000000001, + 7, + 0, + 188.59999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "1's" + ] + ] + ], + [ + [ + [ + 208.69999999999999, + 189.9631, + 227.79499999999999, + 197.7834, + 7, + 1, + 196.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Public" + ], + [ + 229.69820000000001, + 189.9631, + 241.7961, + 197.7834, + 7, + 0, + 196.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Key" + ] + ] + ], + [ + [ + [ + 209.80000000000001, + 248.9631, + 230.2244, + 256.78339999999997, + 7, + 1, + 255.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Owner" + ], + [ + 232.19759999999999, + 248.9631, + 240.89490000000001, + 256.78339999999997, + 7, + 0, + 255.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "0's" + ] + ] + ], + [ + [ + [ + 210.40000000000001, + 256.66309999999999, + 240.38239999999999, + 264.48340000000002, + 7, + 0, + 263, + 0, + 0, + 0, + 0, + 0, + 2, + "Signature" + ] + ] + ], + [ + [ + [ + 214.5, + 219.3631, + 230.78919999999999, + 227.18340000000001, + 7, + 0, + 225.69999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ] + ] + ], + [ + [ + [ + 289.30000000000001, + 165.3631, + 325.58679999999998, + 173.18340000000001, + 7, + 0, + 171.69999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Transaction" + ] + ] + ], + [ + [ + [ + 299, + 182.26310000000001, + 319.42439999999999, + 190.08340000000001, + 7, + 1, + 188.59999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Owner" + ], + [ + 321.39760000000001, + 182.26310000000001, + 330.0949, + 190.08340000000001, + 7, + 0, + 188.59999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "2's" + ] + ] + ], + [ + [ + [ + 298, + 189.9631, + 317.09500000000003, + 197.7834, + 7, + 1, + 196.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Public" + ], + [ + 319.10309999999998, + 189.9631, + 331.20100000000002, + 197.7834, + 7, + 0, + 196.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Key" + ] + ] + ], + [ + [ + [ + 298.60000000000002, + 248.9631, + 319.02440000000001, + 256.78339999999997, + 7, + 1, + 255.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Owner" + ], + [ + 320.99759999999998, + 248.9631, + 329.69490000000002, + 256.78339999999997, + 7, + 0, + 255.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "1's" + ] + ] + ], + [ + [ + [ + 299.19999999999999, + 256.66309999999999, + 329.18239999999997, + 264.48340000000002, + 7, + 0, + 263, + 0, + 0, + 0, + 0, + 0, + 2, + "Signature" + ] + ] + ], + [ + [ + [ + 303.30000000000001, + 219.3631, + 319.58920000000001, + 227.18340000000001, + 7, + 0, + 225.69999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ] + ] + ], + [ + [ + [ + 260.99900000000002, + 233.20650000000001, + 268.42779999999999, + 242.24420000000001, + 7, + 1, + 261.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "V" + ], + [ + 264.83730000000003, + 234.9074, + 271.56240000000003, + 243.63319999999999, + 7, + 1, + 265.43830000000003, + 0, + 0, + 0, + 0, + 0, + 2, + "e" + ], + [ + 268.40039999999999, + 236.4863, + 273.69900000000001, + 244.58000000000001, + 7, + 1, + 269.00139999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "r" + ], + [ + 270.50510000000003, + 237.41900000000001, + 275.09359999999998, + 245.19800000000001, + 7, + 1, + 271.10610000000003, + 0, + 0, + 0, + 0, + 0, + 2, + "i" + ], + [ + 271.97000000000003, + 238.06809999999999, + 276.91030000000001, + 246.00309999999999, + 7, + 1, + 272.57100000000003, + 0, + 0, + 0, + 0, + 0, + 2, + "f" + ], + [ + 273.7996, + 238.87889999999999, + 280.16649999999998, + 247.446, + 7, + 0, + 274.4006, + 0, + 0, + 0, + 0, + 0, + 2, + "y" + ] + ] + ], + [ + [ + [ + 378.10000000000002, + 165.3631, + 414.37979999999999, + 173.18340000000001, + 7, + 0, + 171.69999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Transaction" + ] + ] + ], + [ + [ + [ + 387.80000000000001, + 182.26310000000001, + 408.2244, + 190.08340000000001, + 7, + 1, + 188.59999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Owner" + ], + [ + 410.19760000000002, + 182.26310000000001, + 418.89490000000001, + 190.08340000000001, + 7, + 0, + 188.59999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "3's" + ] + ] + ], + [ + [ + [ + 386.80000000000001, + 189.9631, + 405.89499999999998, + 197.7834, + 7, + 1, + 196.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Public" + ], + [ + 407.90309999999999, + 189.9631, + 420.00099999999998, + 197.7834, + 7, + 0, + 196.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Key" + ] + ] + ], + [ + [ + [ + 388, + 248.9631, + 408.42439999999999, + 256.78339999999997, + 7, + 1, + 255.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Owner" + ], + [ + 410.39760000000001, + 248.9631, + 419.0949, + 256.78339999999997, + 7, + 0, + 255.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "2's" + ] + ] + ], + [ + [ + [ + 388.60000000000002, + 256.66309999999999, + 418.58940000000001, + 264.48340000000002, + 7, + 0, + 263, + 0, + 0, + 0, + 0, + 0, + 2, + "Signature" + ] + ] + ], + [ + [ + [ + 392.69999999999999, + 219.3631, + 408.98919999999998, + 227.18340000000001, + 7, + 0, + 225.69999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ] + ] + ], + [ + [ + [ + 350.09899999999999, + 233.20650000000001, + 357.52780000000001, + 242.24420000000001, + 7, + 1, + 350.69999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "V" + ], + [ + 354.03320000000002, + 234.94990000000001, + 360.75830000000002, + 243.67570000000001, + 7, + 1, + 354.63420000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "e" + ], + [ + 357.59640000000002, + 236.52889999999999, + 362.89499999999998, + 244.62260000000001, + 7, + 1, + 358.19740000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "r" + ], + [ + 359.70100000000002, + 237.4615, + 364.28949999999998, + 245.2405, + 7, + 1, + 360.30200000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "i" + ], + [ + 361.166, + 238.11070000000001, + 366.10629999999998, + 246.04560000000001, + 7, + 1, + 361.767, + 0, + 0, + 0, + 0, + 0, + 2, + "f" + ], + [ + 362.90600000000001, + 238.8817, + 369.27289999999999, + 247.44880000000001, + 7, + 0, + 363.50700000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "y" + ] + ] + ], + [ + [ + [ + 298.60000000000002, + 293.66309999999999, + 319.02440000000001, + 301.48340000000002, + 7, + 1, + 300, + 0, + 0, + 0, + 0, + 0, + 2, + "Owner" + ], + [ + 320.99759999999998, + 293.66309999999999, + 329.69490000000002, + 301.48340000000002, + 7, + 0, + 300, + 0, + 0, + 0, + 0, + 0, + 2, + "2's" + ] + ] + ], + [ + [ + [ + 296.30000000000001, + 301.4631, + 317.99090000000001, + 309.28339999999997, + 7, + 1, + 307.80000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Private" + ], + [ + 319.99900000000002, + 301.4631, + 331.99200000000002, + 309.28339999999997, + 7, + 0, + 307.80000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Key" + ] + ] + ], + [ + [ + [ + 209.80000000000001, + 293.66309999999999, + 230.2244, + 301.48340000000002, + 7, + 1, + 300, + 0, + 0, + 0, + 0, + 0, + 2, + "Owner" + ], + [ + 232.09960000000001, + 293.66309999999999, + 240.804, + 301.48340000000002, + 7, + 0, + 300, + 0, + 0, + 0, + 0, + 0, + 2, + "1's" + ] + ] + ], + [ + [ + [ + 207.5, + 301.4631, + 229.18389999999999, + 309.28339999999997, + 7, + 1, + 307.80000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Private" + ], + [ + 231.19200000000001, + 301.4631, + 243.185, + 309.28339999999997, + 7, + 0, + 307.80000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Key" + ] + ] + ], + [ + [ + [ + 352.60410000000002, + 278.33440000000002, + 361.01999999999998, + 287.38780000000003, + 7, + 0, + 286.19999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "S" + ], + [ + 356.3691, + 277.37869999999998, + 362.29739999999998, + 284.57119999999998, + 7, + 0, + 283.38339999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "i" + ], + [ + 357.65210000000002, + 275.01900000000001, + 365.45179999999999, + 283.6114, + 7, + 0, + 282.42360000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "g" + ], + [ + 360.77280000000002, + 272.68439999999998, + 368.57249999999999, + 281.27679999999998, + 7, + 0, + 280.089, + 0, + 0, + 0, + 0, + 0, + 2, + "n" + ] + ] + ], + [ + [ + [ + 263.40410000000003, + 278.33440000000002, + 271.81999999999999, + 287.38780000000003, + 7, + 0, + 286.19999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "S" + ], + [ + 267.16910000000001, + 277.37869999999998, + 273.09739999999999, + 284.57119999999998, + 7, + 0, + 283.38339999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "i" + ], + [ + 268.36810000000003, + 275.08190000000002, + 276.16770000000002, + 283.67419999999998, + 7, + 0, + 282.4864, + 0, + 0, + 0, + 0, + 0, + 2, + "g" + ], + [ + 271.48880000000003, + 272.7473, + 279.28840000000002, + 281.33960000000002, + 7, + 0, + 280.15179999999998, + 0, + 0, + 0, + 0, + 0, + 2, + "n" + ] + ] + ], + [ + [ + [ + 388, + 293.66309999999999, + 408.42439999999999, + 301.48340000000002, + 7, + 1, + 300, + 0, + 0, + 0, + 0, + 0, + 2, + "Owner" + ], + [ + 410.2996, + 293.66309999999999, + 418.99700000000001, + 301.48340000000002, + 7, + 0, + 300, + 0, + 0, + 0, + 0, + 0, + 2, + "3's" + ] + ] + ], + [ + [ + [ + 385.69999999999999, + 301.4631, + 407.38389999999998, + 309.28339999999997, + 7, + 1, + 307.80000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Private" + ], + [ + 409.392, + 301.4631, + 421.38499999999999, + 309.28339999999997, + 7, + 0, + 307.80000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Key" + ] + ] + ] + ] + ] + ] + ] + ] + ], + [ + 612, + 792, + [ + [ + [ + [ + 0, + 0, + 0, + 0, + [ + [ + [ + [ + 108.09999999999999, + 82.162800000000004, + 114.70099999999999, + 95.987499999999997, + 11.5, + 0, + 93.5, + 0, + 0, + 0, + 0, + 0, + 0, + "4" + ], + [ + 114.70099999999999, + 82.162800000000004, + 117.8865, + 95.987499999999997, + 11.5, + 1, + 93.5, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 133.30000000000001, + 82.162800000000004, + 217.6755, + 95.987499999999997, + 11.5, + 0, + 93.5, + 0, + 0, + 0, + 0, + 0, + 0, + "Proof-of-Work" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 103.5998, + 118.6545, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "To" + ], + [ + 121.40170000000001, + 103.5998, + 164.5994, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "implement" + ], + [ + 167.2961, + 103.5998, + 171.7704, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 174.4873, + 103.5998, + 217.6345, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "distributed" + ], + [ + 220.3817, + 103.5998, + 262.428, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamp" + ], + [ + 265.27620000000002, + 103.5998, + 289.84949999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "server" + ], + [ + 292.68759999999997, + 103.5998, + 302.7371, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 305.48430000000002, + 103.5998, + 309.95859999999999, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 312.6755, + 103.5998, + 361.86250000000001, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "peer-to-peer" + ], + [ + 364.59960000000001, + 103.5998, + 384.72890000000001, + 114.7847, + 10.1, + 0, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "basis" + ], + [ + 384.7996, + 103.5998, + 387.32459999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 389.99099999999999, + 103.5998, + 401.76760000000002, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "we" + ], + [ + 404.48450000000003, + 103.5998, + 420.17989999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "will" + ], + [ + 422.9776, + 103.5998, + 442.03629999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "need" + ], + [ + 444.7835, + 103.5998, + 452.53019999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 455.3784, + 103.5998, + 468.75080000000003, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "use" + ], + [ + 471.56869999999998, + 103.5998, + 476.04300000000001, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 478.76999999999998, + 103.5998, + 503.93920000000003, + 114.7847, + 10.1, + 0, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 115.2998, + 140.66239999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "of-work" + ], + [ + 144.61150000000001, + 115.2998, + 172.7501, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "system" + ], + [ + 176.70930000000001, + 115.2998, + 204.7671, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "similar" + ], + [ + 208.70609999999999, + 115.2998, + 216.5538, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 219.99789999999999, + 115.2998, + 244.65199999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "Adam" + ], + [ + 248.7021, + 115.2998, + 275.13380000000001, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "Back's" + ], + [ + 279.10309999999998, + 115.2998, + 317.76589999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "Hashcash" + ], + [ + 321.81599999999997, + 115.2998, + 325.17930000000001, + 126.4847, + 10.1, + 0, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "[" + ], + [ + 325.21969999999999, + 115.2998, + 330.2697, + 126.4847, + 10.1, + 0, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "6" + ], + [ + 330.2192, + 115.2998, + 333.58249999999998, + 126.4847, + 10.1, + 0, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "]" + ], + [ + 333.62290000000002, + 115.2998, + 336.14789999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 340.12729999999999, + 115.2998, + 363.70069999999998, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "rather" + ], + [ + 367.6397, + 115.2998, + 384.99149999999997, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "than" + ], + [ + 389.04160000000002, + 115.2998, + 432.21910000000003, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "newspaper" + ], + [ + 436.25909999999999, + 115.2998, + 444.62189999999998, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "or" + ], + [ + 448.6619, + 115.2998, + 476.66919999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "Usenet" + ], + [ + 480.6789, + 115.2998, + 501.40410000000003, + 126.4847, + 10.1, + 0, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "posts" + ], + [ + 501.37380000000002, + 115.2998, + 503.89879999999999, + 126.4847, + 10.1, + 0, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 126.8998, + 123.7752, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 127.3001, + 126.8998, + 184.97110000000001, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 188.5162, + 126.8998, + 222.74510000000001, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "involves" + ], + [ + 226.11850000000001, + 126.8998, + 261.9735, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "scanning" + ], + [ + 265.51859999999999, + 126.8998, + 277.1841, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 280.71910000000003, + 126.8998, + 285.1934, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 288.6173, + 126.8998, + 310.4939, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "value" + ], + [ + 313.9178, + 126.8998, + 329.01729999999998, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 332.52199999999999, + 126.8998, + 354.2774, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "when" + ], + [ + 357.82249999999999, + 126.8998, + 385.77929999999998, + 138.0847, + 10.1, + 0, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "hashed" + ], + [ + 385.82979999999998, + 126.8998, + 388.35480000000001, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 391.82920000000001, + 126.8998, + 410.28190000000001, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "such" + ], + [ + 413.73610000000002, + 126.8998, + 422.1696, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 425.54300000000001, + 126.8998, + 443.4907, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "with" + ], + [ + 446.94490000000002, + 126.8998, + 485.6986, + 138.0847, + 10.1, + 0, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "SHA-256" + ], + [ + 485.7491, + 126.8998, + 488.27409999999998, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 491.64749999999998, + 126.8998, + 503.91899999999998, + 138.0847, + 10.1, + 0, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 138.59979999999999, + 126.5527, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "hash" + ], + [ + 129.50190000000001, + 138.59979999999999, + 155.83260000000001, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "begins" + ], + [ + 158.70099999999999, + 138.59979999999999, + 176.54769999999999, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "with" + ], + [ + 179.49690000000001, + 138.59979999999999, + 183.97120000000001, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 186.78909999999999, + 138.59979999999999, + 217.55369999999999, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "number" + ], + [ + 220.3817, + 138.59979999999999, + 228.84549999999999, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 231.68360000000001, + 138.59979999999999, + 249.0455, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "zero" + ], + [ + 251.8937, + 138.59979999999999, + 266.51850000000002, + 149.78469999999999, + 10.1, + 0, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "bits" + ], + [ + 266.48820000000001, + 138.59979999999999, + 269.01319999999998, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 274.48739999999998, + 138.59979999999999, + 290.1626, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 292.98050000000001, + 138.59979999999999, + 324.37130000000002, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "average" + ], + [ + 327.18920000000003, + 138.59979999999999, + 347.94470000000001, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "work" + ], + [ + 350.79289999999997, + 138.59979999999999, + 384.45620000000002, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "required" + ], + [ + 387.30439999999999, + 138.59979999999999, + 394.03100000000001, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 396.89940000000001, + 138.59979999999999, + 444.00580000000002, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "exponential" + ], + [ + 446.80349999999999, + 138.59979999999999, + 454.65120000000002, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 457.49939999999998, + 138.59979999999999, + 469.77089999999998, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 472.68979999999999, + 138.59979999999999, + 503.45440000000002, + 149.78469999999999, + 10.1, + 0, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "number" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 150.19980000000001, + 116.5638, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 119.0989, + 150.19980000000001, + 136.46080000000001, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "zero" + ], + [ + 139.01609999999999, + 150.19980000000001, + 153.53989999999999, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "bits" + ], + [ + 156.11539999999999, + 150.19980000000001, + 189.77869999999999, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "required" + ], + [ + 192.334, + 150.19980000000001, + 206.88810000000001, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 209.4434, + 150.19980000000001, + 223.4016, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "can" + ], + [ + 225.95689999999999, + 150.19980000000001, + 235.5317, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 238.0668, + 150.19980000000001, + 269.4273, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "verified" + ], + [ + 271.98259999999999, + 150.19980000000001, + 282.13310000000001, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 284.67829999999998, + 150.19980000000001, + 323.83600000000001, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "executing" + ], + [ + 326.48219999999998, + 150.19980000000001, + 330.95650000000001, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 333.48149999999998, + 150.19980000000001, + 357.5498, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "single" + ], + [ + 360.0849, + 150.19980000000001, + 378.5376, + 161.38470000000001, + 10.1, + 0, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "hash" + ], + [ + 378.5881, + 150.19980000000001, + 381.11309999999997, + 161.38470000000001, + 10.1, + 0, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 161.8998, + 136.5592, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "For" + ], + [ + 139.48820000000001, + 161.8998, + 152.95150000000001, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "our" + ], + [ + 155.98150000000001, + 161.8998, + 198.01769999999999, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamp" + ], + [ + 200.96690000000001, + 161.8998, + 234.02420000000001, + 173.0847, + 10.1, + 0, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 234.07470000000001, + 161.8998, + 236.59970000000001, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 239.56909999999999, + 161.8998, + 251.34569999999999, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "we" + ], + [ + 254.3656, + 161.8998, + 297.56330000000003, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "implement" + ], + [ + 300.56299999999999, + 161.8998, + 312.83449999999999, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 315.8544, + 161.8998, + 373.62639999999999, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 376.67660000000001, + 161.8998, + 386.82709999999997, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 389.67529999999999, + 161.8998, + 442.93259999999998, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "incrementing" + ], + [ + 445.9828, + 161.8998, + 450.45710000000003, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 453.47699999999998, + 161.8998, + 477.55540000000002, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "nonce" + ], + [ + 480.57530000000003, + 161.8998, + 488.322, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 491.37220000000002, + 161.8998, + 503.64370000000002, + 173.0847, + 10.1, + 0, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 173.49979999999999, + 130.5523, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 134.40039999999999, + 173.49979999999999, + 152.89349999999999, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "until" + ], + [ + 156.7012, + 173.49979999999999, + 161.1755, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 165.1044, + 173.49979999999999, + 186.88, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "value" + ], + [ + 190.80889999999999, + 173.49979999999999, + 197.53550000000001, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 201.31290000000001, + 173.49979999999999, + 224.86609999999999, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "found" + ], + [ + 228.71420000000001, + 173.49979999999999, + 243.91470000000001, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 247.72239999999999, + 173.49979999999999, + 269.05360000000002, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "gives" + ], + [ + 272.82089999999999, + 173.49979999999999, + 285.1934, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 289.0213, + 173.49979999999999, + 317.14980000000003, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "block's" + ], + [ + 321.0181, + 173.49979999999999, + 339.4708, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "hash" + ], + [ + 343.41989999999998, + 173.49979999999999, + 355.69139999999999, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 359.51929999999999, + 173.49979999999999, + 393.18259999999998, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "required" + ], + [ + 397.03070000000002, + 173.49979999999999, + 414.39260000000002, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "zero" + ], + [ + 418.2407, + 173.49979999999999, + 432.8655, + 184.68469999999999, + 10.1, + 0, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "bits" + ], + [ + 432.83519999999999, + 173.49979999999999, + 435.36020000000002, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 443.03620000000001, + 173.49979999999999, + 464.31689999999998, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "Once" + ], + [ + 468.14479999999998, + 173.49979999999999, + 480.51729999999998, + 184.68469999999999, + 10.1, + 1, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 484.34519999999998, + 173.49979999999999, + 503.92910000000001, + 184.68469999999999, + 10.1, + 0, + 182.5, + 0, + 0, + 0, + 0, + 0, + 1, + "CPU" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 185.19980000000001, + 130.4109, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "effort" + ], + [ + 134.90539999999999, + 185.19980000000001, + 148.33840000000001, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "has" + ], + [ + 152.81270000000001, + 185.19980000000001, + 171.87139999999999, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "been" + ], + [ + 176.32550000000001, + 185.19980000000001, + 214.98830000000001, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "expended" + ], + [ + 219.54339999999999, + 185.19980000000001, + 227.39109999999999, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 231.93610000000001, + 185.19980000000001, + 253.71170000000001, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "make" + ], + [ + 258.2466, + 185.19980000000001, + 263.84199999999998, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 268.34660000000002, + 185.19980000000001, + 294.69749999999999, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "satisfy" + ], + [ + 299.24250000000001, + 185.19980000000001, + 311.51400000000001, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 316.03879999999998, + 185.19980000000001, + 373.81079999999997, + 196.38470000000001, + 10.1, + 0, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 373.86130000000003, + 185.19980000000001, + 376.38630000000001, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 380.86059999999998, + 185.19980000000001, + 393.13209999999998, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 397.66699999999997, + 185.19980000000001, + 420.01830000000001, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 424.57339999999999, + 185.19980000000001, + 451.47980000000001, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "cannot" + ], + [ + 455.98439999999999, + 185.19980000000001, + 465.45819999999998, + 196.38470000000001, + 10.1, + 1, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 469.99310000000003, + 185.19980000000001, + 503.65640000000002, + 196.38470000000001, + 10.1, + 0, + 194.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "changed" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 196.7998, + 138.99590000000001, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "without" + ], + [ + 143.00559999999999, + 196.7998, + 173.86109999999999, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "redoing" + ], + [ + 177.91120000000001, + 196.7998, + 190.18270000000001, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 194.31360000000001, + 196.7998, + 214.96809999999999, + 207.9847, + 10.1, + 0, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "work" + ], + [ + 215.01859999999999, + 196.7998, + 217.5436, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 225.01759999999999, + 196.7998, + 236.24879999999999, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "As" + ], + [ + 240.31909999999999, + 196.7998, + 258.18599999999998, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "later" + ], + [ + 262.327, + 196.7998, + 288.65769999999998, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "blocks" + ], + [ + 292.72800000000001, + 196.7998, + 305.00959999999998, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "are" + ], + [ + 309.03949999999998, + 196.7998, + 340.39999999999998, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "chained" + ], + [ + 344.55110000000002, + 196.7998, + 363.024, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "after" + ], + [ + 367.06400000000002, + 196.7998, + 372.65940000000001, + 207.9847, + 10.1, + 0, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 372.65940000000001, + 196.7998, + 375.18439999999998, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 379.26479999999998, + 196.7998, + 391.53629999999998, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 395.56619999999998, + 196.7998, + 416.32170000000002, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "work" + ], + [ + 420.37180000000001, + 196.7998, + 428.21949999999998, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 432.26960000000003, + 196.7998, + 460.8526, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "change" + ], + [ + 464.88249999999999, + 196.7998, + 477.255, + 207.9847, + 10.1, + 1, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 481.28489999999999, + 196.7998, + 503.63619999999997, + 207.9847, + 10.1, + 0, + 205.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 208.49979999999999, + 133.34999999999999, + 219.68469999999999, + 10.1, + 1, + 217.5, + 0, + 0, + 0, + 0, + 0, + 1, + "would" + ], + [ + 135.90530000000001, + 208.49979999999999, + 165.57910000000001, + 219.68469999999999, + 10.1, + 1, + 217.5, + 0, + 0, + 0, + 0, + 0, + 1, + "include" + ], + [ + 168.20509999999999, + 208.49979999999999, + 198.95959999999999, + 219.68469999999999, + 10.1, + 1, + 217.5, + 0, + 0, + 0, + 0, + 0, + 1, + "redoing" + ], + [ + 201.51490000000001, + 208.49979999999999, + 211.61490000000001, + 219.68469999999999, + 10.1, + 1, + 217.5, + 0, + 0, + 0, + 0, + 0, + 1, + "all" + ], + [ + 214.2106, + 208.49979999999999, + 226.4821, + 219.68469999999999, + 10.1, + 1, + 217.5, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 229.0172, + 208.49979999999999, + 255.44890000000001, + 219.68469999999999, + 10.1, + 1, + 217.5, + 0, + 0, + 0, + 0, + 0, + 1, + "blocks" + ], + [ + 257.92340000000002, + 208.49979999999999, + 276.3963, + 219.68469999999999, + 10.1, + 1, + 217.5, + 0, + 0, + 0, + 0, + 0, + 1, + "after" + ], + [ + 279.0324, + 208.49979999999999, + 284.62779999999998, + 219.68469999999999, + 10.1, + 0, + 217.5, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 284.62779999999998, + 208.49979999999999, + 287.15280000000001, + 219.68469999999999, + 10.1, + 0, + 217.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 295.2998, + 138.17519999999999, + 306.48469999999998, + 10.1, + 1, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 140.9931, + 295.2998, + 198.66409999999999, + 306.48469999999998, + 10.1, + 1, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 201.51230000000001, + 295.2998, + 217.66220000000001, + 306.48469999999998, + 10.1, + 1, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "also" + ], + [ + 220.5104, + 295.2998, + 245.74019999999999, + 306.48469999999998, + 10.1, + 1, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "solves" + ], + [ + 248.5076, + 295.2998, + 260.77910000000003, + 306.48469999999998, + 10.1, + 1, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 263.49599999999998, + 295.2998, + 297.14920000000001, + 306.48469999999998, + 10.1, + 1, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "problem" + ], + [ + 299.89640000000003, + 295.2998, + 308.36020000000002, + 306.48469999999998, + 10.1, + 1, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 311.09730000000002, + 295.2998, + 359.85000000000002, + 306.48469999999998, + 10.1, + 1, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "determining" + ], + [ + 362.59719999999999, + 295.2998, + 419.76319999999998, + 306.48469999999998, + 10.1, + 1, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "representation" + ], + [ + 422.5104, + 295.2998, + 430.35809999999998, + 306.48469999999998, + 10.1, + 1, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 433.1053, + 295.2998, + 467.3544, + 306.48469999999998, + 10.1, + 1, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "majority" + ], + [ + 470.10160000000002, + 295.2998, + 503.75479999999999, + 306.48469999999998, + 10.1, + 0, + 304.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "decision" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 306.89980000000003, + 138.34950000000001, + 318.0847, + 10.1, + 0, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "making" + ], + [ + 138.40000000000001, + 306.89980000000003, + 140.92500000000001, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 146.49010000000001, + 306.89980000000003, + 153.25710000000001, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "If" + ], + [ + 155.99420000000001, + 306.89980000000003, + 168.36670000000001, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 171.08359999999999, + 306.89980000000003, + 205.33269999999999, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "majority" + ], + [ + 208.07990000000001, + 306.89980000000003, + 227.76480000000001, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "were" + ], + [ + 230.48169999999999, + 306.89980000000003, + 253.43899999999999, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "based" + ], + [ + 256.28719999999998, + 306.89980000000003, + 266.43770000000001, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 269.18490000000003, + 306.89980000000003, + 368.48809999999997, + 318.0847, + 10.1, + 0, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "one-IP-address-one-vote" + ], + [ + 368.51839999999999, + 306.89980000000003, + 371.04340000000002, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 373.81079999999997, + 306.89980000000003, + 379.40620000000001, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 382.20389999999998, + 306.89980000000003, + 404.55520000000001, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "could" + ], + [ + 407.40339999999998, + 306.89980000000003, + 416.97820000000002, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 419.70519999999999, + 306.89980000000003, + 458.96390000000002, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "subverted" + ], + [ + 461.81209999999999, + 306.89980000000003, + 471.96260000000001, + 318.0847, + 10.1, + 1, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 474.60879999999997, + 306.89980000000003, + 503.78769999999997, + 318.0847, + 10.1, + 0, + 315.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "anyone" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 318.59980000000002, + 124.97709999999999, + 329.78469999999999, + 10.1, + 1, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "able" + ], + [ + 130.4008, + 318.59980000000002, + 138.24850000000001, + 329.78469999999999, + 10.1, + 1, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 143.80350000000001, + 318.59980000000002, + 175.1842, + 329.78469999999999, + 10.1, + 1, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "allocate" + ], + [ + 180.6079, + 318.59980000000002, + 203.06020000000001, + 329.78469999999999, + 10.1, + 1, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "many" + ], + [ + 208.61519999999999, + 318.59980000000002, + 221.44220000000001, + 329.78469999999999, + 10.1, + 0, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "IPs" + ], + [ + 221.5129, + 318.59980000000002, + 224.03790000000001, + 329.78469999999999, + 10.1, + 1, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 235.01660000000001, + 318.59980000000002, + 293.29360000000003, + 329.78469999999999, + 10.1, + 1, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "Proof-of-work" + ], + [ + 298.83850000000001, + 318.59980000000002, + 305.56509999999997, + 329.78469999999999, + 10.1, + 1, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 311.03930000000003, + 318.59980000000002, + 353.69159999999999, + 329.78469999999999, + 10.1, + 1, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "essentially" + ], + [ + 359.1456, + 318.59980000000002, + 435.43090000000001, + 329.78469999999999, + 10.1, + 0, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "one-CPU-one-vote" + ], + [ + 435.46120000000002, + 318.59980000000002, + 437.9862, + 329.78469999999999, + 10.1, + 1, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 448.7629, + 318.59980000000002, + 464.43810000000002, + 329.78469999999999, + 10.1, + 1, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 469.96280000000002, + 318.59980000000002, + 504.21190000000001, + 329.78469999999999, + 10.1, + 0, + 327.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "majority" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 330.19979999999998, + 141.75319999999999, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "decision" + ], + [ + 144.70240000000001, + 330.19979999999998, + 151.429, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 154.29740000000001, + 330.19979999999998, + 200.76750000000001, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "represented" + ], + [ + 203.7167, + 330.19979999999998, + 213.8672, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 216.71539999999999, + 330.19979999999998, + 228.98689999999999, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 231.9058, + 330.19979999999998, + 261.10489999999999, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "longest" + ], + [ + 264.00360000000001, + 330.19979999999998, + 285.75900000000001, + 341.38470000000001, + 10.1, + 0, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 285.80950000000001, + 330.19979999999998, + 288.33449999999999, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 291.2029, + 330.19979999999998, + 315.85700000000003, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "which" + ], + [ + 318.80619999999999, + 330.19979999999998, + 332.34019999999998, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "has" + ], + [ + 335.10759999999999, + 330.19979999999998, + 347.48009999999999, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 350.399, + 330.19979999999998, + 381.709, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "greatest" + ], + [ + 384.70870000000002, + 330.19979999999998, + 442.37970000000001, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 445.32889999999998, + 330.19979999999998, + 467.63979999999998, + 341.38470000000001, + 10.1, + 1, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "effort" + ], + [ + 470.5385, + 330.19979999999998, + 504.09070000000003, + 341.38470000000001, + 10.1, + 0, + 339.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "invested" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 341.89980000000003, + 115.9477, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 119.1999, + 341.89980000000003, + 124.7953, + 353.0847, + 10.1, + 0, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 124.7953, + 341.89980000000003, + 127.3203, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 133.7944, + 341.89980000000003, + 140.56139999999999, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "If" + ], + [ + 143.79339999999999, + 341.89980000000003, + 148.26769999999999, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 151.4896, + 341.89980000000003, + 185.73869999999999, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "majority" + ], + [ + 188.88990000000001, + 341.89980000000003, + 197.3537, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 200.5857, + 341.89980000000003, + 220.1696, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "CPU" + ], + [ + 223.37129999999999, + 341.89980000000003, + 248.64150000000001, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "power" + ], + [ + 251.87350000000001, + 341.89980000000003, + 258.6001, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 261.8725, + 341.89980000000003, + 302.72699999999998, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "controlled" + ], + [ + 306.08019999999999, + 341.89980000000003, + 316.23070000000001, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 319.38189999999997, + 341.89980000000003, + 345.6823, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "honest" + ], + [ + 348.98500000000001, + 341.89980000000003, + 372.51799999999997, + 353.0847, + 10.1, + 0, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 372.48770000000002, + 341.89980000000003, + 375.0127, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 378.2851, + 341.89980000000003, + 390.5566, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 393.87950000000001, + 341.89980000000003, + 420.17989999999998, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "honest" + ], + [ + 423.48259999999999, + 341.89980000000003, + 445.238, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 448.59120000000001, + 341.89980000000003, + 464.28660000000002, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "will" + ], + [ + 467.48829999999998, + 341.89980000000003, + 488.2842, + 353.0847, + 10.1, + 1, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "grow" + ], + [ + 491.48590000000002, + 341.89980000000003, + 503.75740000000002, + 353.0847, + 10.1, + 0, + 350.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 353.49979999999999, + 133.8954, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "fastest" + ], + [ + 137.70310000000001, + 353.49979999999999, + 152.25720000000001, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 156.0043, + 353.49979999999999, + 187.38499999999999, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "outpace" + ], + [ + 191.11189999999999, + 353.49979999999999, + 205.767, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "any" + ], + [ + 209.41309999999999, + 353.49979999999999, + 252.06540000000001, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "competing" + ], + [ + 255.8125, + 353.49979999999999, + 281.64830000000001, + 364.68470000000002, + 10.1, + 0, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "chains" + ], + [ + 281.61799999999999, + 353.49979999999999, + 284.14299999999997, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 291.41500000000002, + 353.49979999999999, + 301.86849999999998, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "To" + ], + [ + 305.61559999999997, + 353.49979999999999, + 334.86520000000002, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "modify" + ], + [ + 338.51130000000001, + 353.49979999999999, + 342.98559999999998, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 346.71249999999998, + 353.49979999999999, + 363.01389999999998, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "past" + ], + [ + 366.72059999999999, + 353.49979999999999, + 389.17290000000003, + 364.68470000000002, + 10.1, + 0, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 389.22340000000003, + 353.49979999999999, + 391.7484, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 395.4248, + 353.49979999999999, + 404.9794, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "an" + ], + [ + 408.72649999999999, + 353.49979999999999, + 440.70310000000001, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "attacker" + ], + [ + 444.44009999999997, + 353.49979999999999, + 469.58909999999997, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "would" + ], + [ + 473.43720000000002, + 353.49979999999999, + 492.4151, + 364.68470000000002, + 10.1, + 1, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "have" + ], + [ + 496.142, + 353.49979999999999, + 503.98970000000003, + 364.68470000000002, + 10.1, + 0, + 362.5, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 365.19979999999998, + 126.0578, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "redo" + ], + [ + 128.90600000000001, + 365.19979999999998, + 141.17750000000001, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 144.09639999999999, + 365.19979999999998, + 201.86840000000001, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 204.7166, + 365.19979999999998, + 213.18039999999999, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 215.91749999999999, + 365.19979999999998, + 228.28999999999999, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 231.1079, + 365.19979999999998, + 253.45920000000001, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 256.40839999999997, + 365.19979999999998, + 270.96249999999998, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 273.8107, + 365.19979999999998, + 283.91070000000002, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "all" + ], + [ + 286.70839999999998, + 365.19979999999998, + 313.03910000000002, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "blocks" + ], + [ + 315.90750000000003, + 365.19979999999998, + 334.38040000000001, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "after" + ], + [ + 337.20839999999998, + 365.19979999999998, + 342.80380000000002, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 345.70249999999999, + 365.19979999999998, + 360.25659999999999, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 363.10480000000001, + 365.19979999999998, + 380.45659999999998, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "then" + ], + [ + 383.3048, + 365.19979999999998, + 404.56529999999998, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "catch" + ], + [ + 407.4135, + 365.19979999999998, + 417.56400000000002, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "up" + ], + [ + 420.41219999999998, + 365.19979999999998, + 438.25889999999998, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "with" + ], + [ + 441.1071, + 365.19979999999998, + 455.66120000000001, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 458.50940000000003, + 365.19979999999998, + 488.24380000000002, + 376.38470000000001, + 10.1, + 1, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "surpass" + ], + [ + 491.11219999999997, + 365.19979999999998, + 503.48469999999998, + 376.38470000000001, + 10.1, + 0, + 374.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 376.7998, + 128.85550000000001, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "work" + ], + [ + 131.41079999999999, + 376.7998, + 139.87459999999999, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 142.40969999999999, + 376.7998, + 154.68119999999999, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 157.21629999999999, + 376.7998, + 183.61770000000001, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "honest" + ], + [ + 186.1225, + 376.7998, + 209.75649999999999, + 387.98469999999998, + 10.1, + 0, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 209.72620000000001, + 376.7998, + 212.25120000000001, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 217.1396, + 376.7998, + 230.31, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "We" + ], + [ + 232.8451, + 376.7998, + 248.54050000000001, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "will" + ], + [ + 251.1362, + 376.7998, + 272.42700000000002, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "show" + ], + [ + 274.94189999999998, + 376.7998, + 292.90980000000002, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "later" + ], + [ + 295.45499999999998, + 376.7998, + 310.55450000000002, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 313.15019999999998, + 376.7998, + 325.42169999999999, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 327.95679999999999, + 376.7998, + 372.30590000000001, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "probability" + ], + [ + 374.8612, + 376.7998, + 383.32499999999999, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 385.76920000000001, + 376.7998, + 390.24349999999998, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 392.77859999999998, + 376.7998, + 419.74560000000002, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "slower" + ], + [ + 422.29079999999999, + 376.7998, + 454.26740000000001, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "attacker" + ], + [ + 456.81259999999997, + 376.7998, + 490.9708, + 387.98469999999998, + 10.1, + 1, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "catching" + ], + [ + 493.52609999999999, + 376.7998, + 503.67660000000001, + 387.98469999999998, + 10.1, + 0, + 385.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "up" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 388.49979999999999, + 151.9239, + 399.68470000000002, + 10.1, + 1, + 397.5, + 0, + 0, + 0, + 0, + 0, + 1, + "diminishes" + ], + [ + 154.39840000000001, + 388.49979999999999, + 209.45349999999999, + 399.68470000000002, + 10.1, + 1, + 397.5, + 0, + 0, + 0, + 0, + 0, + 1, + "exponentially" + ], + [ + 211.90780000000001, + 388.49979999999999, + 220.34129999999999, + 399.68470000000002, + 10.1, + 1, + 397.5, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 222.91679999999999, + 388.49979999999999, + 267.82139999999998, + 399.68470000000002, + 10.1, + 1, + 397.5, + 0, + 0, + 0, + 0, + 0, + 1, + "subsequent" + ], + [ + 270.32619999999997, + 388.49979999999999, + 296.65690000000001, + 399.68470000000002, + 10.1, + 1, + 397.5, + 0, + 0, + 0, + 0, + 0, + 1, + "blocks" + ], + [ + 299.23239999999998, + 388.49979999999999, + 311.51400000000001, + 399.68470000000002, + 10.1, + 1, + 397.5, + 0, + 0, + 0, + 0, + 0, + 1, + "are" + ], + [ + 314.13999999999999, + 388.49979999999999, + 338.19819999999999, + 399.68470000000002, + 10.1, + 0, + 397.5, + 0, + 0, + 0, + 0, + 0, + 1, + "added" + ], + [ + 338.24869999999999, + 388.49979999999999, + 340.77370000000002, + 399.68470000000002, + 10.1, + 0, + 397.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 400.09980000000002, + 133.05449999999999, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "To" + ], + [ + 135.59970000000001, + 400.09980000000002, + 183.1808, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "compensate" + ], + [ + 185.80680000000001, + 400.09980000000002, + 197.57329999999999, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 200.11850000000001, + 400.09980000000002, + 241.57900000000001, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "increasing" + ], + [ + 244.1343, + 400.09980000000002, + 281.72649999999999, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "hardware" + ], + [ + 284.26159999999999, + 400.09980000000002, + 307.21890000000002, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "speed" + ], + [ + 309.77420000000001, + 400.09980000000002, + 324.32830000000001, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 326.97449999999998, + 400.09980000000002, + 357.72899999999998, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "varying" + ], + [ + 360.28429999999997, + 400.09980000000002, + 389.98840000000001, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "interest" + ], + [ + 392.5942, + 400.09980000000002, + 400.44189999999998, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 402.99720000000002, + 400.09980000000002, + 434.3476, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "running" + ], + [ + 436.90289999999999, + 400.09980000000002, + 460.5369, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 463.01139999999998, + 400.09980000000002, + 480.97930000000002, + 411.28469999999999, + 10.1, + 1, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "over" + ], + [ + 483.52449999999999, + 400.09980000000002, + 501.39139999999998, + 411.28469999999999, + 10.1, + 0, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "time" + ], + [ + 501.42169999999999, + 400.09980000000002, + 503.94670000000002, + 411.28469999999999, + 10.1, + 0, + 409.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 411.7998, + 120.4725, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 123.7954, + 411.7998, + 181.56739999999999, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 184.92060000000001, + 411.7998, + 222.37139999999999, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "difficulty" + ], + [ + 225.72460000000001, + 411.7998, + 232.4512, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 235.7236, + 411.7998, + 281.18369999999999, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "determined" + ], + [ + 284.5369, + 411.7998, + 294.68740000000003, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 297.93959999999998, + 411.7998, + 302.41390000000001, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 305.73680000000002, + 411.7998, + 336.5822, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "moving" + ], + [ + 339.93540000000002, + 411.7998, + 371.32619999999997, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "average" + ], + [ + 374.64909999999998, + 411.7998, + 410.40309999999999, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "targeting" + ], + [ + 413.75630000000001, + 411.7998, + 423.2099, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "an" + ], + [ + 426.56310000000002, + 411.7998, + 457.95389999999998, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "average" + ], + [ + 461.37779999999998, + 411.7998, + 492.14240000000001, + 422.98469999999998, + 10.1, + 1, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "number" + ], + [ + 495.57639999999998, + 411.7998, + 504.04020000000003, + 422.98469999999998, + 10.1, + 0, + 420.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 423.39980000000003, + 134.5317, + 434.5847, + 10.1, + 1, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "blocks" + ], + [ + 137.00620000000001, + 423.39980000000003, + 149.87360000000001, + 434.5847, + 10.1, + 1, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "per" + ], + [ + 152.50970000000001, + 423.39980000000003, + 170.9725, + 434.5847, + 10.1, + 0, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "hour" + ], + [ + 170.40690000000001, + 423.39980000000003, + 172.93190000000001, + 434.5847, + 10.1, + 1, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 178.01220000000001, + 423.39980000000003, + 184.7792, + 434.5847, + 10.1, + 1, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "If" + ], + [ + 187.2234, + 423.39980000000003, + 214.3015, + 434.5847, + 10.1, + 1, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "they're" + ], + [ + 216.8366, + 423.39980000000003, + 256.10539999999997, + 434.5847, + 10.1, + 1, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "generated" + ], + [ + 258.66070000000002, + 423.39980000000003, + 271.50790000000001, + 434.5847, + 10.1, + 1, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "too" + ], + [ + 274.06319999999999, + 423.39980000000003, + 288.65769999999998, + 434.5847, + 10.1, + 0, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "fast" + ], + [ + 288.65769999999998, + 423.39980000000003, + 291.18270000000001, + 434.5847, + 10.1, + 1, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 293.75819999999999, + 423.39980000000003, + 306.02969999999999, + 434.5847, + 10.1, + 1, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 308.65570000000002, + 423.39980000000003, + 346.00549999999998, + 434.5847, + 10.1, + 1, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "difficulty" + ], + [ + 348.56079999999997, + 423.39980000000003, + 385.60759999999999, + 434.5847, + 10.1, + 0, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "increases" + ], + [ + 385.57729999999998, + 423.39980000000003, + 388.10230000000001, + 434.5847, + 10.1, + 0, + 432.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 449.76280000000003, + 114.70099999999999, + 463.58749999999998, + 11.5, + 0, + 461.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "5" + ], + [ + 114.70099999999999, + 449.76280000000003, + 117.8865, + 463.58749999999998, + 11.5, + 1, + 461.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 133.30000000000001, + 449.76280000000003, + 185.26849999999999, + 463.58749999999998, + 11.5, + 0, + 461.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "Network" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 471.19979999999998, + 123.7752, + 482.38470000000001, + 10.1, + 1, + 480.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 126.4012, + 471.19979999999998, + 146.53049999999999, + 482.38470000000001, + 10.1, + 1, + 480.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "steps" + ], + [ + 149.10599999999999, + 471.19979999999998, + 156.8527, + 482.38470000000001, + 10.1, + 1, + 480.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 159.49889999999999, + 471.19979999999998, + 172.9521, + 482.38470000000001, + 10.1, + 1, + 480.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "run" + ], + [ + 175.4973, + 471.19979999999998, + 187.7688, + 482.38470000000001, + 10.1, + 1, + 480.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 190.3039, + 471.19979999999998, + 223.3612, + 482.38470000000001, + 10.1, + 1, + 480.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 226.00739999999999, + 471.19979999999998, + 238.28899999999999, + 482.38470000000001, + 10.1, + 1, + 480.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "are" + ], + [ + 240.82409999999999, + 471.19979999999998, + 249.2576, + 482.38470000000001, + 10.1, + 1, + 480.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 251.82300000000001, + 471.19979999999998, + 282.0523, + 482.38470000000001, + 10.1, + 0, + 480.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "follows" + ], + [ + 282.02199999999999, + 471.19979999999998, + 284.81970000000001, + 482.38470000000001, + 10.1, + 0, + 480.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + ":" + ] + ] + ], + [ + [ + [ + 122.5, + 494.49979999999999, + 127.55, + 505.68470000000002, + 10.1, + 0, + 503.5, + 0, + 0, + 0, + 0, + 0, + 1, + "1" + ], + [ + 127.6005, + 494.49979999999999, + 130.96379999999999, + 505.68470000000002, + 10.1, + 1, + 503.5, + 0, + 0, + 0, + 0, + 0, + 1, + ")" + ], + [ + 140.5, + 494.49979999999999, + 159.59909999999999, + 505.68470000000002, + 10.1, + 1, + 503.5, + 0, + 0, + 0, + 0, + 0, + 1, + "New" + ], + [ + 162.114, + 494.49979999999999, + 210.35159999999999, + 505.68470000000002, + 10.1, + 1, + 503.5, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 212.9271, + 494.49979999999999, + 225.20869999999999, + 505.68470000000002, + 10.1, + 1, + 503.5, + 0, + 0, + 0, + 0, + 0, + 1, + "are" + ], + [ + 227.8347, + 494.49979999999999, + 266.447, + 505.68470000000002, + 10.1, + 1, + 503.5, + 0, + 0, + 0, + 0, + 0, + 1, + "broadcast" + ], + [ + 269.05279999999999, + 494.49979999999999, + 276.90050000000002, + 505.68470000000002, + 10.1, + 1, + 503.5, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 279.45580000000001, + 494.49979999999999, + 289.55579999999998, + 505.68470000000002, + 10.1, + 1, + 503.5, + 0, + 0, + 0, + 0, + 0, + 1, + "all" + ], + [ + 292.06060000000002, + 494.49979999999999, + 315.69459999999998, + 505.68470000000002, + 10.1, + 0, + 503.5, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 315.66430000000003, + 494.49979999999999, + 318.1893, + 505.68470000000002, + 10.1, + 0, + 503.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 506.19979999999998, + 127.55, + 517.38469999999995, + 10.1, + 0, + 515.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "2" + ], + [ + 127.6005, + 506.19979999999998, + 130.96379999999999, + 517.38469999999995, + 10.1, + 1, + 515.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + ")" + ], + [ + 140.5, + 506.19979999999998, + 160.65960000000001, + 517.38469999999995, + 10.1, + 1, + 515.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "Each" + ], + [ + 163.2149, + 506.19979999999998, + 182.8897, + 517.38469999999995, + 10.1, + 1, + 515.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "node" + ], + [ + 185.4248, + 506.19979999999998, + 216.26009999999999, + 517.38469999999995, + 10.1, + 1, + 515.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "collects" + ], + [ + 218.8356, + 506.19979999999998, + 235.6319, + 517.38469999999995, + 10.1, + 1, + 515.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "new" + ], + [ + 238.14680000000001, + 506.19979999999998, + 286.48540000000003, + 517.38469999999995, + 10.1, + 1, + 515.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 288.9599, + 506.19979999999998, + 304.60480000000001, + 517.38469999999995, + 10.1, + 1, + 515.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "into" + ], + [ + 307.25099999999998, + 506.19979999999998, + 311.7253, + 517.38469999999995, + 10.1, + 1, + 515.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 314.25029999999998, + 506.19979999999998, + 336.60160000000002, + 517.38469999999995, + 10.1, + 0, + 515.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 336.65210000000002, + 506.19979999999998, + 339.1771, + 517.38469999999995, + 10.1, + 0, + 515.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 517.7998, + 127.55, + 528.98469999999998, + 10.1, + 0, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "3" + ], + [ + 127.6005, + 517.7998, + 130.96379999999999, + 528.98469999999998, + 10.1, + 1, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + ")" + ], + [ + 140.5, + 517.7998, + 160.65960000000001, + 528.98469999999998, + 10.1, + 1, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "Each" + ], + [ + 163.2149, + 517.7998, + 182.8897, + 528.98469999999998, + 10.1, + 1, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "node" + ], + [ + 185.4248, + 517.7998, + 210.15969999999999, + 528.98469999999998, + 10.1, + 1, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "works" + ], + [ + 212.63419999999999, + 517.7998, + 222.78469999999999, + 528.98469999999998, + 10.1, + 1, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 225.239, + 517.7998, + 254.38759999999999, + 528.98469999999998, + 10.1, + 1, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "finding" + ], + [ + 256.94290000000001, + 517.7998, + 261.41719999999998, + 528.98469999999998, + 10.1, + 1, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 264.04320000000001, + 517.7998, + 296.34300000000002, + 528.98469999999998, + 10.1, + 1, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "difficult" + ], + [ + 298.84780000000001, + 517.7998, + 356.6198, + 528.98469999999998, + 10.1, + 1, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 359.26600000000002, + 517.7998, + 370.93150000000003, + 528.98469999999998, + 10.1, + 1, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 373.56760000000003, + 517.7998, + 383.09190000000001, + 528.98469999999998, + 10.1, + 1, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "its" + ], + [ + 385.56639999999999, + 517.7998, + 408.01870000000002, + 528.98469999999998, + 10.1, + 0, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 408.06920000000002, + 517.7998, + 410.5942, + 528.98469999999998, + 10.1, + 0, + 526.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 529.49980000000005, + 127.55, + 540.68470000000002, + 10.1, + 0, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "4" + ], + [ + 127.6005, + 529.49980000000005, + 130.96379999999999, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + ")" + ], + [ + 140.5, + 529.49980000000005, + 164.5582, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "When" + ], + [ + 167.20439999999999, + 529.49980000000005, + 171.67869999999999, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 174.21379999999999, + 529.49980000000005, + 193.7876, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "node" + ], + [ + 196.3227, + 529.49980000000005, + 216.553, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "finds" + ], + [ + 219.11840000000001, + 529.49980000000005, + 223.59270000000001, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 226.12780000000001, + 529.49980000000005, + 283.89980000000003, + 540.68470000000002, + 10.1, + 0, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 283.95030000000003, + 529.49980000000005, + 286.4753, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 288.9599, + 529.49980000000005, + 294.55529999999999, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 297.15100000000001, + 529.49980000000005, + 339.79320000000001, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "broadcasts" + ], + [ + 342.26769999999999, + 529.49980000000005, + 354.64019999999999, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 357.17529999999999, + 529.49980000000005, + 379.52659999999997, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 382.1728, + 529.49980000000005, + 389.91950000000003, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 392.47480000000002, + 529.49980000000005, + 402.57479999999998, + 540.68470000000002, + 10.1, + 1, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "all" + ], + [ + 405.1705, + 529.49980000000005, + 428.70350000000002, + 540.68470000000002, + 10.1, + 0, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 428.67320000000001, + 529.49980000000005, + 431.19819999999999, + 540.68470000000002, + 10.1, + 0, + 538.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 541.09979999999996, + 127.55, + 552.28470000000004, + 10.1, + 0, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "5" + ], + [ + 127.6005, + 541.09979999999996, + 130.96379999999999, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + ")" + ], + [ + 140.5, + 541.09979999999996, + 166.33580000000001, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "Nodes" + ], + [ + 168.90119999999999, + 541.09979999999996, + 194.61580000000001, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "accept" + ], + [ + 197.2216, + 541.09979999999996, + 209.4931, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 212.0282, + 541.09979999999996, + 234.48050000000001, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 237.03579999999999, + 541.09979999999996, + 254.98349999999999, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "only" + ], + [ + 257.53879999999998, + 541.09979999999996, + 263.69979999999998, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "if" + ], + [ + 266.245, + 541.09979999999996, + 276.34500000000003, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "all" + ], + [ + 278.84980000000002, + 541.09979999999996, + 327.0874, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 329.66289999999998, + 541.09979999999996, + 337.51060000000001, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 340.0659, + 541.09979999999996, + 345.66129999999998, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 348.16609999999997, + 541.09979999999996, + 360.5487, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "are" + ], + [ + 363.0838, + 541.09979999999996, + 383.23329999999999, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "valid" + ], + [ + 385.78859999999997, + 541.09979999999996, + 400.34269999999998, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 402.89800000000002, + 541.09979999999996, + 415.79570000000001, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "not" + ], + [ + 418.39139999999998, + 541.09979999999996, + 448.05509999999998, + 552.28470000000004, + 10.1, + 1, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "already" + ], + [ + 450.61040000000003, + 541.09979999999996, + 471.91129999999998, + 552.28470000000004, + 10.1, + 0, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "spent" + ], + [ + 471.91129999999998, + 541.09979999999996, + 474.43630000000002, + 552.28470000000004, + 10.1, + 0, + 550.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 552.7998, + 127.55, + 563.98469999999998, + 10.1, + 0, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "6" + ], + [ + 127.6005, + 552.7998, + 130.96379999999999, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + ")" + ], + [ + 140.5, + 552.7998, + 166.33580000000001, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "Nodes" + ], + [ + 169.20419999999999, + 552.7998, + 199.4436, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "express" + ], + [ + 202.31200000000001, + 552.7998, + 220.7748, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "their" + ], + [ + 223.6028, + 552.7998, + 267.90140000000002, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "acceptance" + ], + [ + 270.71929999999998, + 552.7998, + 279.18310000000002, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 282.02120000000002, + 552.7998, + 294.29270000000002, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 297.21159999999998, + 552.7998, + 319.56290000000001, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 322.41109999999998, + 552.7998, + 332.5616, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 335.30880000000002, + 552.7998, + 368.96199999999999, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "working" + ], + [ + 371.81020000000001, + 552.7998, + 381.96069999999997, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 384.80889999999999, + 552.7998, + 417.27030000000002, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "creating" + ], + [ + 420.11849999999998, + 552.7998, + 432.49099999999999, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 435.30889999999999, + 552.7998, + 452.71120000000002, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "next" + ], + [ + 455.50889999999998, + 552.7998, + 477.86020000000002, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 480.80939999999998, + 552.7998, + 488.55610000000001, + 563.98469999999998, + 10.1, + 1, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 491.50529999999998, + 552.7998, + 503.77679999999998, + 563.98469999999998, + 10.1, + 0, + 561.79999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ] + ] + ], + [ + [ + [ + 140.5, + 564.39980000000003, + 162.35640000000001, + 575.5847, + 10.1, + 0, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 162.40690000000001, + 564.39980000000003, + 164.93190000000001, + 575.5847, + 10.1, + 1, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 167.50739999999999, + 564.39980000000003, + 189.2527, + 575.5847, + 10.1, + 1, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "using" + ], + [ + 191.8989, + 564.39980000000003, + 204.1704, + 575.5847, + 10.1, + 1, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 206.7055, + 564.39980000000003, + 225.25919999999999, + 575.5847, + 10.1, + 1, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "hash" + ], + [ + 227.81450000000001, + 564.39980000000003, + 236.2783, + 575.5847, + 10.1, + 1, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 238.7225, + 564.39980000000003, + 251.095, + 575.5847, + 10.1, + 1, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 253.6301, + 564.39980000000003, + 288.89929999999998, + 575.5847, + 10.1, + 1, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "accepted" + ], + [ + 291.45460000000003, + 564.39980000000003, + 313.90690000000001, + 575.5847, + 10.1, + 1, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 316.4622, + 564.39980000000003, + 324.89569999999998, + 575.5847, + 10.1, + 1, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 327.37020000000001, + 564.39980000000003, + 339.74270000000001, + 575.5847, + 10.1, + 1, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 342.27780000000001, + 564.39980000000003, + 377.11270000000002, + 575.5847, + 10.1, + 1, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "previous" + ], + [ + 379.5872, + 564.39980000000003, + 398.03989999999999, + 575.5847, + 10.1, + 0, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "hash" + ], + [ + 398.09039999999999, + 564.39980000000003, + 400.61540000000002, + 575.5847, + 10.1, + 0, + 573.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 587.69979999999998, + 148.33580000000001, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "Nodes" + ], + [ + 152.60810000000001, + 587.69979999999998, + 180.64570000000001, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "always" + ], + [ + 184.91800000000001, + 587.69979999999998, + 219.18729999999999, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "consider" + ], + [ + 223.42930000000001, + 587.69979999999998, + 235.80179999999999, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 240.13470000000001, + 587.69979999999998, + 269.2328, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "longest" + ], + [ + 273.53539999999998, + 587.69979999999998, + 295.39179999999999, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 299.74489999999997, + 587.69979999999998, + 307.49160000000001, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 311.84469999999999, + 587.69979999999998, + 321.31849999999997, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 325.65140000000002, + 587.69979999999998, + 337.92290000000003, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 342.25580000000002, + 587.69979999999998, + 370.37419999999997, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "correct" + ], + [ + 374.57580000000002, + 587.69979999999998, + 389.15010000000001, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "one" + ], + [ + 393.483, + 587.69979999999998, + 408.03710000000001, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 412.39019999999999, + 587.69979999999998, + 428.0856, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "will" + ], + [ + 432.28719999999998, + 587.69979999999998, + 451.34589999999997, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "keep" + ], + [ + 455.69900000000001, + 587.69979999999998, + 489.35219999999998, + 598.88469999999995, + 10.1, + 1, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "working" + ], + [ + 493.60430000000002, + 587.69979999999998, + 503.75479999999999, + 598.88469999999995, + 10.1, + 0, + 596.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 599.39980000000003, + 147.9546, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "extending" + ], + [ + 151.49969999999999, + 599.39980000000003, + 157.0951, + 610.5847, + 10.1, + 0, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 157.0951, + 599.39980000000003, + 159.62010000000001, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 166.7911, + 599.39980000000003, + 173.5581, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "If" + ], + [ + 177.09309999999999, + 599.39980000000003, + 192.2431, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "two" + ], + [ + 195.88919999999999, + 599.39980000000003, + 219.4222, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 222.99760000000001, + 599.39980000000003, + 261.71089999999998, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "broadcast" + ], + [ + 265.31659999999999, + 599.39980000000003, + 299.92930000000001, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "different" + ], + [ + 303.53500000000003, + 599.39980000000003, + 337.16800000000001, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "versions" + ], + [ + 340.74340000000001, + 599.39980000000003, + 349.2072, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 352.74220000000003, + 599.39980000000003, + 365.01369999999997, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 368.63959999999997, + 599.39980000000003, + 386.0419, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "next" + ], + [ + 389.64760000000001, + 599.39980000000003, + 411.99889999999999, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 415.64499999999998, + 599.39980000000003, + 476.79039999999998, + 610.5847, + 10.1, + 0, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "simultaneously" + ], + [ + 476.14400000000001, + 599.39980000000003, + 478.66899999999998, + 610.5847, + 10.1, + 1, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 482.24439999999998, + 599.39980000000003, + 503.51499999999999, + 610.5847, + 10.1, + 0, + 608.39999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "some" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 610.99980000000005, + 131.73400000000001, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 134.90539999999999, + 610.99980000000005, + 152.34809999999999, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "may" + ], + [ + 155.49930000000001, + 610.99980000000005, + 184.6883, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "receive" + ], + [ + 187.9102, + 610.99980000000005, + 202.4845, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "one" + ], + [ + 205.7064, + 610.99980000000005, + 214.17019999999999, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "or" + ], + [ + 217.40219999999999, + 610.99980000000005, + 229.6737, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 232.8956, + 610.99980000000005, + 253.66120000000001, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "other" + ], + [ + 256.89319999999998, + 610.99980000000005, + 273.19459999999998, + 622.18470000000002, + 10.1, + 0, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "first" + ], + [ + 273.19459999999998, + 610.99980000000005, + 275.71960000000001, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 282.19369999999998, + 610.99980000000005, + 290.54640000000001, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "In" + ], + [ + 293.79860000000002, + 610.99980000000005, + 308.9991, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 312.20080000000002, + 610.99980000000005, + 329.5829, + 622.18470000000002, + 10.1, + 0, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "case" + ], + [ + 329.61320000000001, + 610.99980000000005, + 332.13819999999998, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 335.30959999999999, + 610.99980000000005, + 352.76240000000001, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "they" + ], + [ + 355.91359999999997, + 610.99980000000005, + 376.66910000000001, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "work" + ], + [ + 379.92129999999997, + 610.99980000000005, + 390.0718, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 393.32400000000001, + 610.99980000000005, + 405.59550000000002, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 408.81740000000002, + 610.99980000000005, + 425.11880000000002, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "first" + ], + [ + 428.32049999999998, + 610.99980000000005, + 442.89479999999998, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "one" + ], + [ + 446.11669999999998, + 610.99980000000005, + 463.56950000000001, + 622.18470000000002, + 10.1, + 1, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "they" + ], + [ + 466.82170000000002, + 610.99980000000005, + 500.99000000000001, + 622.18470000000002, + 10.1, + 0, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "received" + ], + [ + 501.04050000000001, + 610.99980000000005, + 503.56549999999999, + 622.18470000000002, + 10.1, + 0, + 620, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 622.69979999999998, + 120.99769999999999, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "but" + ], + [ + 123.8964, + 622.69979999999998, + 141.87440000000001, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "save" + ], + [ + 144.69229999999999, + 622.69979999999998, + 157.06479999999999, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 159.9837, + 622.69979999999998, + 180.64830000000001, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "other" + ], + [ + 183.57730000000001, + 622.69979999999998, + 211.03919999999999, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "branch" + ], + [ + 213.88740000000001, + 622.69979999999998, + 221.73509999999999, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 224.68430000000001, + 622.69979999999998, + 241.96539999999999, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "case" + ], + [ + 244.8843, + 622.69979999999998, + 250.47970000000001, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 253.3784, + 622.69979999999998, + 288.7183, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "becomes" + ], + [ + 291.58670000000001, + 622.69979999999998, + 317.45280000000002, + 633.88469999999995, + 10.1, + 0, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "longer" + ], + [ + 316.88720000000001, + 622.69979999999998, + 319.41219999999998, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 324.97730000000001, + 622.69979999999998, + 340.65249999999997, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "The" + ], + [ + 343.57139999999998, + 622.69979999999998, + 353.64109999999999, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "tie" + ], + [ + 356.459, + 622.69979999999998, + 372.15440000000001, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "will" + ], + [ + 375.05309999999997, + 622.69979999999998, + 384.62790000000001, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 387.44580000000002, + 622.69979999999998, + 415.50360000000001, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "broken" + ], + [ + 418.45280000000002, + 622.69979999999998, + 440.20819999999998, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "when" + ], + [ + 443.1574, + 622.69979999999998, + 455.5299, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 458.34780000000001, + 622.69979999999998, + 475.75009999999997, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "next" + ], + [ + 478.64879999999999, + 622.69979999999998, + 503.81799999999998, + 633.88469999999995, + 10.1, + 0, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 634.2998, + 140.66239999999999, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "of-work" + ], + [ + 145.0155, + 634.2998, + 151.74209999999999, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 156.11539999999999, + 634.2998, + 179.5676, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "found" + ], + [ + 184.02170000000001, + 634.2998, + 198.57579999999999, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 202.9289, + 634.2998, + 217.50319999999999, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "one" + ], + [ + 221.83609999999999, + 634.2998, + 249.298, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "branch" + ], + [ + 253.65110000000001, + 634.2998, + 288.99099999999999, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "becomes" + ], + [ + 293.26330000000002, + 634.2998, + 319.12939999999998, + 645.48469999999998, + 10.1, + 0, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "longer" + ], + [ + 319.16980000000001, + 634.2998, + 321.96749999999997, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + ";" + ], + [ + 326.27010000000001, + 634.2998, + 338.54160000000002, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 342.97550000000001, + 634.2998, + 366.50850000000003, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 370.7808, + 634.2998, + 385.98129999999998, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 390.28390000000002, + 634.2998, + 409.86779999999999, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "were" + ], + [ + 414.30169999999998, + 634.2998, + 447.85390000000001, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "working" + ], + [ + 452.30799999999999, + 634.2998, + 462.35750000000002, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 466.7106, + 634.2998, + 478.9821, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 483.416, + 634.2998, + 504.0806, + 645.48469999999998, + 10.1, + 0, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "other" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 645.99980000000005, + 135.56190000000001, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 1, + "branch" + ], + [ + 138.1172, + 645.99980000000005, + 153.8126, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 1, + "will" + ], + [ + 156.41839999999999, + 645.99980000000005, + 173.77019999999999, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 1, + "then" + ], + [ + 176.32550000000001, + 645.99980000000005, + 202.6764, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 1, + "switch" + ], + [ + 205.23169999999999, + 645.99980000000005, + 213.07939999999999, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 215.63470000000001, + 645.99980000000005, + 227.90620000000001, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 230.44130000000001, + 645.99980000000005, + 256.30739999999997, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 1, + "longer" + ], + [ + 258.8526, + 645.99980000000005, + 273.42689999999999, + 657.18470000000002, + 10.1, + 0, + 655, + 0, + 0, + 0, + 0, + 0, + 1, + "one" + ], + [ + 273.4572, + 645.99980000000005, + 275.98219999999998, + 657.18470000000002, + 10.1, + 0, + 655, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 310.80000000000001, + 708.99980000000005, + 315.85000000000002, + 720.18470000000002, + 10.1, + 0, + 718, + 0, + 0, + 0, + 0, + 0, + 1, + "3" + ] + ] + ], + [ + [ + [ + 216.80000000000001, + 235.482, + 234.50139999999999, + 243.5258, + 7.2000000000000002, + 0, + 242, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ] + ] + ], + [ + [ + [ + 228.80000000000001, + 251.08199999999999, + 243.5967, + 259.12580000000003, + 7.2000000000000002, + 1, + 257.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "Prev" + ], + [ + 245.6027, + 251.08199999999999, + 262.39819999999997, + 259.12580000000003, + 7.2000000000000002, + 1, + 257.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ], + [ + 278.60000000000002, + 251.08199999999999, + 299.49369999999999, + 259.12580000000003, + 7.2000000000000002, + 0, + 257.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "Nonce" + ] + ] + ], + [ + [ + [ + 230, + 268.48200000000003, + 237.89449999999999, + 276.5258, + 7.2000000000000002, + 1, + 275, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx" + ], + [ + 258.80000000000001, + 268.48200000000003, + 266.69450000000001, + 276.5258, + 7.2000000000000002, + 1, + 275, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx" + ], + [ + 288.80000000000001, + 268.48200000000003, + 290.79160000000002, + 276.5258, + 7.2000000000000002, + 0, + 275, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 290.79880000000003, + 268.48200000000003, + 292.79039999999998, + 276.5258, + 7.2000000000000002, + 0, + 275, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 292.79759999999999, + 268.48200000000003, + 294.78910000000002, + 276.5258, + 7.2000000000000002, + 0, + 275, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ] + ] + ], + [ + [ + [ + 332, + 235.482, + 349.70139999999998, + 243.5258, + 7.2000000000000002, + 0, + 242, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ] + ] + ], + [ + [ + [ + 343.89999999999998, + 251.08199999999999, + 358.69670000000002, + 259.12580000000003, + 7.2000000000000002, + 1, + 257.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "Prev" + ], + [ + 360.70269999999999, + 251.08199999999999, + 377.4982, + 259.12580000000003, + 7.2000000000000002, + 1, + 257.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ], + [ + 393.69999999999999, + 251.08199999999999, + 414.59370000000001, + 259.12580000000003, + 7.2000000000000002, + 0, + 257.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "Nonce" + ] + ] + ], + [ + [ + [ + 345.10000000000002, + 268.48200000000003, + 352.99450000000002, + 276.5258, + 7.2000000000000002, + 1, + 275, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx" + ], + [ + 373.89999999999998, + 268.48200000000003, + 381.79450000000003, + 276.5258, + 7.2000000000000002, + 1, + 275, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx" + ], + [ + 403.89999999999998, + 268.48200000000003, + 405.89159999999998, + 276.5258, + 7.2000000000000002, + 0, + 275, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 405.99939999999998, + 268.48200000000003, + 407.99099999999999, + 276.5258, + 7.2000000000000002, + 0, + 275, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 407.9982, + 268.48200000000003, + 409.9898, + 276.5258, + 7.2000000000000002, + 0, + 275, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ] + ] + ] + ] + ] + ] + ] + ] + ], + [ + 612, + 792, + [ + [ + [ + [ + 0, + 0, + 0, + 0, + [ + [ + [ + [ + 122.5, + 82.599800000000002, + 141.59909999999999, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "New" + ], + [ + 144.50790000000001, + 82.599800000000002, + 188.76609999999999, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "transaction" + ], + [ + 191.71530000000001, + 82.599800000000002, + 234.35749999999999, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "broadcasts" + ], + [ + 237.2259, + 82.599800000000002, + 247.27539999999999, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "do" + ], + [ + 250.22460000000001, + 82.599800000000002, + 263.1223, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "not" + ], + [ + 265.92000000000002, + 82.599800000000002, + 310.8852, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "necessarily" + ], + [ + 313.73340000000002, + 82.599800000000002, + 332.7921, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "need" + ], + [ + 335.64030000000002, + 82.599800000000002, + 343.488, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 346.43720000000002, + 82.599800000000002, + 368.20269999999999, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "reach" + ], + [ + 371.15190000000001, + 82.599800000000002, + 381.25189999999998, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "all" + ], + [ + 384.1506, + 82.599800000000002, + 407.68360000000001, + 93.784700000000001, + 10.1, + 0, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "nodes" + ], + [ + 407.6533, + 82.599800000000002, + 410.17829999999998, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 415.45049999999998, + 82.599800000000002, + 426.68169999999998, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "As" + ], + [ + 429.55009999999999, + 82.599800000000002, + 447.39679999999998, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "long" + ], + [ + 450.346, + 82.599800000000002, + 458.77949999999998, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "as" + ], + [ + 461.64789999999999, + 82.599800000000002, + 478.99970000000002, + 93.784700000000001, + 10.1, + 1, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "they" + ], + [ + 481.94889999999998, + 82.599800000000002, + 503.71440000000001, + 93.784700000000001, + 10.1, + 0, + 91.599999999999994, + 0, + 0, + 0, + 0, + 0, + 0, + "reach" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 94.199799999999996, + 130.5523, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "many" + ], + [ + 133.1985, + 94.199799999999996, + 156.73150000000001, + 105.3847, + 10.1, + 0, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "nodes" + ], + [ + 156.7012, + 94.199799999999996, + 159.22620000000001, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 161.89259999999999, + 94.199799999999996, + 179.34540000000001, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "they" + ], + [ + 181.9007, + 94.199799999999996, + 197.59610000000001, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "will" + ], + [ + 200.2928, + 94.199799999999996, + 212.59460000000001, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "get" + ], + [ + 215.2004, + 94.199799999999996, + 230.94630000000001, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "into" + ], + [ + 233.5925, + 94.199799999999996, + 238.0668, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 240.69280000000001, + 94.199799999999996, + 263.04410000000001, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "block" + ], + [ + 265.79129999999998, + 94.199799999999996, + 291.57659999999998, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "before" + ], + [ + 294.20260000000002, + 94.199799999999996, + 312.04930000000002, + 105.3847, + 10.1, + 0, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "long" + ], + [ + 312.09980000000002, + 94.199799999999996, + 314.62479999999999, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 319.88690000000003, + 94.199799999999996, + 343.935, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "Block" + ], + [ + 346.68220000000002, + 94.199799999999996, + 389.32440000000003, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "broadcasts" + ], + [ + 391.8999, + 94.199799999999996, + 404.28250000000003, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "are" + ], + [ + 406.9085, + 94.199799999999996, + 423.15940000000001, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "also" + ], + [ + 425.80560000000003, + 94.199799999999996, + 456.61059999999998, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "tolerant" + ], + [ + 459.3073, + 94.199799999999996, + 467.77109999999999, + 105.3847, + 10.1, + 1, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ], + [ + 470.30619999999999, + 94.199799999999996, + 503.36349999999999, + 105.3847, + 10.1, + 0, + 103.2, + 0, + 0, + 0, + 0, + 0, + 0, + "dropped" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 105.8998, + 146.32849999999999, + 117.0847, + 10.1, + 0, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "messages" + ], + [ + 146.29820000000001, + 105.8998, + 148.82320000000001, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 153.90350000000001, + 105.8998, + 160.6705, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "If" + ], + [ + 163.1147, + 105.8998, + 167.589, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 170.1241, + 105.8998, + 189.7989, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "node" + ], + [ + 192.334, + 105.8998, + 210.86750000000001, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "does" + ], + [ + 213.34200000000001, + 105.8998, + 226.2397, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "not" + ], + [ + 228.83539999999999, + 105.8998, + 257.92340000000002, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "receive" + ], + [ + 260.45850000000002, + 105.8998, + 264.93279999999999, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 267.55880000000002, + 105.8998, + 289.9101, + 117.0847, + 10.1, + 0, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "block" + ], + [ + 289.9606, + 105.8998, + 292.48559999999998, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 294.97019999999998, + 105.8998, + 300.56560000000002, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "it" + ], + [ + 303.17140000000001, + 105.8998, + 318.86680000000001, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "will" + ], + [ + 321.3716, + 105.8998, + 350.58080000000001, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "request" + ], + [ + 353.0856, + 105.8998, + 358.68099999999998, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "it" + ], + [ + 361.27670000000001, + 105.8998, + 383.03210000000001, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "when" + ], + [ + 385.67829999999998, + 105.8998, + 391.27370000000002, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "it" + ], + [ + 393.77850000000001, + 105.8998, + 426.92669999999998, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "receives" + ], + [ + 429.40120000000002, + 105.8998, + 441.77370000000002, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 444.30880000000002, + 105.8998, + 461.71109999999999, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "next" + ], + [ + 464.21589999999998, + 105.8998, + 486.66820000000001, + 117.0847, + 10.1, + 1, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "block" + ], + [ + 489.2235, + 105.8998, + 503.77760000000001, + 117.0847, + 10.1, + 0, + 114.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "and" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 117.49979999999999, + 138.94540000000001, + 128.68469999999999, + 10.1, + 1, + 126.5, + 0, + 0, + 0, + 0, + 0, + 0, + "realizes" + ], + [ + 141.52090000000001, + 117.49979999999999, + 147.1163, + 128.68469999999999, + 10.1, + 1, + 126.5, + 0, + 0, + 0, + 0, + 0, + 0, + "it" + ], + [ + 149.62110000000001, + 117.49979999999999, + 177.6688, + 128.68469999999999, + 10.1, + 1, + 126.5, + 0, + 0, + 0, + 0, + 0, + 0, + "missed" + ], + [ + 180.22409999999999, + 117.49979999999999, + 194.79839999999999, + 128.68469999999999, + 10.1, + 0, + 126.5, + 0, + 0, + 0, + 0, + 0, + 0, + "one" + ], + [ + 194.8287, + 117.49979999999999, + 197.3537, + 128.68469999999999, + 10.1, + 0, + 126.5, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 143.86279999999999, + 114.70099999999999, + 157.6875, + 11.5, + 0, + 155.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "6" + ], + [ + 114.70099999999999, + 143.86279999999999, + 117.8865, + 157.6875, + 11.5, + 1, + 155.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 133.30000000000001, + 143.86279999999999, + 189.90299999999999, + 157.6875, + 11.5, + 0, + 155.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "Incentive" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 165.2998, + 119.9473, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "By" + ], + [ + 122.4016, + 165.2998, + 167.25569999999999, + 176.4847, + 10.1, + 0, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "convention" + ], + [ + 167.30619999999999, + 165.2998, + 169.8312, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 172.4067, + 165.2998, + 184.6782, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 187.2133, + 165.2998, + 203.5147, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "first" + ], + [ + 206.01949999999999, + 165.2998, + 250.37870000000001, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "transaction" + ], + [ + 252.934, + 165.2998, + 260.7817, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "in" + ], + [ + 263.33699999999999, + 165.2998, + 267.81130000000002, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 270.34640000000002, + 165.2998, + 292.6977, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "block" + ], + [ + 295.34390000000002, + 165.2998, + 302.07049999999998, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "is" + ], + [ + 304.54500000000002, + 165.2998, + 309.01929999999999, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 311.55439999999999, + 165.2998, + 339.66269999999997, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "special" + ], + [ + 342.16750000000002, + 165.2998, + 386.42570000000001, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "transaction" + ], + [ + 389.07190000000003, + 165.2998, + 404.17140000000001, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "that" + ], + [ + 406.67619999999999, + 165.2998, + 428.00740000000002, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "starts" + ], + [ + 430.5829, + 165.2998, + 435.05720000000002, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 437.59230000000002, + 165.2998, + 454.3886, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "new" + ], + [ + 456.90350000000001, + 165.2998, + 474.25529999999998, + 176.4847, + 10.1, + 1, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "coin" + ], + [ + 476.9015, + 165.2998, + 503.75740000000002, + 176.4847, + 10.1, + 0, + 174.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "owned" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 176.99979999999999, + 118.2505, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "by" + ], + [ + 120.7957, + 176.99979999999999, + 133.06720000000001, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 135.60230000000001, + 176.99979999999999, + 163.68029999999999, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "creator" + ], + [ + 166.22550000000001, + 176.99979999999999, + 174.6893, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ], + [ + 177.1335, + 176.99979999999999, + 189.506, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 192.0411, + 176.99979999999999, + 214.39240000000001, + 188.18469999999999, + 10.1, + 0, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "block" + ], + [ + 214.44290000000001, + 176.99979999999999, + 216.96789999999999, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 221.84620000000001, + 176.99979999999999, + 239.87469999999999, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "This" + ], + [ + 242.3492, + 176.99979999999999, + 260.8827, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "adds" + ], + [ + 263.44810000000001, + 176.99979999999999, + 272.90170000000001, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "an" + ], + [ + 275.45699999999999, + 176.99979999999999, + 312.43310000000002, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "incentive" + ], + [ + 315.0591, + 176.99979999999999, + 326.72460000000001, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "for" + ], + [ + 329.36070000000001, + 176.99979999999999, + 352.89370000000002, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "nodes" + ], + [ + 355.45909999999998, + 176.99979999999999, + 363.20580000000001, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 365.7611, + 176.99979999999999, + 396.06110000000001, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "support" + ], + [ + 398.6669, + 176.99979999999999, + 410.9384, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 413.4735, + 176.99979999999999, + 446.5308, + 188.18469999999999, + 10.1, + 0, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "network" + ], + [ + 446.5813, + 176.99979999999999, + 449.10629999999998, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 451.68180000000001, + 176.99979999999999, + 466.23590000000002, + 188.18469999999999, + 10.1, + 1, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "and" + ], + [ + 468.7912, + 176.99979999999999, + 503.62610000000001, + 188.18469999999999, + 10.1, + 0, + 186, + 0, + 0, + 0, + 0, + 0, + 0, + "provides" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 188.59979999999999, + 112.57429999999999, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 115.2003, + 188.59979999999999, + 132.05719999999999, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "way" + ], + [ + 134.60239999999999, + 188.59979999999999, + 142.34909999999999, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 144.99529999999999, + 188.59979999999999, + 176.4366, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "initially" + ], + [ + 178.99189999999999, + 188.59979999999999, + 217.15979999999999, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "distribute" + ], + [ + 219.78579999999999, + 188.59979999999999, + 241.11699999999999, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "coins" + ], + [ + 243.6824, + 188.59979999999999, + 259.32729999999998, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "into" + ], + [ + 261.9735, + 188.59979999999999, + 305.13080000000002, + 199.78469999999999, + 10.1, + 0, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "circulation" + ], + [ + 305.18130000000002, + 188.59979999999999, + 307.7063, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 310.28179999999998, + 188.59979999999999, + 330.95650000000001, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "since" + ], + [ + 333.58249999999998, + 188.59979999999999, + 353.76229999999998, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "there" + ], + [ + 356.38830000000002, + 188.59979999999999, + 363.11489999999998, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "is" + ], + [ + 365.68029999999999, + 188.59979999999999, + 375.72980000000001, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "no" + ], + [ + 378.37599999999998, + 188.59979999999999, + 405.78739999999999, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "central" + ], + [ + 408.38310000000001, + 188.59979999999999, + 444.834, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "authority" + ], + [ + 447.48020000000002, + 188.59979999999999, + 455.2269, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 457.87310000000002, + 188.59979999999999, + 478.0428, + 199.78469999999999, + 10.1, + 1, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "issue" + ], + [ + 480.66879999999998, + 188.59979999999999, + 500.81830000000002, + 199.78469999999999, + 10.1, + 0, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "them" + ], + [ + 500.86880000000002, + 188.59979999999999, + 503.3938, + 199.78469999999999, + 10.1, + 0, + 197.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 200.2998, + 123.7752, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "The" + ], + [ + 126.69410000000001, + 200.2998, + 152.55009999999999, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "steady" + ], + [ + 155.39830000000001, + 200.2998, + 188.44550000000001, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "addition" + ], + [ + 191.3947, + 200.2998, + 199.85849999999999, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ], + [ + 202.69659999999999, + 200.2998, + 207.17089999999999, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 209.9888, + 200.2998, + 243.6925, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "constant" + ], + [ + 246.59119999999999, + 200.2998, + 255.05500000000001, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ], + [ + 257.8931, + 200.2998, + 288.09210000000002, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "amount" + ], + [ + 290.99079999999998, + 200.2998, + 299.45460000000003, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ], + [ + 302.29270000000002, + 200.2998, + 319.089, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "new" + ], + [ + 321.99779999999998, + 200.2998, + 343.32900000000001, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "coins" + ], + [ + 346.19740000000002, + 200.2998, + 352.92399999999998, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "is" + ], + [ + 355.79239999999999, + 200.2998, + 396.82870000000003, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "analogous" + ], + [ + 399.69709999999998, + 200.2998, + 407.44380000000001, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 410.39299999999997, + 200.2998, + 428.34070000000003, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "gold" + ], + [ + 431.18889999999999, + 200.2998, + 458.72149999999999, + 211.4847, + 10.1, + 1, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "miners" + ], + [ + 461.5899, + 200.2998, + 503.6463, + 211.4847, + 10.1, + 0, + 209.30000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "expending" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 211.8998, + 146.24770000000001, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "resources" + ], + [ + 148.82320000000001, + 211.8998, + 156.67089999999999, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 159.22620000000001, + 211.8998, + 173.78030000000001, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "add" + ], + [ + 176.3356, + 211.8998, + 194.2833, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "gold" + ], + [ + 196.83860000000001, + 211.8998, + 204.58529999999999, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 207.23150000000001, + 211.8998, + 250.3888, + 223.0847, + 10.1, + 0, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "circulation" + ], + [ + 250.4393, + 211.8998, + 252.96430000000001, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 257.95370000000003, + 211.8998, + 266.4074, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "In" + ], + [ + 268.96269999999998, + 211.8998, + 282.42599999999999, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "our" + ], + [ + 284.96109999999999, + 211.8998, + 302.24220000000003, + 223.0847, + 10.1, + 0, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "case" + ], + [ + 302.27249999999998, + 211.8998, + 304.79750000000001, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 307.37299999999999, + 211.8998, + 312.96839999999997, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "it" + ], + [ + 315.47320000000002, + 211.8998, + 322.19979999999998, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "is" + ], + [ + 324.77530000000002, + 211.8998, + 344.35919999999999, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "CPU" + ], + [ + 346.9751, + 211.8998, + 364.84199999999998, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "time" + ], + [ + 367.46800000000002, + 211.8998, + 382.02210000000002, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "and" + ], + [ + 384.56729999999999, + 211.8998, + 424.92689999999999, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "electricity" + ], + [ + 427.48219999999998, + 211.8998, + 442.58170000000001, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "that" + ], + [ + 445.17739999999998, + 211.8998, + 451.904, + 223.0847, + 10.1, + 1, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "is" + ], + [ + 454.37849999999997, + 211.8998, + 493.04129999999998, + 223.0847, + 10.1, + 0, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "expended" + ], + [ + 493.09179999999998, + 211.8998, + 495.61680000000001, + 223.0847, + 10.1, + 0, + 220.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 223.59979999999999, + 138.17519999999999, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "The" + ], + [ + 141.0941, + 223.59979999999999, + 178.0702, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "incentive" + ], + [ + 180.98910000000001, + 223.59979999999999, + 194.94730000000001, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "can" + ], + [ + 197.8965, + 223.59979999999999, + 214.1474, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "also" + ], + [ + 216.9956, + 223.59979999999999, + 226.57040000000001, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "be" + ], + [ + 229.48929999999999, + 223.59979999999999, + 257.4461, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "funded" + ], + [ + 260.39530000000002, + 223.59979999999999, + 278.24200000000002, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "with" + ], + [ + 281.19119999999998, + 223.59979999999999, + 325.44940000000003, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "transaction" + ], + [ + 328.39859999999999, + 223.59979999999999, + 344.63940000000002, + 234.78469999999999, + 10.1, + 0, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "fees" + ], + [ + 344.71010000000001, + 223.59979999999999, + 347.23509999999999, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 353.00220000000002, + 223.59979999999999, + 359.76920000000001, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "If" + ], + [ + 362.60730000000001, + 223.59979999999999, + 374.87880000000001, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 377.79770000000002, + 223.59979999999999, + 403.59309999999999, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "output" + ], + [ + 406.49180000000001, + 223.59979999999999, + 428.26740000000001, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "value" + ], + [ + 431.18630000000002, + 223.59979999999999, + 439.65010000000001, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ], + [ + 442.48820000000001, + 223.59979999999999, + 446.96249999999998, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 449.88139999999999, + 223.59979999999999, + 494.13959999999997, + 234.78469999999999, + 10.1, + 1, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "transaction" + ], + [ + 497.08879999999999, + 223.59979999999999, + 503.81540000000001, + 234.78469999999999, + 10.1, + 0, + 232.59999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "is" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 235.19980000000001, + 123.2298, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "less" + ], + [ + 126.3002, + 235.19980000000001, + 143.65199999999999, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "than" + ], + [ + 146.60120000000001, + 235.19980000000001, + 156.12549999999999, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "its" + ], + [ + 159.19589999999999, + 235.19980000000001, + 179.89080000000001, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "input" + ], + [ + 182.8905, + 235.19980000000001, + 204.7671, + 246.38470000000001, + 10.1, + 0, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "value" + ], + [ + 204.79740000000001, + 235.19980000000001, + 207.32239999999999, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 210.29179999999999, + 235.19980000000001, + 222.5633, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 225.58320000000001, + 235.19980000000001, + 266.27609999999999, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "difference" + ], + [ + 269.29599999999999, + 235.19980000000001, + 276.02260000000001, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "is" + ], + [ + 278.99200000000002, + 235.19980000000001, + 283.46629999999999, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 286.4862, + 235.19980000000001, + 330.74439999999998, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "transaction" + ], + [ + 333.7946, + 235.19980000000001, + 346.07619999999997, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "fee" + ], + [ + 349.09609999999998, + 235.19980000000001, + 364.19560000000001, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "that" + ], + [ + 367.19529999999997, + 235.19980000000001, + 373.92189999999999, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "is" + ], + [ + 376.8913, + 235.19980000000001, + 401.0505, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "added" + ], + [ + 403.99970000000002, + 235.19980000000001, + 411.84739999999999, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 414.89760000000001, + 235.19980000000001, + 427.16910000000001, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 430.18900000000002, + 235.19980000000001, + 467.1651, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "incentive" + ], + [ + 470.185, + 235.19980000000001, + 492.0616, + 246.38470000000001, + 10.1, + 1, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "value" + ], + [ + 494.98050000000001, + 235.19980000000001, + 503.4443, + 246.38470000000001, + 10.1, + 0, + 244.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 246.8998, + 120.4725, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 125.4922, + 246.8998, + 147.84350000000001, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "block" + ], + [ + 152.99449999999999, + 246.8998, + 195.54580000000001, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "containing" + ], + [ + 200.5958, + 246.8998, + 212.9683, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 217.988, + 246.8998, + 262.24619999999999, + 258.0847, + 10.1, + 0, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "transaction" + ], + [ + 262.29669999999999, + 246.8998, + 264.82170000000002, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 274.89139999999998, + 246.8998, + 296.1721, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "Once" + ], + [ + 301.1918, + 246.8998, + 305.66609999999997, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 310.68579999999997, + 246.8998, + 369.05369999999999, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "predetermined" + ], + [ + 374.1037, + 246.8998, + 404.96929999999998, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "number" + ], + [ + 409.9991, + 246.8998, + 418.46289999999999, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ], + [ + 423.39170000000001, + 246.8998, + 444.72289999999998, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "coins" + ], + [ + 449.79309999999998, + 246.8998, + 468.77100000000002, + 258.0847, + 10.1, + 1, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "have" + ], + [ + 473.89170000000001, + 246.8998, + 503.55540000000002, + 258.0847, + 10.1, + 0, + 255.90000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "entered" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 258.49979999999999, + 151.25729999999999, + 269.68470000000002, + 10.1, + 0, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "circulation" + ], + [ + 151.30779999999999, + 258.49979999999999, + 153.83279999999999, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 157.71119999999999, + 258.49979999999999, + 169.98269999999999, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 173.81059999999999, + 258.49979999999999, + 210.8877, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "incentive" + ], + [ + 214.71559999999999, + 258.49979999999999, + 228.6738, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "can" + ], + [ + 232.52189999999999, + 258.49979999999999, + 270.6696, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "transition" + ], + [ + 274.51769999999999, + 258.49979999999999, + 305.3732, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "entirely" + ], + [ + 309.22129999999999, + 258.49979999999999, + 317.06900000000002, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 320.9171, + 258.49979999999999, + 365.17529999999999, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "transaction" + ], + [ + 369.02339999999998, + 258.49979999999999, + 385.35509999999999, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "fees" + ], + [ + 389.12240000000003, + 258.49979999999999, + 403.67649999999998, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "and" + ], + [ + 407.52460000000002, + 258.49979999999999, + 417.0994, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "be" + ], + [ + 420.9273, + 258.49979999999999, + 465.78140000000002, + 269.68470000000002, + 10.1, + 1, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "completely" + ], + [ + 469.62950000000001, + 258.49979999999999, + 503.77760000000001, + 269.68470000000002, + 10.1, + 0, + 267.5, + 0, + 0, + 0, + 0, + 0, + 0, + "inflation" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 270.19979999999998, + 123.78530000000001, + 281.38470000000001, + 10.1, + 0, + 279.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "free" + ], + [ + 123.8156, + 270.19979999999998, + 126.34059999999999, + 281.38470000000001, + 10.1, + 0, + 279.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 281.7998, + 138.17519999999999, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "The" + ], + [ + 142.80099999999999, + 281.7998, + 179.77709999999999, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "incentive" + ], + [ + 184.40289999999999, + 281.7998, + 201.75470000000001, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "may" + ], + [ + 206.4007, + 281.7998, + 223.7525, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "help" + ], + [ + 228.30760000000001, + 281.7998, + 269.79840000000002, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "encourage" + ], + [ + 274.42419999999998, + 281.7998, + 298.0582, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "nodes" + ], + [ + 302.6234, + 281.7998, + 310.37009999999998, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 315.01609999999999, + 281.7998, + 331.267, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "stay" + ], + [ + 335.82209999999998, + 281.7998, + 362.2235, + 292.98469999999998, + 10.1, + 0, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "honest" + ], + [ + 362.2235, + 281.7998, + 364.74849999999998, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 373.91930000000002, + 281.7998, + 380.68630000000002, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "If" + ], + [ + 385.22120000000001, + 281.7998, + 389.69549999999998, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 394.32130000000001, + 281.7998, + 421.78320000000002, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "greedy" + ], + [ + 426.32819999999998, + 281.7998, + 458.3048, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "attacker" + ], + [ + 462.94069999999999, + 281.7998, + 469.66730000000001, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "is" + ], + [ + 474.23250000000002, + 281.7998, + 491.0086, + 292.98469999999998, + 10.1, + 1, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "able" + ], + [ + 495.63440000000003, + 281.7998, + 503.3811, + 292.98469999999998, + 10.1, + 0, + 290.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 293.49979999999999, + 145.167, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "assemble" + ], + [ + 148.28790000000001, + 293.49979999999999, + 168.96260000000001, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "more" + ], + [ + 172.18450000000001, + 293.49979999999999, + 191.76840000000001, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "CPU" + ], + [ + 194.8691, + 293.49979999999999, + 220.13929999999999, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "power" + ], + [ + 223.27029999999999, + 293.49979999999999, + 240.62209999999999, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "than" + ], + [ + 243.77330000000001, + 293.49979999999999, + 253.8733, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "all" + ], + [ + 256.97399999999999, + 293.49979999999999, + 269.34649999999999, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 272.4674, + 293.49979999999999, + 298.86880000000002, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "honest" + ], + [ + 301.96949999999998, + 293.49979999999999, + 325.6035, + 304.68470000000002, + 10.1, + 0, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "nodes" + ], + [ + 325.57319999999999, + 293.49979999999999, + 328.09820000000002, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 331.16860000000003, + 293.49979999999999, + 340.74340000000001, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "he" + ], + [ + 343.86430000000001, + 293.49979999999999, + 369.01330000000002, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "would" + ], + [ + 372.26549999999997, + 293.49979999999999, + 391.24340000000001, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "have" + ], + [ + 394.46530000000001, + 293.49979999999999, + 402.21199999999999, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 405.36320000000001, + 293.49979999999999, + 433.44119999999998, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "choose" + ], + [ + 436.56209999999999, + 293.49979999999999, + 470.22539999999998, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "between" + ], + [ + 473.3766, + 293.49979999999999, + 495.22289999999998, + 304.68470000000002, + 10.1, + 1, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "using" + ], + [ + 498.3741, + 293.49979999999999, + 503.96949999999998, + 304.68470000000002, + 10.1, + 0, + 302.5, + 0, + 0, + 0, + 0, + 0, + 0, + "it" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 305.09980000000002, + 115.9477, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 119.0989, + 305.09980000000002, + 149.86349999999999, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "defraud" + ], + [ + 153.0147, + 305.09980000000002, + 179.89080000000001, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "people" + ], + [ + 183.01169999999999, + 305.09980000000002, + 193.16220000000001, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "by" + ], + [ + 196.2124, + 305.09980000000002, + 227.56280000000001, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "stealing" + ], + [ + 230.714, + 305.09980000000002, + 249.67169999999999, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "back" + ], + [ + 252.8229, + 305.09980000000002, + 264.64999999999998, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "his" + ], + [ + 267.72039999999998, + 305.09980000000002, + 306.35289999999998, + 316.28469999999999, + 10.1, + 0, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "payments" + ], + [ + 306.42360000000002, + 305.09980000000002, + 308.9486, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 312.01900000000001, + 305.09980000000002, + 320.3818, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "or" + ], + [ + 323.51280000000003, + 305.09980000000002, + 345.35910000000001, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "using" + ], + [ + 348.40929999999997, + 305.09980000000002, + 354.00470000000001, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "it" + ], + [ + 357.10539999999997, + 305.09980000000002, + 364.95310000000001, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 368.10430000000002, + 305.09980000000002, + 402.2928, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "generate" + ], + [ + 405.41370000000001, + 305.09980000000002, + 422.20999999999998, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "new" + ], + [ + 425.32080000000002, + 305.09980000000002, + 446.55099999999999, + 316.28469999999999, + 10.1, + 0, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "coins" + ], + [ + 446.62169999999998, + 305.09980000000002, + 449.14670000000001, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 455.31779999999998, + 305.09980000000002, + 466.99340000000001, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "He" + ], + [ + 470.11430000000001, + 305.09980000000002, + 493.11200000000002, + 316.28469999999999, + 10.1, + 1, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "ought" + ], + [ + 496.21269999999998, + 305.09980000000002, + 503.95940000000002, + 316.28469999999999, + 10.1, + 0, + 314.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 316.7998, + 124.3509, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "find" + ], + [ + 127.4011, + 316.7998, + 132.9965, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "it" + ], + [ + 136.09719999999999, + 316.7998, + 156.77189999999999, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "more" + ], + [ + 159.89279999999999, + 316.7998, + 199.07069999999999, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "profitable" + ], + [ + 202.19159999999999, + 316.7998, + 209.9383, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 212.98849999999999, + 316.7998, + 230.44130000000001, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "play" + ], + [ + 233.4915, + 316.7998, + 243.642, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "by" + ], + [ + 246.59119999999999, + 316.7998, + 258.86270000000002, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 261.88260000000002, + 316.7998, + 281.61799999999999, + 327.98469999999998, + 10.1, + 0, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "rules" + ], + [ + 281.58769999999998, + 316.7998, + 284.11270000000002, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 287.08210000000003, + 316.7998, + 305.63580000000002, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "such" + ], + [ + 308.68599999999998, + 316.7998, + 328.32040000000001, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "rules" + ], + [ + 331.28980000000001, + 316.7998, + 346.49029999999999, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "that" + ], + [ + 349.49000000000001, + 316.7998, + 375.86110000000002, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "favour" + ], + [ + 378.89109999999999, + 316.7998, + 394.637, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "him" + ], + [ + 397.68720000000002, + 316.7998, + 415.53390000000002, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "with" + ], + [ + 418.58409999999998, + 316.7998, + 439.35980000000001, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "more" + ], + [ + 442.37970000000001, + 316.7998, + 459.17599999999999, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "new" + ], + [ + 462.27670000000001, + 316.7998, + 483.60789999999997, + 327.98469999999998, + 10.1, + 1, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "coins" + ], + [ + 486.57729999999998, + 316.7998, + 503.92910000000001, + 327.98469999999998, + 10.1, + 0, + 325.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "than" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 328.39980000000003, + 145.08619999999999, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "everyone" + ], + [ + 147.7122, + 328.39980000000003, + 163.38740000000001, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "else" + ], + [ + 165.92250000000001, + 328.39980000000003, + 205.67609999999999, + 339.5847, + 10.1, + 0, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "combined" + ], + [ + 205.72659999999999, + 328.39980000000003, + 208.2516, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 210.8271, + 328.39980000000003, + 228.1789, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "than" + ], + [ + 230.73419999999999, + 328.39980000000003, + 238.58189999999999, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 241.13720000000001, + 328.39980000000003, + 284.31470000000002, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "undermine" + ], + [ + 286.84980000000002, + 328.39980000000003, + 299.22230000000002, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 301.75740000000002, + 328.39980000000003, + 329.80509999999998, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "system" + ], + [ + 332.3503, + 328.39980000000003, + 346.90440000000001, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "and" + ], + [ + 349.4597, + 328.39980000000003, + 361.7312, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 364.2663, + 328.39980000000003, + 395.21269999999998, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "validity" + ], + [ + 397.66699999999997, + 328.39980000000003, + 406.13080000000002, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ], + [ + 408.67599999999999, + 328.39980000000003, + 420.40210000000002, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "his" + ], + [ + 422.9776, + 328.39980000000003, + 440.32940000000002, + 339.5847, + 10.1, + 1, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "own" + ], + [ + 442.88470000000001, + 328.39980000000003, + 469.74059999999997, + 339.5847, + 10.1, + 0, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "wealth" + ], + [ + 469.79109999999997, + 328.39980000000003, + 472.31610000000001, + 339.5847, + 10.1, + 0, + 337.39999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 354.76280000000003, + 114.70099999999999, + 368.58749999999998, + 11.5, + 0, + 366.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "7" + ], + [ + 114.70099999999999, + 354.76280000000003, + 117.8865, + 368.58749999999998, + 11.5, + 1, + 366.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 133.30000000000001, + 354.76280000000003, + 201.21899999999999, + 368.58749999999998, + 11.5, + 1, + 366.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "Reclaiming" + ], + [ + 204.59999999999999, + 354.76280000000003, + 231.76300000000001, + 368.58749999999998, + 11.5, + 1, + 366.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "Disk" + ], + [ + 235.1095, + 354.76280000000003, + 270.52949999999998, + 368.58749999999998, + 11.5, + 0, + 366.10000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "Space" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 376.19979999999998, + 129.38069999999999, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "Once" + ], + [ + 132.4006, + 376.19979999999998, + 144.7731, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 147.69200000000001, + 376.19979999999998, + 168.99289999999999, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "latest" + ], + [ + 171.99260000000001, + 376.19979999999998, + 216.3518, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "transaction" + ], + [ + 219.30099999999999, + 376.19979999999998, + 227.14869999999999, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "in" + ], + [ + 230.19890000000001, + 376.19979999999998, + 234.67320000000001, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 237.59209999999999, + 376.19979999999998, + 254.94390000000001, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "coin" + ], + [ + 257.9941, + 376.19979999999998, + 264.72070000000002, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "is" + ], + [ + 267.69009999999997, + 376.19979999999998, + 293.44510000000002, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "buried" + ], + [ + 296.49529999999999, + 376.19979999999998, + 319.46269999999998, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "under" + ], + [ + 322.49270000000001, + 376.19979999999998, + 352.1463, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "enough" + ], + [ + 355.19650000000001, + 376.19979999999998, + 381.52719999999999, + 387.38470000000001, + 10.1, + 0, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "blocks" + ], + [ + 381.49689999999998, + 376.19979999999998, + 384.02190000000002, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 386.99130000000002, + 376.19979999999998, + 399.36380000000003, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 402.38369999999998, + 376.19979999999998, + 423.68459999999999, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "spent" + ], + [ + 426.68430000000001, + 376.19979999999998, + 474.92189999999999, + 387.38470000000001, + 10.1, + 1, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "transactions" + ], + [ + 477.8913, + 376.19979999999998, + 503.67660000000001, + 387.38470000000001, + 10.1, + 0, + 385.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "before" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 387.89980000000003, + 113.69540000000001, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "it" + ], + [ + 118.29089999999999, + 387.89980000000003, + 132.2491, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "can" + ], + [ + 136.80420000000001, + 387.89980000000003, + 146.37899999999999, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "be" + ], + [ + 150.90379999999999, + 387.89980000000003, + 189.56659999999999, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "discarded" + ], + [ + 194.1217, + 387.89980000000003, + 201.96940000000001, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 206.52449999999999, + 387.89980000000003, + 224.4015, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "save" + ], + [ + 229.0273, + 387.89980000000003, + 245.7731, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "disk" + ], + [ + 250.32820000000001, + 387.89980000000003, + 272.81079999999997, + 399.0847, + 10.1, + 0, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "space" + ], + [ + 272.84109999999998, + 387.89980000000003, + 275.36610000000002, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 284.25409999999999, + 387.89980000000003, + 294.70760000000001, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "To" + ], + [ + 299.2627, + 387.89980000000003, + 334.54199999999997, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "facilitate" + ], + [ + 339.1678, + 387.89980000000003, + 353.69159999999999, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "this" + ], + [ + 358.26690000000002, + 387.89980000000003, + 389.06180000000001, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "without" + ], + [ + 393.65730000000002, + 387.89980000000003, + 428.91640000000001, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "breaking" + ], + [ + 433.47149999999999, + 387.89980000000003, + 445.84399999999999, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 450.37889999999999, + 387.89980000000003, + 478.50740000000002, + 399.0847, + 10.1, + 1, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "block's" + ], + [ + 483.08269999999999, + 387.89980000000003, + 501.53539999999998, + 399.0847, + 10.1, + 0, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "hash" + ], + [ + 501.58589999999998, + 387.89980000000003, + 504.11090000000002, + 399.0847, + 10.1, + 0, + 396.89999999999998, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 399.49979999999999, + 156.43860000000001, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "transactions" + ], + [ + 159.00399999999999, + 399.49979999999999, + 171.28559999999999, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "are" + ], + [ + 173.91159999999999, + 399.49979999999999, + 201.86840000000001, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "hashed" + ], + [ + 204.5146, + 399.49979999999999, + 212.3623, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "in" + ], + [ + 214.91759999999999, + 399.49979999999999, + 219.39189999999999, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 222.0179, + 399.49979999999999, + 251.1968, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "Merkle" + ], + [ + 253.6309, + 399.49979999999999, + 271.7099, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "Tree" + ], + [ + 274.33589999999998, + 399.49979999999999, + 277.69920000000002, + 410.68470000000002, + 10.1, + 0, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "[" + ], + [ + 277.6386, + 399.49979999999999, + 282.68860000000001, + 410.68470000000002, + 10.1, + 0, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "7" + ], + [ + 282.73910000000001, + 399.49979999999999, + 286.10239999999999, + 410.68470000000002, + 10.1, + 0, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "]" + ], + [ + 286.04180000000002, + 399.49979999999999, + 289.4051, + 410.68470000000002, + 10.1, + 0, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "[" + ], + [ + 289.44549999999998, + 399.49979999999999, + 294.49549999999999, + 410.68470000000002, + 10.1, + 0, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "2" + ], + [ + 294.44499999999999, + 399.49979999999999, + 297.80829999999997, + 410.68470000000002, + 10.1, + 0, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "]" + ], + [ + 297.84870000000001, + 399.49979999999999, + 301.21199999999999, + 410.68470000000002, + 10.1, + 0, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "[" + ], + [ + 301.25240000000002, + 399.49979999999999, + 306.30239999999998, + 410.68470000000002, + 10.1, + 0, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "5" + ], + [ + 306.25189999999998, + 399.49979999999999, + 309.61520000000002, + 410.68470000000002, + 10.1, + 0, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "]" + ], + [ + 309.65559999999999, + 399.49979999999999, + 312.18060000000003, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 314.7561, + 399.49979999999999, + 332.7038, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "with" + ], + [ + 335.35000000000002, + 399.49979999999999, + 353.29770000000002, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "only" + ], + [ + 355.84289999999999, + 399.49979999999999, + 368.11439999999999, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 370.74040000000002, + 399.49979999999999, + 387.04180000000002, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "root" + ], + [ + 389.63749999999999, + 399.49979999999999, + 424.29059999999998, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "included" + ], + [ + 426.93680000000001, + 399.49979999999999, + 434.78449999999998, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "in" + ], + [ + 437.33980000000003, + 399.49979999999999, + 449.71230000000003, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 452.3383, + 399.49979999999999, + 480.46679999999998, + 410.68470000000002, + 10.1, + 1, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "block's" + ], + [ + 483.04230000000001, + 399.49979999999999, + 501.495, + 410.68470000000002, + 10.1, + 0, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "hash" + ], + [ + 501.5455, + 399.49979999999999, + 504.07049999999998, + 410.68470000000002, + 10.1, + 0, + 408.5, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 411.19979999999998, + 123.25, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "Old" + ], + [ + 126.6032, + 411.19979999999998, + 152.93389999999999, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "blocks" + ], + [ + 156.3073, + 411.19979999999998, + 170.2655, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "can" + ], + [ + 173.61869999999999, + 411.19979999999998, + 190.97049999999999, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "then" + ], + [ + 194.4247, + 411.19979999999998, + 203.89850000000001, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "be" + ], + [ + 207.22139999999999, + 411.19979999999998, + 250.9847, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "compacted" + ], + [ + 254.33789999999999, + 411.19979999999998, + 264.48840000000001, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "by" + ], + [ + 267.74059999999997, + 411.19979999999998, + 302.4846, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "stubbing" + ], + [ + 305.83780000000002, + 411.19979999999998, + 317.50330000000002, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "off" + ], + [ + 320.83629999999999, + 411.19979999999998, + 356.68119999999999, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "branches" + ], + [ + 360.05459999999999, + 411.19979999999998, + 368.51839999999999, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ], + [ + 371.75040000000001, + 411.19979999999998, + 384.12290000000002, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 387.44580000000002, + 411.19979999999998, + 402.52510000000001, + 422.38470000000001, + 10.1, + 0, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "tree" + ], + [ + 402.55540000000002, + 411.19979999999998, + 405.0804, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 411.55450000000002, + 411.19979999999998, + 427.22969999999998, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "The" + ], + [ + 430.65359999999998, + 411.19979999999998, + 460.31729999999999, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "interior" + ], + [ + 463.65030000000002, + 411.19979999999998, + 490.58699999999999, + 422.38470000000001, + 10.1, + 1, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "hashes" + ], + [ + 493.96039999999999, + 411.19979999999998, + 504.00990000000002, + 422.38470000000001, + 10.1, + 0, + 420.19999999999999, + 0, + 0, + 0, + 0, + 0, + 0, + "do" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 422.7998, + 120.99769999999999, + 433.98469999999998, + 10.1, + 1, + 431.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "not" + ], + [ + 123.5934, + 422.7998, + 142.55109999999999, + 433.98469999999998, + 10.1, + 1, + 431.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "need" + ], + [ + 145.19730000000001, + 422.7998, + 152.94399999999999, + 433.98469999999998, + 10.1, + 1, + 431.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "to" + ], + [ + 155.49930000000001, + 422.7998, + 165.07409999999999, + 433.98469999999998, + 10.1, + 1, + 431.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "be" + ], + [ + 167.60919999999999, + 422.7998, + 192.26329999999999, + 433.98469999999998, + 10.1, + 0, + 431.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "stored" + ], + [ + 192.31379999999999, + 422.7998, + 194.83879999999999, + 433.98469999999998, + 10.1, + 0, + 431.80000000000001, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 622.69979999999998, + 129.79220000000001, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "A" + ], + [ + 133.49889999999999, + 622.69979999999998, + 155.8502, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "block" + ], + [ + 160.20330000000001, + 622.69979999999998, + 187.07939999999999, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "header" + ], + [ + 191.32140000000001, + 622.69979999999998, + 209.26910000000001, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "with" + ], + [ + 213.52119999999999, + 622.69979999999998, + 223.57069999999999, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "no" + ], + [ + 227.8228, + 622.69979999999998, + 276.06040000000002, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "transactions" + ], + [ + 280.33269999999999, + 622.69979999999998, + 305.48169999999999, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "would" + ], + [ + 309.83479999999997, + 622.69979999999998, + 319.30860000000001, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "be" + ], + [ + 323.54050000000001, + 622.69979999999998, + 346.04329999999999, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "about" + ], + [ + 350.24489999999997, + 622.69979999999998, + 360.2944, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "80" + ], + [ + 364.54649999999998, + 622.69979999999998, + 385.8777, + 633.88469999999995, + 10.1, + 0, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "bytes" + ], + [ + 385.84739999999999, + 622.69979999999998, + 388.37240000000003, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 396.85640000000001, + 622.69979999999998, + 403.6234, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "If" + ], + [ + 407.86540000000002, + 622.69979999999998, + 419.541, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "we" + ], + [ + 423.87389999999999, + 622.69979999999998, + 456.34539999999998, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "suppose" + ], + [ + 460.57729999999998, + 622.69979999999998, + 487.00900000000001, + 633.88469999999995, + 10.1, + 1, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "blocks" + ], + [ + 491.18029999999999, + 622.69979999999998, + 503.56290000000001, + 633.88469999999995, + 10.1, + 0, + 631.70000000000005, + 0, + 0, + 0, + 0, + 0, + 0, + "are" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 634.2998, + 147.36879999999999, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "generated" + ], + [ + 150.41900000000001, + 634.2998, + 172.78039999999999, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "every" + ], + [ + 175.8306, + 634.2998, + 185.8801, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "10" + ], + [ + 188.82929999999999, + 634.2998, + 220.85640000000001, + 645.48469999999998, + 10.1, + 0, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "minutes" + ], + [ + 220.8261, + 634.2998, + 223.3511, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 226.32050000000001, + 634.2998, + 236.471, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "80" + ], + [ + 239.42019999999999, + 634.2998, + 260.75139999999999, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "bytes" + ], + [ + 263.7208, + 634.2998, + 268.77080000000001, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "*" + ], + [ + 271.72000000000003, + 634.2998, + 276.76999999999998, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "6" + ], + [ + 279.8202, + 634.2998, + 284.87020000000001, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "*" + ], + [ + 287.81939999999997, + 634.2998, + 297.8689, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "24" + ], + [ + 300.91910000000001, + 634.2998, + 305.96910000000003, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "*" + ], + [ + 308.91829999999999, + 634.2998, + 324.06830000000002, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "365" + ], + [ + 327.01749999999998, + 634.2998, + 332.7038, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "=" + ], + [ + 335.71359999999999, + 634.2998, + 340.7636, + 645.48469999999998, + 10.1, + 0, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "4" + ], + [ + 340.8141, + 634.2998, + 343.33909999999997, + 645.48469999999998, + 10.1, + 0, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 343.21789999999999, + 634.2998, + 364.04410000000001, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "2MB" + ], + [ + 367.01350000000002, + 634.2998, + 379.8809, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "per" + ], + [ + 382.91090000000003, + 634.2998, + 400.28289999999998, + 645.48469999999998, + 10.1, + 0, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "year" + ], + [ + 399.71730000000002, + 634.2998, + 402.2423, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 408.00940000000003, + 634.2998, + 427.75490000000002, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "With" + ], + [ + 430.80509999999998, + 634.2998, + 468.87200000000001, + 645.48469999999998, + 10.1, + 1, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "computer" + ], + [ + 471.90199999999999, + 634.2998, + 503.82810000000001, + 645.48469999999998, + 10.1, + 0, + 643.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "systems" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 645.99980000000005, + 143.44999999999999, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "typically" + ], + [ + 147.09610000000001, + 645.99980000000005, + 173.9419, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "selling" + ], + [ + 177.58799999999999, + 645.99980000000005, + 195.43469999999999, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "with" + ], + [ + 199.08080000000001, + 645.99980000000005, + 218.21019999999999, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "2GB" + ], + [ + 221.78559999999999, + 645.99980000000005, + 230.24940000000001, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ], + [ + 233.68340000000001, + 645.99980000000005, + 256.66090000000003, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "RAM" + ], + [ + 260.27670000000001, + 645.99980000000005, + 268.71019999999999, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "as" + ], + [ + 272.28559999999999, + 645.99980000000005, + 280.74939999999998, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ], + [ + 284.28440000000001, + 645.99980000000005, + 304.53489999999999, + 657.18470000000002, + 10.1, + 0, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "2008" + ], + [ + 304.48439999999999, + 645.99980000000005, + 307.00940000000003, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 310.58479999999997, + 645.99980000000005, + 325.13889999999998, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "and" + ], + [ + 328.78500000000003, + 645.99980000000005, + 361.40800000000002, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "Moore's" + ], + [ + 364.98340000000002, + 645.99980000000005, + 382.98160000000001, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "Law" + ], + [ + 386.48630000000003, + 645.99980000000005, + 427.4418, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "predicting" + ], + [ + 431.08789999999999, + 645.99980000000005, + 459.70119999999997, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "current" + ], + [ + 463.30689999999998, + 645.99980000000005, + 491.8596, + 657.18470000000002, + 10.1, + 1, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "growth" + ], + [ + 495.50569999999999, + 645.99980000000005, + 503.96949999999998, + 657.18470000000002, + 10.1, + 0, + 655, + 0, + 0, + 0, + 0, + 0, + 0, + "of" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 657.59979999999996, + 113.15000000000001, + 668.78470000000004, + 10.1, + 0, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "1" + ], + [ + 113.20050000000001, + 657.59979999999996, + 115.7255, + 668.78470000000004, + 10.1, + 0, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 115.70529999999999, + 657.59979999999996, + 134.7337, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "2GB" + ], + [ + 139.208, + 657.59979999999996, + 152.0754, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "per" + ], + [ + 156.41839999999999, + 657.59979999999996, + 173.79040000000001, + 668.78470000000004, + 10.1, + 0, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "year" + ], + [ + 173.42679999999999, + 657.59979999999996, + 175.95179999999999, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "," + ], + [ + 180.33519999999999, + 657.59979999999996, + 209.51410000000001, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "storage" + ], + [ + 213.94800000000001, + 657.59979999999996, + 240.7938, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "should" + ], + [ + 245.24789999999999, + 657.59979999999996, + 258.1456, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "not" + ], + [ + 262.54919999999998, + 657.59979999999996, + 272.02300000000002, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "be" + ], + [ + 276.45690000000002, + 657.59979999999996, + 280.93119999999999, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "a" + ], + [ + 285.26409999999998, + 657.59979999999996, + 318.91730000000001, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "problem" + ], + [ + 323.37139999999999, + 657.59979999999996, + 342.32909999999998, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "even" + ], + [ + 346.78320000000002, + 657.59979999999996, + 352.94420000000002, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "if" + ], + [ + 357.38819999999998, + 657.59979999999996, + 369.65969999999999, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "the" + ], + [ + 374.09359999999998, + 657.59979999999996, + 396.44490000000002, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "block" + ], + [ + 400.899, + 657.59979999999996, + 431.74439999999998, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "headers" + ], + [ + 436.11770000000001, + 657.59979999999996, + 455.71170000000001, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "must" + ], + [ + 460.11529999999999, + 657.59979999999996, + 469.69009999999997, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "be" + ], + [ + 474.02300000000002, + 657.59979999999996, + 491.42529999999999, + 668.78470000000004, + 10.1, + 1, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "kept" + ], + [ + 495.82889999999998, + 657.59979999999996, + 503.67660000000001, + 668.78470000000004, + 10.1, + 0, + 666.60000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "in" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 669.2998, + 141.8441, + 680.48469999999998, + 10.1, + 0, + 678.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "memory" + ], + [ + 141.0967, + 669.2998, + 143.6217, + 680.48469999999998, + 10.1, + 0, + 678.29999999999995, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ] + ] + ], + [ + [ + [ + 310.80000000000001, + 708.99980000000005, + 315.85000000000002, + 720.18470000000002, + 10.1, + 0, + 718, + 0, + 0, + 0, + 0, + 0, + 0, + "4" + ] + ] + ], + [ + [ + [ + 162.90000000000001, + 449.05360000000002, + 179.86850000000001, + 456.76220000000001, + 6.9000000000000004, + 1, + 455.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ], + [ + 190.40000000000001, + 454.75360000000001, + 207.36160000000001, + 462.4622, + 6.9000000000000004, + 1, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ], + [ + 209.40309999999999, + 454.75360000000001, + 232.1155, + 462.4622, + 6.9000000000000004, + 1, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + "Header" + ], + [ + 234.01159999999999, + 454.75360000000001, + 236.31610000000001, + 462.4622, + 6.9000000000000004, + 0, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + "(" + ], + [ + 236.3092, + 454.75360000000001, + 253.27770000000001, + 462.4622, + 6.9000000000000004, + 1, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ], + [ + 255.22229999999999, + 454.75360000000001, + 271.37430000000001, + 462.4622, + 6.9000000000000004, + 0, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ], + [ + 271.42959999999999, + 454.75360000000001, + 273.73410000000001, + 462.4622, + 6.9000000000000004, + 1, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + ")" + ], + [ + 323.10000000000002, + 449.05360000000002, + 340.0616, + 456.76220000000001, + 6.9000000000000004, + 0, + 455.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ] + ] + ], + [ + [ + [ + 202.5, + 469.65359999999998, + 216.65889999999999, + 477.36219999999997, + 6.9000000000000004, + 1, + 475.89999999999998, + 0, + 0, + 0, + 0, + 0, + 2, + "Prev" + ], + [ + 218.5966, + 469.65359999999998, + 234.74850000000001, + 477.36219999999997, + 6.9000000000000004, + 1, + 475.89999999999998, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ], + [ + 252.5, + 469.65359999999998, + 272.45119999999997, + 477.36219999999997, + 6.9000000000000004, + 0, + 475.89999999999998, + 0, + 0, + 0, + 0, + 0, + 2, + "Nonce" + ] + ] + ], + [ + [ + [ + 184.09999999999999, + 519.45360000000005, + 207.95419999999999, + 527.16219999999998, + 6.9000000000000004, + 0, + 525.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash01" + ] + ] + ], + [ + [ + [ + 169.80000000000001, + 552.05359999999996, + 189.75120000000001, + 559.76220000000001, + 6.9000000000000004, + 1, + 558.29999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash0" + ], + [ + 203.09999999999999, + 552.05359999999996, + 223.05119999999999, + 559.76220000000001, + 6.9000000000000004, + 1, + 558.29999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash1" + ], + [ + 235.80000000000001, + 552.05359999999996, + 255.85499999999999, + 559.76220000000001, + 6.9000000000000004, + 1, + 558.29999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash2" + ], + [ + 269.10000000000002, + 552.05359999999996, + 289.15499999999997, + 559.76220000000001, + 6.9000000000000004, + 0, + 558.29999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash3" + ] + ] + ], + [ + [ + [ + 250.69999999999999, + 519.45360000000005, + 274.65800000000002, + 527.16219999999998, + 6.9000000000000004, + 0, + 525.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash23" + ] + ] + ], + [ + [ + [ + 212.80000000000001, + 486.25360000000001, + 227.42259999999999, + 493.9622, + 6.9000000000000004, + 1, + 492.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Root" + ], + [ + 229.40870000000001, + 486.25360000000001, + 245.56059999999999, + 493.9622, + 6.9000000000000004, + 0, + 492.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ] + ] + ], + [ + [ + [ + 343.19999999999999, + 519.45360000000005, + 367.05419999999998, + 527.16219999999998, + 6.9000000000000004, + 0, + 525.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash01" + ] + ] + ], + [ + [ + [ + 396, + 552.05359999999996, + 416.05500000000001, + 559.76220000000001, + 6.9000000000000004, + 0, + 558.29999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash2" + ] + ] + ], + [ + [ + [ + 433.30000000000001, + 579.55359999999996, + 444.64929999999998, + 587.26220000000001, + 6.9000000000000004, + 0, + 585.79999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx3" + ] + ] + ], + [ + [ + [ + 409.19999999999999, + 519.45360000000005, + 433.15800000000002, + 527.16219999999998, + 6.9000000000000004, + 0, + 525.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash23" + ] + ] + ], + [ + [ + [ + 350.69999999999999, + 454.75360000000001, + 367.66849999999999, + 462.4622, + 6.9000000000000004, + 1, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ], + [ + 369.6062, + 454.75360000000001, + 392.3186, + 462.4622, + 6.9000000000000004, + 1, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + "Header" + ], + [ + 394.21480000000003, + 454.75360000000001, + 396.51920000000001, + 462.4622, + 6.9000000000000004, + 0, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + "(" + ], + [ + 396.51229999999998, + 454.75360000000001, + 413.47390000000001, + 462.4622, + 6.9000000000000004, + 1, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ], + [ + 415.41849999999999, + 454.75360000000001, + 431.57049999999998, + 462.4622, + 6.9000000000000004, + 0, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ], + [ + 431.62580000000003, + 454.75360000000001, + 433.93029999999999, + 462.4622, + 6.9000000000000004, + 0, + 461, + 0, + 0, + 0, + 0, + 0, + 2, + ")" + ] + ] + ], + [ + [ + [ + 373.10000000000002, + 486.25360000000001, + 387.7226, + 493.9622, + 6.9000000000000004, + 1, + 492.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Root" + ], + [ + 389.70870000000002, + 486.25360000000001, + 405.86059999999998, + 493.9622, + 6.9000000000000004, + 0, + 492.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ] + ] + ], + [ + [ + [ + 168.59999999999999, + 600.25360000000001, + 207.96950000000001, + 607.96220000000005, + 6.9000000000000004, + 1, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Transactions" + ], + [ + 210.011, + 600.25360000000001, + 233.86519999999999, + 607.96220000000005, + 6.9000000000000004, + 1, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Hashed" + ], + [ + 235.8237, + 600.25360000000001, + 241.26990000000001, + 607.96220000000005, + 6.9000000000000004, + 1, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "in" + ], + [ + 243.12459999999999, + 600.25360000000001, + 246.97219999999999, + 607.96220000000005, + 6.9000000000000004, + 1, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "a" + ], + [ + 248.9307, + 600.25360000000001, + 269.58080000000001, + 607.96220000000005, + 6.9000000000000004, + 1, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Merkle" + ], + [ + 271.43540000000002, + 600.25360000000001, + 285.38670000000002, + 607.96220000000005, + 6.9000000000000004, + 1, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Tree" + ], + [ + 334.60000000000002, + 600.25360000000001, + 349.21559999999999, + 607.96220000000005, + 6.9000000000000004, + 1, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "After" + ], + [ + 351.11180000000002, + 600.25360000000001, + 374.95909999999998, + 607.96220000000005, + 6.9000000000000004, + 1, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Pruning" + ], + [ + 376.81369999999998, + 600.25360000000001, + 394.25979999999998, + 607.96220000000005, + 6.9000000000000004, + 1, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx0-2" + ], + [ + 396.21820000000002, + 600.25360000000001, + 410.0865, + 607.96220000000005, + 6.9000000000000004, + 1, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "from" + ], + [ + 412.02420000000001, + 600.25360000000001, + 421.67099999999999, + 607.96220000000005, + 6.9000000000000004, + 1, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "the" + ], + [ + 423.62950000000001, + 600.25360000000001, + 440.59109999999998, + 607.96220000000005, + 6.9000000000000004, + 0, + 606.5, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ] + ] + ], + [ + [ + [ + 362.10000000000002, + 469.65359999999998, + 376.36270000000002, + 477.36219999999997, + 6.9000000000000004, + 1, + 475.89999999999998, + 0, + 0, + 0, + 0, + 0, + 2, + "Prev" + ], + [ + 378.20350000000002, + 469.65359999999998, + 394.35550000000001, + 477.36219999999997, + 6.9000000000000004, + 1, + 475.89999999999998, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ], + [ + 412.10000000000002, + 469.65359999999998, + 432.15499999999997, + 477.36219999999997, + 6.9000000000000004, + 0, + 475.89999999999998, + 0, + 0, + 0, + 0, + 0, + 2, + "Nonce" + ] + ] + ], + [ + [ + [ + 428.19999999999999, + 552.05359999999996, + 448.15120000000002, + 559.76220000000001, + 6.9000000000000004, + 0, + 558.29999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash3" + ] + ] + ], + [ + [ + [ + 174.40000000000001, + 579.55359999999996, + 185.74930000000001, + 587.26220000000001, + 6.9000000000000004, + 1, + 585.79999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx0" + ], + [ + 207.09999999999999, + 579.55359999999996, + 218.34549999999999, + 587.26220000000001, + 6.9000000000000004, + 1, + 585.79999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx1" + ], + [ + 240.40000000000001, + 579.55359999999996, + 251.6455, + 587.26220000000001, + 6.9000000000000004, + 1, + 585.79999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx2" + ], + [ + 273.10000000000002, + 579.55359999999996, + 284.44929999999999, + 587.26220000000001, + 6.9000000000000004, + 0, + 585.79999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx3" + ] + ] + ] + ] + ] + ] + ] + ] + ], + [ + 612, + 792, + [ + [ + [ + [ + 0, + 0, + 0, + 0, + [ + [ + [ + [ + 108.09999999999999, + 82.162800000000004, + 114.70099999999999, + 95.987499999999997, + 11.5, + 0, + 93.5, + 0, + 0, + 0, + 0, + 0, + 0, + "8" + ], + [ + 114.70099999999999, + 82.162800000000004, + 117.8865, + 95.987499999999997, + 11.5, + 1, + 93.5, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 133.30000000000001, + 82.162800000000004, + 195.27350000000001, + 95.987499999999997, + 11.5, + 1, + 93.5, + 0, + 0, + 0, + 0, + 0, + 0, + "Simplified" + ], + [ + 198.62, + 82.162800000000004, + 251.81899999999999, + 95.987499999999997, + 11.5, + 1, + 93.5, + 0, + 0, + 0, + 0, + 0, + 0, + "Payment" + ], + [ + 255.131, + 82.162800000000004, + 326.93700000000001, + 95.987499999999997, + 11.5, + 0, + 93.5, + 0, + 0, + 0, + 0, + 0, + 0, + "Verification" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 103.5998, + 114.3014, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "It" + ], + [ + 117.20010000000001, + 103.5998, + 123.9267, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 126.79510000000001, + 103.5998, + 159.86250000000001, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "possible" + ], + [ + 162.78139999999999, + 103.5998, + 170.52809999999999, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 173.47730000000001, + 103.5998, + 197.63650000000001, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "verify" + ], + [ + 200.4847, + 103.5998, + 239.2182, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "payments" + ], + [ + 242.0866, + 103.5998, + 272.98250000000002, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "without" + ], + [ + 275.88119999999998, + 103.5998, + 307.23160000000001, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "running" + ], + [ + 310.18079999999998, + 103.5998, + 314.6551, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 317.47300000000001, + 103.5998, + 331.47160000000002, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "full" + ], + [ + 334.37029999999999, + 103.5998, + 367.42759999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 370.3768, + 103.5998, + 389.95060000000001, + 114.7847, + 10.1, + 0, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "node" + ], + [ + 389.98090000000002, + 103.5998, + 392.5059, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 397.6771, + 103.5998, + 404.96929999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "A" + ], + [ + 407.37310000000002, + 103.5998, + 424.13909999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "user" + ], + [ + 427.07819999999998, + 103.5998, + 445.02589999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "only" + ], + [ + 447.8741, + 103.5998, + 470.91219999999998, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "needs" + ], + [ + 473.78059999999999, + 103.5998, + 481.52730000000003, + 114.7847, + 10.1, + 1, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 484.47649999999999, + 103.5998, + 503.53519999999997, + 114.7847, + 10.1, + 0, + 112.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "keep" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 115.2998, + 112.57429999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 116.30119999999999, + 115.2998, + 135.95580000000001, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "copy" + ], + [ + 139.6019, + 115.2998, + 148.06569999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 151.70169999999999, + 115.2998, + 163.97319999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 167.70009999999999, + 115.2998, + 190.0514, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 193.79849999999999, + 115.2998, + 224.6439, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "headers" + ], + [ + 228.3203, + 115.2998, + 236.7841, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 240.42009999999999, + 115.2998, + 252.79259999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 256.51949999999999, + 115.2998, + 285.61759999999998, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "longest" + ], + [ + 289.32429999999999, + 115.2998, + 347.09629999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "proof-of-work" + ], + [ + 350.84339999999997, + 115.2998, + 372.69979999999998, + 126.4847, + 10.1, + 0, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 372.75029999999998, + 115.2998, + 375.27530000000002, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 378.95170000000002, + 115.2998, + 403.50479999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "which" + ], + [ + 407.25189999999998, + 115.2998, + 416.82670000000002, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "he" + ], + [ + 420.45260000000002, + 115.2998, + 434.41079999999999, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "can" + ], + [ + 438.15789999999998, + 115.2998, + 450.5607, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "get" + ], + [ + 454.16640000000001, + 115.2998, + 464.31689999999998, + 126.4847, + 10.1, + 1, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 467.96300000000002, + 115.2998, + 503.81799999999998, + 126.4847, + 10.1, + 0, + 124.3, + 0, + 0, + 0, + 0, + 0, + 1, + "querying" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 126.8998, + 141.15729999999999, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 146.00530000000001, + 126.8998, + 169.53829999999999, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 174.2045, + 126.8998, + 192.79859999999999, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "until" + ], + [ + 197.49510000000001, + 126.8998, + 212.7158, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "he's" + ], + [ + 217.483, + 126.8998, + 258.94349999999997, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "convinced" + ], + [ + 263.69049999999999, + 126.8998, + 273.26530000000002, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "he" + ], + [ + 277.98200000000003, + 126.8998, + 291.41500000000002, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "has" + ], + [ + 296.18220000000002, + 126.8998, + 308.45370000000003, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 313.27140000000003, + 126.8998, + 342.36950000000002, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "longest" + ], + [ + 347.16699999999997, + 126.8998, + 368.92239999999998, + 138.0847, + 10.1, + 0, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 368.97289999999998, + 126.8998, + 371.49790000000002, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 376.26510000000002, + 126.8998, + 390.81920000000002, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 395.56619999999998, + 126.8998, + 420.71519999999998, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "obtain" + ], + [ + 425.56319999999999, + 126.8998, + 437.8347, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 442.5514, + 126.8998, + 471.7303, + 138.0847, + 10.1, + 1, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "Merkle" + ], + [ + 476.447, + 126.8998, + 503.90890000000002, + 138.0847, + 10.1, + 0, + 135.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "branch" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 138.59979999999999, + 136.74359999999999, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "linking" + ], + [ + 141.4906, + 138.59979999999999, + 153.8631, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 158.57980000000001, + 138.59979999999999, + 202.93899999999999, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 207.68600000000001, + 138.59979999999999, + 215.53370000000001, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 220.3817, + 138.59979999999999, + 232.6532, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 237.4709, + 138.59979999999999, + 259.82220000000001, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 264.67020000000002, + 138.59979999999999, + 275.9923, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "it's" + ], + [ + 280.8605, + 138.59979999999999, + 332.41090000000003, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "timestamped" + ], + [ + 337.25889999999998, + 138.59979999999999, + 345.00560000000002, + 149.78469999999999, + 10.1, + 0, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 345.05610000000001, + 138.59979999999999, + 347.58109999999999, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 357.14580000000001, + 138.59979999999999, + 368.92239999999998, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "He" + ], + [ + 373.63909999999998, + 138.59979999999999, + 392.24329999999998, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "can't" + ], + [ + 397.14179999999999, + 138.59979999999999, + 420.60410000000002, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "check" + ], + [ + 425.45209999999997, + 138.59979999999999, + 437.72359999999998, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 442.54129999999998, + 138.59979999999999, + 486.79950000000002, + 149.78469999999999, + 10.1, + 1, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 491.64749999999998, + 138.59979999999999, + 503.41399999999999, + 149.78469999999999, + 10.1, + 0, + 147.59999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 150.19980000000001, + 138.4606, + 161.38470000000001, + 10.1, + 0, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "himself" + ], + [ + 138.40000000000001, + 150.19980000000001, + 140.92500000000001, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 143.99539999999999, + 150.19980000000001, + 156.8931, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "but" + ], + [ + 159.99379999999999, + 150.19980000000001, + 170.14429999999999, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 173.19450000000001, + 150.19980000000001, + 201.7371, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "linking" + ], + [ + 204.88829999999999, + 150.19980000000001, + 210.4837, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 213.58439999999999, + 150.19980000000001, + 221.33109999999999, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 224.48230000000001, + 150.19980000000001, + 228.95660000000001, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 232.07749999999999, + 150.19980000000001, + 253.35820000000001, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "place" + ], + [ + 256.47910000000002, + 150.19980000000001, + 264.22579999999999, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 267.37700000000001, + 150.19980000000001, + 279.64850000000001, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 282.76940000000002, + 150.19980000000001, + 304.62580000000003, + 161.38470000000001, + 10.1, + 0, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "chain" + ], + [ + 304.67630000000003, + 150.19980000000001, + 307.2013, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 310.27170000000001, + 150.19980000000001, + 319.74549999999999, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "he" + ], + [ + 322.8664, + 150.19980000000001, + 336.82459999999998, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "can" + ], + [ + 339.97579999999999, + 150.19980000000001, + 352.85329999999999, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "see" + ], + [ + 355.9742, + 150.19980000000001, + 371.07369999999997, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 374.17439999999999, + 150.19980000000001, + 378.64870000000002, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 381.66860000000003, + 150.19980000000001, + 414.72590000000002, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 417.87709999999998, + 150.19980000000001, + 437.55189999999999, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "node" + ], + [ + 440.5718, + 150.19980000000001, + 454.10579999999999, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "has" + ], + [ + 457.17619999999999, + 150.19980000000001, + 492.44540000000001, + 161.38470000000001, + 10.1, + 1, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "accepted" + ], + [ + 495.59660000000002, + 150.19980000000001, + 501.19200000000001, + 161.38470000000001, + 10.1, + 0, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 501.19200000000001, + 150.19980000000001, + 503.71699999999998, + 161.38470000000001, + 10.1, + 0, + 159.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 161.8998, + 122.6541, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 125.2094, + 161.8998, + 151.64109999999999, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "blocks" + ], + [ + 154.1156, + 161.8998, + 178.2748, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "added" + ], + [ + 180.83009999999999, + 161.8998, + 199.303, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "after" + ], + [ + 201.84819999999999, + 161.8998, + 207.4436, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 210.0393, + 161.8998, + 237.41030000000001, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "further" + ], + [ + 240.04640000000001, + 161.8998, + 272.00279999999998, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "confirm" + ], + [ + 274.55810000000002, + 161.8998, + 286.82960000000003, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 289.36470000000003, + 161.8998, + 322.42200000000003, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 325.06819999999999, + 161.8998, + 338.50119999999998, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "has" + ], + [ + 341.06659999999999, + 161.8998, + 376.33580000000001, + 173.0847, + 10.1, + 1, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "accepted" + ], + [ + 378.89109999999999, + 161.8998, + 384.48649999999998, + 173.0847, + 10.1, + 0, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 384.48649999999998, + 161.8998, + 387.01150000000001, + 173.0847, + 10.1, + 0, + 170.90000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 355.89980000000003, + 133.7312, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "As" + ], + [ + 137.00360000000001, + 355.89980000000003, + 155.4563, + 367.0847, + 10.1, + 0, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "such" + ], + [ + 155.5068, + 355.89980000000003, + 158.0318, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 161.20320000000001, + 355.89980000000003, + 173.57570000000001, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 176.79759999999999, + 355.89980000000003, + 223.3586, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "verification" + ], + [ + 226.61080000000001, + 355.89980000000003, + 233.3374, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 236.60980000000001, + 355.89980000000003, + 266.88959999999997, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "reliable" + ], + [ + 270.11149999999998, + 355.89980000000003, + 278.54500000000002, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 281.71640000000002, + 355.89980000000003, + 299.66410000000002, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "long" + ], + [ + 302.91629999999998, + 355.89980000000003, + 311.34980000000002, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 314.62220000000002, + 355.89980000000003, + 340.92259999999999, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "honest" + ], + [ + 344.2253, + 355.89980000000003, + 367.75830000000002, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 370.92970000000003, + 355.89980000000003, + 399.63389999999998, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "control" + ], + [ + 402.8356, + 355.89980000000003, + 415.1071, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 418.43000000000001, + 355.89980000000003, + 451.4873, + 367.0847, + 10.1, + 0, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 451.5378, + 355.89980000000003, + 454.06279999999998, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 457.23419999999999, + 355.89980000000003, + 470.13189999999997, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "but" + ], + [ + 473.33359999999999, + 355.89980000000003, + 480.06020000000001, + 367.0847, + 10.1, + 1, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 483.33260000000001, + 355.89980000000003, + 504.10829999999999, + 367.0847, + 10.1, + 0, + 364.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "more" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 367.59980000000002, + 150.7826, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "vulnerable" + ], + [ + 155.7013, + 367.59980000000002, + 161.8623, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "if" + ], + [ + 166.7911, + 367.59980000000002, + 179.0626, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 184.0823, + 367.59980000000002, + 217.1396, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 222.08860000000001, + 367.59980000000002, + 228.8152, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 233.68340000000001, + 367.59980000000002, + 286.45589999999999, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "overpowered" + ], + [ + 291.4049, + 367.59980000000002, + 301.55540000000002, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 306.40339999999998, + 367.59980000000002, + 315.95800000000003, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "an" + ], + [ + 320.90699999999998, + 367.59980000000002, + 352.8836, + 378.78469999999999, + 10.1, + 0, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "attacker" + ], + [ + 352.31799999999998, + 367.59980000000002, + 354.84300000000002, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 364.50869999999998, + 367.59980000000002, + 389.18299999999999, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "While" + ], + [ + 394.10169999999999, + 367.59980000000002, + 427.15899999999999, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 432.108, + 367.59980000000002, + 455.74200000000002, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 460.61020000000002, + 367.59980000000002, + 474.5684, + 378.78469999999999, + 10.1, + 1, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "can" + ], + [ + 479.51740000000001, + 367.59980000000002, + 503.67660000000001, + 378.78469999999999, + 10.1, + 0, + 376.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "verify" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 379.19979999999998, + 156.43860000000001, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 161.20580000000001, + 379.19979999999998, + 172.97229999999999, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 177.81020000000001, + 379.19979999999998, + 222.74510000000001, + 390.38470000000001, + 10.1, + 0, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "themselves" + ], + [ + 222.7148, + 379.19979999999998, + 225.2398, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 230.108, + 379.19979999999998, + 242.37950000000001, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 247.19720000000001, + 379.19979999999998, + 288.14260000000002, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "simplified" + ], + [ + 292.99059999999997, + 379.19979999999998, + 323.24009999999998, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "method" + ], + [ + 328.1891, + 379.19979999999998, + 342.14729999999997, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "can" + ], + [ + 346.99529999999999, + 379.19979999999998, + 356.46910000000003, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 361.38780000000003, + 379.19979999999998, + 387.14280000000002, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "fooled" + ], + [ + 391.99079999999998, + 379.19979999999998, + 402.1413, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "by" + ], + [ + 406.88830000000002, + 379.19979999999998, + 416.44290000000001, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "an" + ], + [ + 421.29090000000002, + 379.19979999999998, + 458.93360000000001, + 390.38470000000001, + 10.1, + 1, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "attacker's" + ], + [ + 463.80180000000001, + 379.19979999999998, + 504.17149999999998, + 390.38470000000001, + 10.1, + 0, + 388.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "fabricated" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 390.89980000000003, + 156.43860000000001, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 159.71100000000001, + 390.89980000000003, + 171.37649999999999, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 174.70949999999999, + 390.89980000000003, + 183.143, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 186.41540000000001, + 390.89980000000003, + 204.3631, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "long" + ], + [ + 207.61529999999999, + 390.89980000000003, + 216.0488, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "as" + ], + [ + 219.3212, + 390.89980000000003, + 231.69370000000001, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 235.01660000000001, + 390.89980000000003, + 266.8922, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "attacker" + ], + [ + 270.22519999999997, + 390.89980000000003, + 284.18340000000001, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "can" + ], + [ + 287.53660000000002, + 390.89980000000003, + 322.3109, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "continue" + ], + [ + 325.53280000000001, + 390.89980000000003, + 333.38049999999998, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 336.7337, + 390.89980000000003, + 379.91120000000001, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "overpower" + ], + [ + 383.14319999999998, + 390.89980000000003, + 395.51569999999998, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 398.83859999999999, + 390.89980000000003, + 431.89589999999998, + 402.0847, + 10.1, + 0, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 431.94639999999998, + 390.89980000000003, + 434.47140000000002, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 440.94549999999998, + 390.89980000000003, + 457.82260000000002, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "One" + ], + [ + 461.04450000000003, + 390.89980000000003, + 493.1019, + 402.0847, + 10.1, + 1, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "strategy" + ], + [ + 496.35410000000002, + 390.89980000000003, + 504.20179999999999, + 402.0847, + 10.1, + 0, + 399.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 402.49979999999999, + 136.20830000000001, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "protect" + ], + [ + 140.01599999999999, + 402.49979999999999, + 168.51820000000001, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "against" + ], + [ + 172.32589999999999, + 402.49979999999999, + 186.95070000000001, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "this" + ], + [ + 190.72810000000001, + 402.49979999999999, + 215.97810000000001, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "would" + ], + [ + 219.7252, + 402.49979999999999, + 229.30000000000001, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 233.12790000000001, + 402.49979999999999, + 240.87459999999999, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 244.7227, + 402.49979999999999, + 270.53829999999999, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "accept" + ], + [ + 274.346, + 402.49979999999999, + 296.18220000000002, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "alerts" + ], + [ + 299.95960000000002, + 402.49979999999999, + 319.61419999999998, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "from" + ], + [ + 323.37139999999999, + 402.49979999999999, + 356.42869999999999, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "network" + ], + [ + 360.27679999999998, + 402.49979999999999, + 383.91079999999999, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 387.5872, + 402.49979999999999, + 409.4436, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "when" + ], + [ + 413.29169999999999, + 402.49979999999999, + 430.64350000000002, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "they" + ], + [ + 434.49160000000001, + 402.49979999999999, + 458.6003, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "detect" + ], + [ + 462.40800000000002, + 402.49979999999999, + 471.86160000000001, + 413.68470000000002, + 10.1, + 1, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "an" + ], + [ + 475.7097, + 402.49979999999999, + 503.65640000000002, + 413.68470000000002, + 10.1, + 0, + 411.5, + 0, + 0, + 0, + 0, + 0, + 1, + "invalid" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 414.19979999999998, + 130.5523, + 425.38470000000001, + 10.1, + 0, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 130.6028, + 414.19979999999998, + 133.12780000000001, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 137.99600000000001, + 414.19979999999998, + 180.04230000000001, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "prompting" + ], + [ + 184.9913, + 414.19979999999998, + 197.3638, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 202.2825, + 414.19979999999998, + 224.81559999999999, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "user's" + ], + [ + 229.78479999999999, + 414.19979999999998, + 264.56920000000002, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "software" + ], + [ + 269.48790000000002, + 414.19979999999998, + 277.3356, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 282.28460000000001, + 414.19979999999998, + 322.03820000000002, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "download" + ], + [ + 327.08819999999997, + 414.19979999999998, + 339.35969999999998, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 344.27839999999998, + 414.19979999999998, + 358.27699999999999, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "full" + ], + [ + 363.2765, + 414.19979999999998, + 385.62779999999998, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "block" + ], + [ + 390.67779999999999, + 414.19979999999998, + 405.2319, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 410.18090000000001, + 414.19979999999998, + 437.54180000000002, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "alerted" + ], + [ + 442.59179999999998, + 414.19979999999998, + 490.82940000000002, + 425.38470000000001, + 10.1, + 1, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 495.79860000000002, + 414.19979999999998, + 503.5453, + 425.38470000000001, + 10.1, + 0, + 423.19999999999999, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 425.7998, + 140.1574, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "confirm" + ], + [ + 143.10659999999999, + 425.7998, + 155.47909999999999, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 158.499, + 425.7998, + 213.54400000000001, + 436.98469999999998, + 10.1, + 0, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "inconsistency" + ], + [ + 212.79660000000001, + 425.7998, + 215.32159999999999, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 221.39169999999999, + 425.7998, + 265.72059999999999, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "Businesses" + ], + [ + 268.791, + 425.7998, + 283.89049999999997, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 286.99119999999999, + 425.7998, + 316.07920000000001, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "receive" + ], + [ + 319.09910000000002, + 425.7998, + 352.81290000000001, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "frequent" + ], + [ + 355.81259999999997, + 425.7998, + 394.54610000000002, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "payments" + ], + [ + 397.61649999999997, + 425.7998, + 413.31189999999998, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "will" + ], + [ + 416.3116, + 425.7998, + 452.26760000000002, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "probably" + ], + [ + 455.21679999999998, + 425.7998, + 470.40719999999999, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "still" + ], + [ + 473.40690000000001, + 425.7998, + 493.11200000000002, + 436.98469999999998, + 10.1, + 1, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "want" + ], + [ + 496.11169999999998, + 425.7998, + 503.85840000000002, + 436.98469999999998, + 10.1, + 0, + 434.80000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 437.49979999999999, + 121.5532, + 448.68470000000002, + 10.1, + 1, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "run" + ], + [ + 124.10850000000001, + 437.49979999999999, + 142.57130000000001, + 448.68470000000002, + 10.1, + 1, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "their" + ], + [ + 145.20740000000001, + 437.49979999999999, + 162.5592, + 448.68470000000002, + 10.1, + 1, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "own" + ], + [ + 165.11449999999999, + 437.49979999999999, + 188.64750000000001, + 448.68470000000002, + 10.1, + 1, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "nodes" + ], + [ + 191.21289999999999, + 437.49979999999999, + 202.9794, + 448.68470000000002, + 10.1, + 1, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 205.52459999999999, + 437.49979999999999, + 226.19929999999999, + 448.68470000000002, + 10.1, + 1, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "more" + ], + [ + 228.8253, + 437.49979999999999, + 278.13350000000003, + 448.68470000000002, + 10.1, + 1, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "independent" + ], + [ + 280.72919999999999, + 437.49979999999999, + 312.68560000000002, + 448.68470000000002, + 10.1, + 1, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "security" + ], + [ + 315.23079999999999, + 437.49979999999999, + 329.78489999999999, + 448.68470000000002, + 10.1, + 1, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 332.33010000000002, + 437.49979999999999, + 362.59980000000002, + 448.68470000000002, + 10.1, + 1, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "quicker" + ], + [ + 365.14499999999998, + 437.49979999999999, + 411.70600000000002, + 448.68470000000002, + 10.1, + 0, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "verification" + ], + [ + 411.75650000000002, + 437.49979999999999, + 414.28149999999999, + 448.68470000000002, + 10.1, + 0, + 446.5, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 463.76280000000003, + 114.70099999999999, + 477.58749999999998, + 11.5, + 0, + 475.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "9" + ], + [ + 114.70099999999999, + 463.76280000000003, + 117.8865, + 477.58749999999998, + 11.5, + 1, + 475.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "." + ], + [ + 133.30000000000001, + 463.76280000000003, + 199.11449999999999, + 477.58749999999998, + 11.5, + 1, + 475.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "Combining" + ], + [ + 202.40350000000001, + 463.76280000000003, + 224.9665, + 477.58749999999998, + 11.5, + 1, + 475.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "and" + ], + [ + 228.31299999999999, + 463.76280000000003, + 280.93700000000001, + 477.58749999999998, + 11.5, + 1, + 475.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "Splitting" + ], + [ + 284.226, + 463.76280000000003, + 318.43849999999998, + 477.58749999999998, + 11.5, + 0, + 475.10000000000002, + 0, + 0, + 0, + 0, + 0, + 0, + "Value" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 485.2998, + 146.24770000000001, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "Although" + ], + [ + 150.60079999999999, + 485.2998, + 156.1962, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 160.59979999999999, + 485.2998, + 185.74879999999999, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "would" + ], + [ + 190.1019, + 485.2998, + 199.67670000000001, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 204.00960000000001, + 485.2998, + 237.077, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "possible" + ], + [ + 241.40989999999999, + 485.2998, + 249.2576, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 253.61070000000001, + 485.2998, + 280.48680000000002, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "handle" + ], + [ + 284.92070000000001, + 485.2998, + 306.25189999999998, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "coins" + ], + [ + 310.52420000000001, + 485.2998, + 359.36779999999999, + 496.48469999999998, + 10.1, + 0, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "individually" + ], + [ + 358.72140000000002, + 485.2998, + 361.24639999999999, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 365.52879999999999, + 485.2998, + 371.12419999999997, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "it" + ], + [ + 375.52780000000001, + 485.2998, + 400.67680000000001, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "would" + ], + [ + 405.0299, + 485.2998, + 414.60469999999998, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 418.93759999999997, + 485.2998, + 456.48939999999999, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "unwieldy" + ], + [ + 460.84249999999997, + 485.2998, + 468.6902, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 473.04329999999999, + 485.2998, + 494.91989999999998, + 496.48469999999998, + 10.1, + 1, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "make" + ], + [ + 499.25279999999998, + 485.2998, + 503.72710000000001, + 496.48469999999998, + 10.1, + 0, + 494.30000000000001, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 496.89980000000003, + 141.1876, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "separate" + ], + [ + 145.90430000000001, + 496.89980000000003, + 190.26349999999999, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 195.01050000000001, + 496.89980000000003, + 206.77699999999999, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 211.51390000000001, + 496.89980000000003, + 233.97630000000001, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "every" + ], + [ + 238.72329999999999, + 496.89980000000003, + 255.52969999999999, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "cent" + ], + [ + 260.22620000000001, + 496.89980000000003, + 268.07389999999998, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "in" + ], + [ + 272.82089999999999, + 496.89980000000003, + 277.29520000000002, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 282.01190000000003, + 496.89980000000003, + 312.88760000000002, + 508.0847, + 10.1, + 0, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "transfer" + ], + [ + 312.322, + 496.89980000000003, + 314.84699999999998, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 324.11880000000002, + 496.89980000000003, + 334.57229999999998, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "To" + ], + [ + 339.3193, + 496.89980000000003, + 361.81200000000001, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "allow" + ], + [ + 366.51859999999999, + 496.89980000000003, + 388.39519999999999, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "value" + ], + [ + 393.11189999999999, + 496.89980000000003, + 400.95960000000002, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 405.70659999999998, + 496.89980000000003, + 415.18040000000002, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 419.89710000000002, + 496.89980000000003, + 437.28930000000003, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "split" + ], + [ + 442.08679999999998, + 496.89980000000003, + 456.64089999999999, + 508.0847, + 10.1, + 1, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 461.3879, + 496.89980000000003, + 501.14150000000001, + 508.0847, + 10.1, + 0, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "combined" + ], + [ + 501.19200000000001, + 496.89980000000003, + 503.71699999999998, + 508.0847, + 10.1, + 0, + 505.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 508.59980000000002, + 156.43860000000001, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 160.40790000000001, + 508.59980000000002, + 190.0615, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "contain" + ], + [ + 194.11160000000001, + 508.59980000000002, + 227.6739, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "multiple" + ], + [ + 231.7038, + 508.59980000000002, + 256.42860000000002, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "inputs" + ], + [ + 260.39789999999999, + 508.59980000000002, + 274.952, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 279.00209999999998, + 508.59980000000002, + 308.72640000000001, + 519.78470000000004, + 10.1, + 0, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "outputs" + ], + [ + 308.6961, + 508.59980000000002, + 311.22109999999998, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 319.20010000000002, + 508.59980000000002, + 357.8528, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "Normally" + ], + [ + 361.90289999999999, + 508.59980000000002, + 382.08269999999999, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "there" + ], + [ + 386.01159999999999, + 508.59980000000002, + 401.70699999999999, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "will" + ], + [ + 405.70659999999998, + 508.59980000000002, + 415.28140000000002, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 419.21030000000002, + 508.59980000000002, + 442.17770000000002, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "either" + ], + [ + 446.21769999999998, + 508.59980000000002, + 450.69200000000001, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 454.72190000000001, + 508.59980000000002, + 478.79020000000003, + 519.78470000000004, + 10.1, + 1, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "single" + ], + [ + 482.82010000000002, + 508.59980000000002, + 503.51499999999999, + 519.78470000000004, + 10.1, + 0, + 517.60000000000002, + 0, + 0, + 0, + 0, + 0, + 1, + "input" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 520.19979999999998, + 127.7546, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "from" + ], + [ + 130.3099, + 520.19979999999998, + 134.7842, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 137.4102, + 520.19979999999998, + 160.7816, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "larger" + ], + [ + 163.4177, + 520.19979999999998, + 198.1516, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "previous" + ], + [ + 200.71700000000001, + 520.19979999999998, + 244.9752, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 247.62139999999999, + 520.19979999999998, + 256.08519999999999, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "or" + ], + [ + 258.62029999999999, + 520.19979999999998, + 292.28359999999998, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "multiple" + ], + [ + 294.90960000000001, + 520.19979999999998, + 319.53339999999997, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "inputs" + ], + [ + 322.10890000000001, + 520.19979999999998, + 365.2561, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "combining" + ], + [ + 367.90230000000003, + 520.19979999999998, + 397.66699999999997, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "smaller" + ], + [ + 400.2122, + 520.19979999999998, + 434.44110000000001, + 531.38469999999995, + 10.1, + 0, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "amounts" + ], + [ + 434.41079999999999, + 520.19979999999998, + 436.93579999999997, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 439.60219999999998, + 520.19979999999998, + 454.15629999999999, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 456.71159999999998, + 520.19979999999998, + 464.01389999999998, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "at" + ], + [ + 466.6096, + 520.19979999999998, + 486.20359999999999, + 531.38469999999995, + 10.1, + 1, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "most" + ], + [ + 488.79930000000002, + 520.19979999999998, + 503.94929999999999, + 531.38469999999995, + 10.1, + 0, + 529.20000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "two" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 531.89980000000003, + 137.82429999999999, + 543.0847, + 10.1, + 0, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "outputs" + ], + [ + 137.79400000000001, + 531.89980000000003, + 140.5917, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + ":" + ], + [ + 143.19749999999999, + 531.89980000000003, + 157.77180000000001, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "one" + ], + [ + 160.30690000000001, + 531.89980000000003, + 172.07339999999999, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "for" + ], + [ + 174.61859999999999, + 531.89980000000003, + 186.99109999999999, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 189.52619999999999, + 531.89980000000003, + 224.33080000000001, + 543.0847, + 10.1, + 0, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "payment" + ], + [ + 224.33080000000001, + 531.89980000000003, + 226.85579999999999, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 229.34039999999999, + 531.89980000000003, + 243.89449999999999, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 246.44980000000001, + 531.89980000000003, + 261.02409999999998, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "one" + ], + [ + 263.55919999999998, + 531.89980000000003, + 300.61610000000002, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "returning" + ], + [ + 303.17140000000001, + 531.89980000000003, + 315.44290000000001, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 318.06889999999999, + 531.89980000000003, + 346.65190000000001, + 543.0847, + 10.1, + 0, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "change" + ], + [ + 346.68220000000002, + 531.89980000000003, + 349.2072, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 351.6918, + 531.89980000000003, + 357.8528, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "if" + ], + [ + 360.39800000000002, + 531.89980000000003, + 375.05309999999997, + 543.0847, + 10.1, + 0, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "any" + ], + [ + 374.3057, + 531.89980000000003, + 376.83069999999998, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 379.40620000000001, + 531.89980000000003, + 398.4649, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "back" + ], + [ + 401.02019999999999, + 531.89980000000003, + 408.76690000000002, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 411.41309999999999, + 531.89980000000003, + 423.68459999999999, + 543.0847, + 10.1, + 1, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 426.21969999999999, + 531.89980000000003, + 452.5908, + 543.0847, + 10.1, + 0, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "sender" + ], + [ + 452.02519999999998, + 531.89980000000003, + 454.55020000000002, + 543.0847, + 10.1, + 0, + 540.89999999999998, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 122.5, + 632.99980000000005, + 128.70140000000001, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "It" + ], + [ + 131.3981, + 632.99980000000005, + 158.2439, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "should" + ], + [ + 160.99109999999999, + 632.99980000000005, + 170.4649, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "be" + ], + [ + 173.18180000000001, + 632.99980000000005, + 195.53309999999999, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "noted" + ], + [ + 198.28030000000001, + 632.99980000000005, + 213.48079999999999, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "that" + ], + [ + 216.07650000000001, + 632.99980000000005, + 245.28569999999999, + 644.18470000000002, + 10.1, + 0, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "fan-out" + ], + [ + 245.28569999999999, + 632.99980000000005, + 247.8107, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 250.47710000000001, + 632.99980000000005, + 275.16149999999999, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "where" + ], + [ + 277.88850000000002, + 632.99980000000005, + 282.36279999999999, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 284.98880000000003, + 632.99980000000005, + 329.34800000000001, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction" + ], + [ + 331.99419999999998, + 632.99980000000005, + 365.13229999999999, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "depends" + ], + [ + 367.7987, + 632.99980000000005, + 377.94920000000002, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 380.59539999999998, + 632.99980000000005, + 409.20870000000002, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "several" + ], + [ + 411.90539999999999, + 632.99980000000005, + 460.14299999999997, + 644.18470000000002, + 10.1, + 0, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 460.11270000000002, + 632.99980000000005, + 462.6377, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 465.30410000000001, + 632.99980000000005, + 479.85820000000001, + 644.18470000000002, + 10.1, + 1, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "and" + ], + [ + 482.60539999999997, + 632.99980000000005, + 503.87599999999998, + 644.18470000000002, + 10.1, + 0, + 642, + 0, + 0, + 0, + 0, + 0, + 1, + "those" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 644.69979999999998, + 156.43860000000001, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "transactions" + ], + [ + 159.81200000000001, + 644.69979999999998, + 188.86969999999999, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "depend" + ], + [ + 192.32390000000001, + 644.69979999999998, + 202.4744, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "on" + ], + [ + 205.82759999999999, + 644.69979999999998, + 228.2799, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "many" + ], + [ + 231.63310000000001, + 644.69979999999998, + 252.40880000000001, + 655.88469999999995, + 10.1, + 0, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "more" + ], + [ + 252.4391, + 644.69979999999998, + 254.9641, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "," + ], + [ + 258.33749999999998, + 644.69979999999998, + 265.0641, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 268.4375, + 644.69979999999998, + 281.33519999999999, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "not" + ], + [ + 284.7389, + 644.69979999999998, + 289.21319999999997, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 292.53609999999998, + 644.69979999999998, + 326.1893, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "problem" + ], + [ + 329.64350000000002, + 644.69979999999998, + 346.9246, + 655.88469999999995, + 10.1, + 0, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "here" + ], + [ + 346.95490000000001, + 644.69979999999998, + 349.47989999999999, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ], + [ + 356.05500000000001, + 644.69979999999998, + 379.63850000000002, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "There" + ], + [ + 383.06240000000003, + 644.69979999999998, + 389.78899999999999, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "is" + ], + [ + 393.16239999999999, + 644.69979999999998, + 415.53390000000002, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "never" + ], + [ + 418.96789999999999, + 644.69979999999998, + 431.23939999999999, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "the" + ], + [ + 434.66329999999999, + 644.69979999999998, + 453.72199999999998, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "need" + ], + [ + 457.0752, + 644.69979999999998, + 464.92290000000003, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "to" + ], + [ + 468.37709999999998, + 644.69979999999998, + 495.7885, + 655.88469999999995, + 10.1, + 1, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "extract" + ], + [ + 499.19220000000001, + 644.69979999999998, + 503.66649999999998, + 655.88469999999995, + 10.1, + 0, + 653.70000000000005, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ] + ] + ], + [ + [ + [ + 108.09999999999999, + 656.2998, + 145.1771, + 667.48469999999998, + 10.1, + 1, + 665.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "complete" + ], + [ + 147.7122, + 656.2998, + 190.8897, + 667.48469999999998, + 10.1, + 1, + 665.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "standalone" + ], + [ + 193.4248, + 656.2998, + 213.07939999999999, + 667.48469999999998, + 10.1, + 1, + 665.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "copy" + ], + [ + 215.62459999999999, + 656.2998, + 224.08840000000001, + 667.48469999999998, + 10.1, + 1, + 665.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "of" + ], + [ + 226.5326, + 656.2998, + 231.0069, + 667.48469999999998, + 10.1, + 1, + 665.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "a" + ], + [ + 233.542, + 656.2998, + 283.57740000000001, + 667.48469999999998, + 10.1, + 1, + 665.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "transaction's" + ], + [ + 286.15289999999999, + 656.2998, + 314.30160000000001, + 667.48469999999998, + 10.1, + 0, + 665.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "history" + ], + [ + 313.55419999999998, + 656.2998, + 316.07920000000001, + 667.48469999999998, + 10.1, + 0, + 665.29999999999995, + 0, + 0, + 0, + 0, + 0, + 1, + "." + ] + ] + ], + [ + [ + [ + 310.80000000000001, + 708.99980000000005, + 315.85000000000002, + 720.18470000000002, + 10.1, + 0, + 718, + 0, + 0, + 0, + 0, + 0, + 1, + "5" + ] + ] + ], + [ + [ + [ + 278.30000000000001, + 557.45360000000005, + 314.47230000000002, + 565.16219999999998, + 6.9000000000000004, + 0, + 563.70000000000005, + 0, + 0, + 0, + 0, + 0, + 2, + "Transaction" + ] + ] + ], + [ + [ + [ + 289.30000000000001, + 589.75360000000001, + 295.15769999999998, + 597.46220000000005, + 6.9000000000000004, + 0, + 596, + 0, + 0, + 0, + 0, + 0, + 2, + "In" + ] + ] + ], + [ + [ + [ + 289.30000000000001, + 606.35360000000003, + 291.22019999999998, + 614.06219999999996, + 6.9000000000000004, + 0, + 612.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 291.30340000000001, + 606.35360000000003, + 293.22359999999998, + 614.06219999999996, + 6.9000000000000004, + 0, + 612.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 293.20280000000002, + 606.35360000000003, + 295.12310000000002, + 614.06219999999996, + 6.9000000000000004, + 0, + 612.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ] + ] + ], + [ + [ + [ + 289.30000000000001, + 573.05359999999996, + 295.15769999999998, + 580.76220000000001, + 6.9000000000000004, + 1, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "In" + ], + [ + 314.19999999999999, + 573.05359999999996, + 325.32619999999997, + 580.76220000000001, + 6.9000000000000004, + 0, + 579.29999999999995, + 0, + 0, + 0, + 0, + 0, + 2, + "Out" + ] + ] + ], + [ + [ + [ + 317.10000000000002, + 589.75360000000001, + 319.02019999999999, + 597.46220000000005, + 6.9000000000000004, + 0, + 596, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 318.99939999999998, + 589.75360000000001, + 320.91969999999998, + 597.46220000000005, + 6.9000000000000004, + 0, + 596, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ], + [ + 321.00279999999998, + 589.75360000000001, + 322.92309999999998, + 597.46220000000005, + 6.9000000000000004, + 0, + 596, + 0, + 0, + 0, + 0, + 0, + 2, + "." + ] + ] + ], + [ + [ + [ + 254.09999999999999, + 272.17259999999999, + 278.64249999999998, + 280.1046, + 7.0999999999999996, + 0, + 278.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash01" + ] + ] + ], + [ + [ + [ + 304.39999999999998, + 306.57260000000002, + 325.04379999999998, + 314.50459999999998, + 7.0999999999999996, + 1, + 313, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash2" + ], + [ + 344.10000000000002, + 306.57260000000002, + 364.64440000000002, + 314.50459999999998, + 7.0999999999999996, + 0, + 313, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash3" + ] + ] + ], + [ + [ + [ + 322.19999999999999, + 272.17259999999999, + 346.74250000000001, + 280.1046, + 7.0999999999999996, + 0, + 278.60000000000002, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash23" + ] + ] + ], + [ + [ + [ + 260.60000000000002, + 205.9726, + 278.04820000000001, + 213.90459999999999, + 7.0999999999999996, + 1, + 212.40000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ], + [ + 280.10050000000001, + 205.9726, + 303.45710000000003, + 213.90459999999999, + 7.0999999999999996, + 0, + 212.40000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Header" + ] + ] + ], + [ + [ + [ + 280.10000000000002, + 237.87260000000001, + 301.44690000000003, + 245.80459999999999, + 7.0999999999999996, + 1, + 244.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Merkle" + ], + [ + 303.39980000000003, + 237.87260000000001, + 318.46190000000001, + 245.80459999999999, + 7.0999999999999996, + 0, + 244.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Root" + ] + ] + ], + [ + [ + [ + 272.39999999999998, + 220.77260000000001, + 287.05020000000002, + 228.7046, + 7.0999999999999996, + 1, + 227.19999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Prev" + ], + [ + 289.00310000000002, + 220.77260000000001, + 305.64879999999999, + 228.7046, + 7.0999999999999996, + 1, + 227.19999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ], + [ + 323.89999999999998, + 220.77260000000001, + 344.54379999999998, + 228.7046, + 7.0999999999999996, + 0, + 227.19999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Nonce" + ] + ] + ], + [ + [ + [ + 141, + 205.9726, + 158.44820000000001, + 213.90459999999999, + 7.0999999999999996, + 1, + 212.40000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ], + [ + 160.50049999999999, + 205.9726, + 183.8571, + 213.90459999999999, + 7.0999999999999996, + 0, + 212.40000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Header" + ] + ] + ], + [ + [ + [ + 159.90000000000001, + 237.87260000000001, + 181.24690000000001, + 245.80459999999999, + 7.0999999999999996, + 1, + 244.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Merkle" + ], + [ + 183.19980000000001, + 237.87260000000001, + 198.2619, + 245.80459999999999, + 7.0999999999999996, + 0, + 244.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Root" + ] + ] + ], + [ + [ + [ + 152.19999999999999, + 220.77260000000001, + 166.8502, + 228.7046, + 7.0999999999999996, + 1, + 227.19999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Prev" + ], + [ + 168.8031, + 220.77260000000001, + 185.44880000000001, + 228.7046, + 7.0999999999999996, + 1, + 227.19999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ], + [ + 203.69999999999999, + 220.77260000000001, + 224.24440000000001, + 228.7046, + 7.0999999999999996, + 0, + 227.19999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Nonce" + ] + ] + ], + [ + [ + [ + 379.60000000000002, + 205.9726, + 397.04820000000001, + 213.90459999999999, + 7.0999999999999996, + 1, + 212.40000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Block" + ], + [ + 399.10050000000001, + 205.9726, + 422.45710000000003, + 213.90459999999999, + 7.0999999999999996, + 0, + 212.40000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Header" + ] + ] + ], + [ + [ + [ + 399.69999999999999, + 237.87260000000001, + 421.04689999999999, + 245.80459999999999, + 7.0999999999999996, + 1, + 244.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Merkle" + ], + [ + 422.99979999999999, + 237.87260000000001, + 438.06189999999998, + 245.80459999999999, + 7.0999999999999996, + 0, + 244.30000000000001, + 0, + 0, + 0, + 0, + 0, + 2, + "Root" + ] + ] + ], + [ + [ + [ + 392, + 220.77260000000001, + 406.65019999999998, + 228.7046, + 7.0999999999999996, + 1, + 227.19999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Prev" + ], + [ + 408.60309999999998, + 220.77260000000001, + 425.24880000000002, + 228.7046, + 7.0999999999999996, + 1, + 227.19999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Hash" + ], + [ + 443.5, + 220.77260000000001, + 464.1438, + 228.7046, + 7.0999999999999996, + 0, + 227.19999999999999, + 0, + 0, + 0, + 0, + 0, + 2, + "Nonce" + ] + ] + ], + [ + [ + [ + 362.39999999999998, + 287.57260000000002, + 383.74689999999998, + 295.50459999999998, + 7.0999999999999996, + 1, + 294, + 0, + 0, + 0, + 0, + 0, + 2, + "Merkle" + ], + [ + 385.69979999999998, + 287.57260000000002, + 408.34620000000001, + 295.50459999999998, + 7.0999999999999996, + 1, + 294, + 0, + 0, + 0, + 0, + 0, + 2, + "Branch" + ], + [ + 410.29910000000001, + 287.57260000000002, + 418.66460000000001, + 295.50459999999998, + 7.0999999999999996, + 1, + 294, + 0, + 0, + 0, + 0, + 0, + 2, + "for" + ], + [ + 420.60320000000002, + 287.57260000000002, + 432.15010000000001, + 295.50459999999998, + 7.0999999999999996, + 0, + 294, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx3" + ] + ] + ], + [ + [ + [ + 123.8, + 187.57259999999999, + 149.2586, + 195.50460000000001, + 7.0999999999999996, + 1, + 194, + 0, + 0, + 0, + 0, + 0, + 2, + "Longest" + ], + [ + 151.29669999999999, + 187.57259999999999, + 195.9504, + 195.50460000000001, + 7.0999999999999996, + 1, + 194, + 0, + 0, + 0, + 0, + 0, + 2, + "Proof-of-Work" + ], + [ + 198.0027, + 187.57259999999999, + 216.64400000000001, + 195.50460000000001, + 7.0999999999999996, + 0, + 194, + 0, + 0, + 0, + 0, + 0, + 2, + "Chain" + ] + ] + ], + [ + [ + [ + 351.19999999999999, + 334.9726, + 362.74689999999998, + 342.90460000000002, + 7.0999999999999996, + 0, + 341.39999999999998, + 0, + 0, + 0, + 0, + 0, + 2, + "Tx3" + ] + ] + ] + ] + ] + ] + ] + ] + ] + ], + "metadata" : { + "CreationDate" : "D:20090324113315-06'00'", + "Producer" : "OpenOffice.org 2.4", + "Language" : "en-GB", + "Creator" : "Writer" + }, + "totalPages" : 9 +} diff --git a/ZoteroTests/PDFWorkerControllerSpec.swift b/ZoteroTests/PDFWorkerControllerSpec.swift new file mode 100644 index 000000000..e8e9c5713 --- /dev/null +++ b/ZoteroTests/PDFWorkerControllerSpec.swift @@ -0,0 +1,146 @@ +// +// PDFWorkerControllerSpec.swift +// ZoteroTests +// +// Created by Miltiadis Vasilakis on 21/2/25. +// Copyright © 2025 Corporation for Digital Scholarship. All rights reserved. +// + +import UIKit +import WebKit + +@testable import Zotero + +import Nimble +import Quick +import RxSwift + +class WebViewProviderViewController: UIViewController { } + +extension WebViewProviderViewController: WebViewProvider { + func addWebView(configuration: WKWebViewConfiguration?) -> WKWebView { + let webView: WKWebView = configuration.flatMap({ WKWebView(frame: .zero, configuration: $0) }) ?? WKWebView() + webView.isHidden = true + view.insertSubview(webView, at: 0) + return webView + } +} + +final class PDFWorkerControllerSpec: QuickSpec { + override class func spec() { + var webViewProviderViewController: WebViewProviderViewController! + var pdfWorkerController: PDFWorkerController! + var disposeBag: DisposeBag! + + beforeSuite { + webViewProviderViewController = WebViewProviderViewController() + webViewProviderViewController.loadViewIfNeeded() + pdfWorkerController = PDFWorkerController() + pdfWorkerController.webViewProvider = webViewProviderViewController + disposeBag = DisposeBag() + } + + describe("a PDF Worker Controller") { + context("with a valid PDF URL") { + let fileΝame = "bitcoin" + let fileExtension = "pdf" + let contentType = "application/pdf" + let key = "aaaaaaaa" + let fileURL = Bundle(for: Self.self).url(forResource: fileΝame, withExtension: fileExtension)! + let data = try! Data(contentsOf: fileURL) + let libraryId = LibraryIdentifier.custom(.myLibrary) + let file = Files.attachmentFile(in: libraryId, key: key, filename: fileΝame, contentType: contentType) as! FileData + try! TestControllers.fileStorage.write(data, to: file, options: .atomic) + expect(TestControllers.fileStorage.has(file)).to(beTrue()) + + it("can extract recognizer data") { + let work = PDFWorkerController.PDFWork(file: file, kind: .recognizer) + var emittedUpdates: [PDFWorkerController.Update.Kind] = [] + + waitUntil(timeout: .seconds(10)) { completion in + pdfWorkerController.queue(work: work) { observable in + expect(observable).toNot(beNil()) + guard let observable else { return } + observable.subscribe(onNext: { update in + expect(update.work).to(equal(work)) + emittedUpdates.append(update.kind) + switch update.kind { + case .failed, .cancelled, .extractedRecognizerData, .extractedFullText: + completion() + + case .inProgress: + break + } + }) + .disposed(by: disposeBag) + } + } + + expect(emittedUpdates.count).toEventually(equal(2), timeout: .seconds(20)) + for (index, update) in emittedUpdates.enumerated() { + switch update { + case .inProgress: + expect(index).to(equal(0)) + + case .extractedRecognizerData(let data): + expect(index).to(equal(1)) + let recognizerDataURL = Bundle(for: Self.self).url(forResource: "bitcoin_pdf_recognizer_data", withExtension: "json")! + let recognizerData = try! Data(contentsOf: recognizerDataURL) + let recognizerJSONData = try! JSONSerialization.jsonObject(with: recognizerData, options: .allowFragments) as! [String: Any] + expect(data as? [String: AnyHashable]).to(equal(recognizerJSONData as! [String: AnyHashable])) + + default: + fail("unexpected update \(index): \(update)") + } + } + } + + it("can extract full text") { + let work = PDFWorkerController.PDFWork(file: file, kind: .fullText) + var emittedUpdates: [PDFWorkerController.Update.Kind] = [] + + waitUntil(timeout: .seconds(10)) { completion in + pdfWorkerController.queue(work: work) { observable in + expect(observable).toNot(beNil()) + guard let observable else { return } + observable.subscribe(onNext: { update in + expect(update.work).to(equal(work)) + emittedUpdates.append(update.kind) + switch update.kind { + case .failed, .cancelled, .extractedRecognizerData, .extractedFullText: + completion() + + case .inProgress: + break + } + }) + .disposed(by: disposeBag) + } + } + + expect(emittedUpdates.count).toEventually(equal(2), timeout: .seconds(20)) + for (index, update) in emittedUpdates.enumerated() { + switch update { + case .inProgress: + expect(index).to(equal(0)) + + case .extractedRecognizerData(let data): + expect(index).to(equal(1)) + let fullTextURL = Bundle(for: Self.self).url(forResource: "bitcoin_pdf_full_text", withExtension: "json")! + let fullTextData = try! Data(contentsOf: fullTextURL) + let fullTextJSONData = try! JSONSerialization.jsonObject(with: fullTextData, options: .allowFragments) as! [String: Any] + expect(data as? [String: AnyHashable]).to(equal(fullTextJSONData as! [String: AnyHashable])) + + default: + fail("unexpected update \(index): \(update)") + } + } + } + } + } + + afterSuite { + try? TestControllers.fileStorage.remove(Files.downloads) + } + } +} From 68f57cb2c6bab93909a4f533b84b89bc4d5432a5 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Fri, 21 Feb 2025 17:33:58 +0200 Subject: [PATCH 11/33] Update submodule pdf-worker --- pdf-worker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf-worker b/pdf-worker index 01901bf65..4e05314f3 160000 --- a/pdf-worker +++ b/pdf-worker @@ -1 +1 @@ -Subproject commit 01901bf65e12741e727df38eaaa24a67d4fc6a5e +Subproject commit 4e05314f39093f4cdf42b79833ca89ca0d871474 From 11a19aa541ff77ab4e0ed4c5e5863de15f4f8859 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Mon, 24 Feb 2025 15:09:02 +0200 Subject: [PATCH 12/33] Cancel all Recognizer tasks on user logout --- Zotero/Controllers/Controllers.swift | 2 ++ Zotero/Controllers/RecognizerController.swift | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Zotero/Controllers/Controllers.swift b/Zotero/Controllers/Controllers.swift index e4918e41c..46761cffe 100644 --- a/Zotero/Controllers/Controllers.swift +++ b/Zotero/Controllers/Controllers.swift @@ -274,6 +274,8 @@ final class Controllers { controllers?.remoteFileDownloader.stop() // Cancel all background uploads controllers?.backgroundUploadObserver.cancelAllUploads() + // Cancel all Recognizer Tasks + controllers?.recognizerController.cancellAllTasks() // Cancel all PDF workers controllers?.pdfWorkerController.cancellAllWorks() // Clear user controllers diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index c3a3521f8..bbc491bff 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -550,6 +550,33 @@ final class RecognizerController { } } + func cancel(task: RecognizerTask) { + cleanupTask(for: task) { observable in + DDLogInfo("RecognizerController: cancelled \(task)") + observable?.on(.next(Update(task: task, kind: .cancelled))) + } + } + + func cancellAllTasks() { + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { return } + DDLogInfo("RecognizerController: cancel all tasks") + // Immediatelly release all lookup web views. + let keys = lookupWebViewHandlersByRecognizerTask.keys + for key in keys { + guard let webView = lookupWebViewHandlersByRecognizerTask.removeValue(forKey: key)?.webViewHandler.webView else { continue } + DispatchQueue.main.async { + webView.removeFromSuperview() + } + } + // Then cancel actual tasks, and send cancelled event for each queued task. + let tasks = queue.keys + for task in tasks { + cancel(task: task) + } + } + } + private func cleanupTask(for task: RecognizerTask, completion: @escaping (_ observable: PublishSubject?) -> Void) { if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { cleanup(for: task, completion: completion) From 1f2f7b10355ca4c8411cde8831cf3bc0a366ba36 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Mon, 24 Feb 2025 17:40:19 +0200 Subject: [PATCH 13/33] Improve WebViewHandler --- Zotero/Controllers/IdentifierLookupController.swift | 10 ++-------- Zotero/Controllers/PDFWorkerController.swift | 11 ++--------- Zotero/Controllers/RecognizerController.swift | 11 ++--------- .../Web View Handling/WebViewHandler.swift | 8 +++++++- 4 files changed, 13 insertions(+), 27 deletions(-) diff --git a/Zotero/Controllers/IdentifierLookupController.swift b/Zotero/Controllers/IdentifierLookupController.swift index 88a085201..867f406a6 100644 --- a/Zotero/Controllers/IdentifierLookupController.swift +++ b/Zotero/Controllers/IdentifierLookupController.swift @@ -223,10 +223,7 @@ final class IdentifierLookupController { DDLogInfo("IdentifierLookupController: cancel all lookups") let keys = lookupWebViewHandlersByLookupSettings.keys for key in keys { - guard let webView = lookupWebViewHandlersByLookupSettings.removeValue(forKey: key)?.webViewHandler.webView else { continue } - inMainThread { - webView.removeFromSuperview() - } + lookupWebViewHandlersByLookupSettings.removeValue(forKey: key)?.webViewHandler.removeFromSuperviewAsynchronously() } remoteFileDownloader.stop() let lookupData = self.lookupData @@ -531,10 +528,7 @@ final class IdentifierLookupController { DDLogInfo("IdentifierLookupController: cleaned up lookup data") let keys = lookupWebViewHandlersByLookupSettings.keys for key in keys { - guard let webView = lookupWebViewHandlersByLookupSettings.removeValue(forKey: key)?.webViewHandler.webView else { continue } - DispatchQueue.main.async { - webView.removeFromSuperview() - } + lookupWebViewHandlersByLookupSettings.removeValue(forKey: key)?.webViewHandler.removeFromSuperviewAsynchronously() } completion(true) } diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index 4a3b7b807..f79e331e0 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -176,10 +176,7 @@ final class PDFWorkerController { // Immediatelly release all PDFWorker web views. let keys = pdfWorkerWebViewHandlersByPDFWork.keys for key in keys { - guard let webView = pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: key)?.webViewHandler.webView else { continue } - DispatchQueue.main.async { - webView.removeFromSuperview() - } + pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: key)?.webViewHandler.removeFromSuperviewAsynchronously() } // Then cancel actual works, and send cancelled event for each queued work. let works = queue.keys @@ -201,11 +198,7 @@ final class PDFWorkerController { func cleanup(for work: PDFWork, completion: @escaping (_ observable: PublishSubject?) -> Void) { let observable = queue.removeValue(forKey: work).flatMap({ $0.observable }) DDLogInfo("PDFWorkerController: cleaned up for \(work)") - if let webView = pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: work)?.webViewHandler.webView { - DispatchQueue.main.async { - webView.removeFromSuperview() - } - } + pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: work)?.webViewHandler.removeFromSuperviewAsynchronously() completion(observable) startWorkIfNeeded() } diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index bbc491bff..8faed7024 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -564,10 +564,7 @@ final class RecognizerController { // Immediatelly release all lookup web views. let keys = lookupWebViewHandlersByRecognizerTask.keys for key in keys { - guard let webView = lookupWebViewHandlersByRecognizerTask.removeValue(forKey: key)?.webViewHandler.webView else { continue } - DispatchQueue.main.async { - webView.removeFromSuperview() - } + lookupWebViewHandlersByRecognizerTask.removeValue(forKey: key)?.webViewHandler.removeFromSuperviewAsynchronously() } // Then cancel actual tasks, and send cancelled event for each queued task. let tasks = queue.keys @@ -593,11 +590,7 @@ final class RecognizerController { latestUpdates[libraryId] = libraryLatestUpdates } DDLogInfo("RecognizerController: \(task) - cleaned up") - if let webView = lookupWebViewHandlersByRecognizerTask.removeValue(forKey: task)?.webViewHandler.webView { - DispatchQueue.main.async { - webView.removeFromSuperview() - } - } + lookupWebViewHandlersByRecognizerTask.removeValue(forKey: task)?.webViewHandler.removeFromSuperviewAsynchronously() completion(observable) startRecognitionIfNeeded() } diff --git a/Zotero/Controllers/Web View Handling/WebViewHandler.swift b/Zotero/Controllers/Web View Handling/WebViewHandler.swift index 73bfbfd94..e9e246c5e 100644 --- a/Zotero/Controllers/Web View Handling/WebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/WebViewHandler.swift @@ -24,7 +24,7 @@ final class WebViewHandler: NSObject { private let session: URLSession - private(set) weak var webView: WKWebView? + private weak var webView: WKWebView? private var webDidLoad: ((SingleEvent<()>) -> Void)? var receivedMessageHandler: ((String, Any) -> Void)? // Cookies, User-Agent and Referrer from original website are stored and added to requests in `sendRequest(with:)`. @@ -143,6 +143,12 @@ final class WebViewHandler: NSObject { } } + func removeFromSuperviewAsynchronously() { + DispatchQueue.main.async { + self.webView?.removeFromSuperview() + } + } + // MARK: - HTTP Requests /// Sends HTTP request based on options. Sends back response with HTTP response to `webView`. From 18790c0e8a7fc80745458159072e2e045de040df Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Mon, 24 Feb 2025 15:31:41 +0200 Subject: [PATCH 14/33] Simplify PDFWorkerController --- Zotero/Controllers/PDFWorkerController.swift | 12 +++--------- Zotero/Controllers/RecognizerController.swift | 19 +++++++++++-------- ZoteroTests/PDFWorkerControllerSpec.swift | 8 ++++---- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index f79e331e0..bcd7360da 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -30,8 +30,7 @@ final class PDFWorkerController { case failed case cancelled case inProgress - case extractedRecognizerData(data: [String: Any]) - case extractedFullText(data: [String: Any]) + case extractedData(data: [String: Any]) } let work: PDFWork @@ -140,14 +139,9 @@ final class PDFWorkerController { switch result { case .success(let data): switch data { - case .recognizerData(let data): + case .recognizerData(let data), .fullText(let data): cleanupPDFWorker(for: work) { observable in - observable?.on(.next(Update(work: work, kind: .extractedRecognizerData(data: data)))) - } - - case .fullText(let data): - cleanupPDFWorker(for: work) { observable in - observable?.on(.next(Update(work: work, kind: .extractedFullText(data: data)))) + observable?.on(.next(Update(work: work, kind: .extractedData(data: data)))) } } diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index 8faed7024..0dcd37c4d 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -257,14 +257,17 @@ final class RecognizerController { case .inProgress: break - case .extractedRecognizerData(data: let data): - DDLogInfo("RecognizerController: \(task) - extracted recognizer data") - startRemoteRecognition(for: task, with: data) - - case .extractedFullText: - DDLogError("RecognizerController: \(task) - PDF worker error") - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.pdfWorkerError)))) + case .extractedData(let data): + switch update.work.kind { + case .recognizer: + DDLogInfo("RecognizerController: \(task) - extracted recognizer data") + startRemoteRecognition(for: task, with: data) + + case .fullText: + DDLogError("RecognizerController: \(task) - PDF worker error") + cleanupTask(for: task) { observable in + observable?.on(.next(Update(task: task, kind: .failed(.pdfWorkerError)))) + } } } } diff --git a/ZoteroTests/PDFWorkerControllerSpec.swift b/ZoteroTests/PDFWorkerControllerSpec.swift index e8e9c5713..11fd2437f 100644 --- a/ZoteroTests/PDFWorkerControllerSpec.swift +++ b/ZoteroTests/PDFWorkerControllerSpec.swift @@ -65,7 +65,7 @@ final class PDFWorkerControllerSpec: QuickSpec { expect(update.work).to(equal(work)) emittedUpdates.append(update.kind) switch update.kind { - case .failed, .cancelled, .extractedRecognizerData, .extractedFullText: + case .failed, .cancelled, .extractedData: completion() case .inProgress: @@ -82,7 +82,7 @@ final class PDFWorkerControllerSpec: QuickSpec { case .inProgress: expect(index).to(equal(0)) - case .extractedRecognizerData(let data): + case .extractedData(let data): expect(index).to(equal(1)) let recognizerDataURL = Bundle(for: Self.self).url(forResource: "bitcoin_pdf_recognizer_data", withExtension: "json")! let recognizerData = try! Data(contentsOf: recognizerDataURL) @@ -107,7 +107,7 @@ final class PDFWorkerControllerSpec: QuickSpec { expect(update.work).to(equal(work)) emittedUpdates.append(update.kind) switch update.kind { - case .failed, .cancelled, .extractedRecognizerData, .extractedFullText: + case .failed, .cancelled, .extractedData: completion() case .inProgress: @@ -124,7 +124,7 @@ final class PDFWorkerControllerSpec: QuickSpec { case .inProgress: expect(index).to(equal(0)) - case .extractedRecognizerData(let data): + case .extractedData(let data): expect(index).to(equal(1)) let fullTextURL = Bundle(for: Self.self).url(forResource: "bitcoin_pdf_full_text", withExtension: "json")! let fullTextData = try! Data(contentsOf: fullTextURL) From 4b7a3589f0b0fb4aa281c4b67bd23477d76accb7 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Mon, 24 Feb 2025 15:31:00 +0200 Subject: [PATCH 15/33] Improve code --- ZShare/ViewModels/ExtensionViewModel.swift | 2 +- Zotero.xcodeproj/project.pbxproj | 20 ++++++++++++---- .../API/Requests/RecognizerRequest.swift | 23 ++++++++++++++++++ .../Responses}/AuthorizeUploadResponse.swift | 0 .../API/Responses}/CollectionResponse.swift | 0 .../API/Responses}/DeletionsResponse.swift | 0 .../API/Responses}/GroupResponse.swift | 0 .../API/Responses}/ItemResponse.swift | 0 .../API/Responses}/KeyResponse.swift | 0 .../API/Responses}/LibraryResponse.swift | 0 .../API/Responses}/LinksResponse.swift | 0 .../API/Responses}/LoginResponse.swift | 0 .../Responses/RemoteRecognizerResponse.swift | 17 +++++++++++++ .../API/Responses}/RemoteStylesResponse.swift | 0 .../API/Responses}/SchemaResponse.swift | 0 .../API/Responses}/SearchesResponse.swift | 0 .../API/Responses}/SettingsResponse.swift | 0 .../API/Responses}/UpdatesResponse.swift | 0 Zotero/Controllers/PDFWorkerController.swift | 10 +++++--- Zotero/Controllers/RecognizerController.swift | 24 ------------------- 20 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 Zotero/Controllers/API/Requests/RecognizerRequest.swift rename Zotero/{Models/API => Controllers/API/Responses}/AuthorizeUploadResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/CollectionResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/DeletionsResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/GroupResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/ItemResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/KeyResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/LibraryResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/LinksResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/LoginResponse.swift (100%) create mode 100644 Zotero/Controllers/API/Responses/RemoteRecognizerResponse.swift rename Zotero/{Models/API => Controllers/API/Responses}/RemoteStylesResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/SchemaResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/SearchesResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/SettingsResponse.swift (100%) rename Zotero/{Models/API => Controllers/API/Responses}/UpdatesResponse.swift (100%) diff --git a/ZShare/ViewModels/ExtensionViewModel.swift b/ZShare/ViewModels/ExtensionViewModel.swift index d2eabc32e..5d7298dc6 100644 --- a/ZShare/ViewModels/ExtensionViewModel.swift +++ b/ZShare/ViewModels/ExtensionViewModel.swift @@ -538,7 +538,7 @@ final class ExtensionViewModel { } func recognize(file: FileData) { - recognizerController.queue(task: RecognizerController.RecognizerTask(file: file, kind: .simple)) { [weak self] observable in + recognizerController.queue(task: .init(file: file, kind: .simple)) { [weak self] observable in guard let self else { return } observable?.subscribe { [weak self] update in guard let self else { return } diff --git a/Zotero.xcodeproj/project.pbxproj b/Zotero.xcodeproj/project.pbxproj index 17d5a0917..c68fdbbe8 100644 --- a/Zotero.xcodeproj/project.pbxproj +++ b/Zotero.xcodeproj/project.pbxproj @@ -9,6 +9,10 @@ /* Begin PBXBuildFile section */ 6102D2B22D68ABF100505E6A /* bitcoin_pdf_recognizer_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 6102D2B12D68ABD900505E6A /* bitcoin_pdf_recognizer_data.json */; }; 6102D2B62D68B5E800505E6A /* bitcoin_pdf_full_text.json in Resources */ = {isa = PBXBuildFile; fileRef = 6102D2B52D68B5DE00505E6A /* bitcoin_pdf_full_text.json */; }; + 6102D2B82D6CCDD300505E6A /* RemoteRecognizerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6102D2B72D6CCDD300505E6A /* RemoteRecognizerResponse.swift */; }; + 6102D2B92D6CCDD300505E6A /* RemoteRecognizerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6102D2B72D6CCDD300505E6A /* RemoteRecognizerResponse.swift */; }; + 6102D2BC2D6CCF7600505E6A /* RecognizerRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6102D2BB2D6CCF7600505E6A /* RecognizerRequest.swift */; }; + 6102D2BD2D6CCF7600505E6A /* RecognizerRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6102D2BB2D6CCF7600505E6A /* RecognizerRequest.swift */; }; 61099E6E2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m in Sources */ = {isa = PBXBuildFile; fileRef = 61099E6D2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m */; }; 61099E6F2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m in Sources */ = {isa = PBXBuildFile; fileRef = 61099E6D2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m */; }; 61168F612D50D4B6005495E8 /* PDFWorkerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61168F602D50D4B6005495E8 /* PDFWorkerController.swift */; }; @@ -1308,6 +1312,8 @@ /* Begin PBXFileReference section */ 6102D2B12D68ABD900505E6A /* bitcoin_pdf_recognizer_data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitcoin_pdf_recognizer_data.json; sourceTree = ""; }; 6102D2B52D68B5DE00505E6A /* bitcoin_pdf_full_text.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitcoin_pdf_full_text.json; sourceTree = ""; }; + 6102D2B72D6CCDD300505E6A /* RemoteRecognizerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteRecognizerResponse.swift; sourceTree = ""; }; + 6102D2BB2D6CCF7600505E6A /* RecognizerRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecognizerRequest.swift; sourceTree = ""; }; 61099E6A2C91BAF300EDD92C /* Zotero-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Zotero-Bridging-Header.h"; sourceTree = ""; }; 61099E6B2C91BAF300EDD92C /* ZShare-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ZShare-Bridging-Header.h"; sourceTree = ""; }; 61099E6C2C91BAF300EDD92C /* NSDecimalNumber+Rounding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDecimalNumber+Rounding.h"; sourceTree = ""; }; @@ -2553,6 +2559,7 @@ isa = PBXGroup; children = ( B305648E23FC051E003304F2 /* Requests */, + B305654523FC051E003304F2 /* Responses */, B305648D23FC051E003304F2 /* Alamofire+RxSwift.swift */, B305648B23FC051E003304F2 /* ApiClient.swift */, B3DF9AD42747AB4F007933CB /* ApiEndpoint.swift */, @@ -2596,6 +2603,7 @@ B3A351DA2715761D002E597A /* WebDavPropfindRequest.swift */, B3A351DE271577D0002E597A /* WebDavTestWriteRequest.swift */, B3FC7430272181E500F55531 /* WebDavWriteRequest.swift */, + 6102D2BB2D6CCF7600505E6A /* RecognizerRequest.swift */, ); path = Requests; sourceTree = ""; @@ -2709,7 +2717,6 @@ B305652B23FC051E003304F2 /* Models */ = { isa = PBXGroup; children = ( - B305654523FC051E003304F2 /* API */, B305652D23FC051E003304F2 /* Database */, B305655523FC051E003304F2 /* Sync */, B324277025C82D6900567504 /* WebSocket */, @@ -2790,11 +2797,10 @@ path = Database; sourceTree = ""; }; - B305654523FC051E003304F2 /* API */ = { + B305654523FC051E003304F2 /* Responses */ = { isa = PBXGroup; children = ( B305654923FC051E003304F2 /* AuthorizeUploadResponse.swift */, - B3A2AEDD26565211004BF3A4 /* RemoteStylesResponse.swift */, B305654C23FC051E003304F2 /* CollectionResponse.swift */, B305654F23FC051E003304F2 /* DeletionsResponse.swift */, B305655023FC051E003304F2 /* GroupResponse.swift */, @@ -2803,12 +2809,14 @@ B305654823FC051E003304F2 /* LibraryResponse.swift */, B305654D23FC051E003304F2 /* LinksResponse.swift */, B305654E23FC051E003304F2 /* LoginResponse.swift */, + 6102D2B72D6CCDD300505E6A /* RemoteRecognizerResponse.swift */, + B3A2AEDD26565211004BF3A4 /* RemoteStylesResponse.swift */, B305655223FC051E003304F2 /* SchemaResponse.swift */, B305655123FC051E003304F2 /* SearchesResponse.swift */, B305654723FC051E003304F2 /* SettingsResponse.swift */, B305654B23FC051E003304F2 /* UpdatesResponse.swift */, ); - path = API; + path = Responses; sourceTree = ""; }; B305655523FC051E003304F2 /* Sync */ = { @@ -5519,6 +5527,7 @@ B34A9F6425BF1ABB007C9A4A /* PDFDocumentExporter.swift in Sources */, B377F2A5249373F300022943 /* AttachmentFileCleanupController.swift in Sources */, 61639F852AE03B8500026003 /* InstantPresenter.swift in Sources */, + 6102D2BC2D6CCF7600505E6A /* RecognizerRequest.swift in Sources */, 61391B802B3C6F22003B314A /* CopyBibliographyState.swift in Sources */, B3E8FE3E27142AA300F51458 /* StylePickerActionHandler.swift in Sources */, B305661823FC051E003304F2 /* LoadPermissionsSyncAction.swift in Sources */, @@ -5555,6 +5564,7 @@ B300B3382429254900C1FE1E /* SyncTranslatorsDbRequest.swift in Sources */, B34341A5260A16AB00093E63 /* ReadLibraryDbRequest.swift in Sources */, B30565B323FC051E003304F2 /* DeleteObjectsDbRequest.swift in Sources */, + 6102D2B92D6CCDD300505E6A /* RemoteRecognizerResponse.swift in Sources */, B390EE2C29B5F419004F979B /* PDFCoordinator.swift in Sources */, B305662323FC051F003304F2 /* MarkForResyncSyncAction.swift in Sources */, B32EBFC1276A89190003897E /* URLSessionCreator.swift in Sources */, @@ -5830,6 +5840,7 @@ B3FC7436272195DE00F55531 /* WebDavDownloadRequest.swift in Sources */, B3FC7437272195E100F55531 /* WebDavNonexistentPropRequest.swift in Sources */, B30549002B3327CB00853966 /* ReadAllDownloadsDbRequest.swift in Sources */, + 6102D2B82D6CCDD300505E6A /* RemoteRecognizerResponse.swift in Sources */, B3B557142C884B8300BD6325 /* AnnotationPreview.swift in Sources */, B305676F23FC0A55003304F2 /* ArrayEncoding.swift in Sources */, B305672523FC0909003304F2 /* DeleteGroupSyncAction.swift in Sources */, @@ -5885,6 +5896,7 @@ B3E7C150278348DC00076131 /* BackgroundUploadObserver.swift in Sources */, B305672123FC08EF003304F2 /* RealmDbStorage.swift in Sources */, B30566E223FC0810003304F2 /* DeletionsRequest.swift in Sources */, + 6102D2BD2D6CCF7600505E6A /* RecognizerRequest.swift in Sources */, B3100922272C098D003FC743 /* ReadWebDavDeletionsDbRequest.swift in Sources */, B305673A23FC0938003304F2 /* SyncScheduler.swift in Sources */, B357DEE7241661E300E06153 /* DebugLogging.swift in Sources */, diff --git a/Zotero/Controllers/API/Requests/RecognizerRequest.swift b/Zotero/Controllers/API/Requests/RecognizerRequest.swift new file mode 100644 index 000000000..0f5a865a5 --- /dev/null +++ b/Zotero/Controllers/API/Requests/RecognizerRequest.swift @@ -0,0 +1,23 @@ +// +// RecognizerRequest.swift +// Zotero +// +// Created by Miltiadis Vasilakis on 24/2/25. +// Copyright © 2025 Corporation for Digital Scholarship. All rights reserved. +// + +import Foundation + +struct RecognizerRequest: ApiResponseRequest { + typealias Response = RemoteRecognizerResponse + let parameters: [String: Any]? + + var endpoint: ApiEndpoint { .other(URL(string: "https://services.zotero.org/recognizer/recognize")!) } + var httpMethod: ApiHttpMethod { .post } + var encoding: ApiParameterEncoding { .json } + var headers: [String: String]? { nil } + + init(parameters: [String: Any]) { + self.parameters = parameters + } +} diff --git a/Zotero/Models/API/AuthorizeUploadResponse.swift b/Zotero/Controllers/API/Responses/AuthorizeUploadResponse.swift similarity index 100% rename from Zotero/Models/API/AuthorizeUploadResponse.swift rename to Zotero/Controllers/API/Responses/AuthorizeUploadResponse.swift diff --git a/Zotero/Models/API/CollectionResponse.swift b/Zotero/Controllers/API/Responses/CollectionResponse.swift similarity index 100% rename from Zotero/Models/API/CollectionResponse.swift rename to Zotero/Controllers/API/Responses/CollectionResponse.swift diff --git a/Zotero/Models/API/DeletionsResponse.swift b/Zotero/Controllers/API/Responses/DeletionsResponse.swift similarity index 100% rename from Zotero/Models/API/DeletionsResponse.swift rename to Zotero/Controllers/API/Responses/DeletionsResponse.swift diff --git a/Zotero/Models/API/GroupResponse.swift b/Zotero/Controllers/API/Responses/GroupResponse.swift similarity index 100% rename from Zotero/Models/API/GroupResponse.swift rename to Zotero/Controllers/API/Responses/GroupResponse.swift diff --git a/Zotero/Models/API/ItemResponse.swift b/Zotero/Controllers/API/Responses/ItemResponse.swift similarity index 100% rename from Zotero/Models/API/ItemResponse.swift rename to Zotero/Controllers/API/Responses/ItemResponse.swift diff --git a/Zotero/Models/API/KeyResponse.swift b/Zotero/Controllers/API/Responses/KeyResponse.swift similarity index 100% rename from Zotero/Models/API/KeyResponse.swift rename to Zotero/Controllers/API/Responses/KeyResponse.swift diff --git a/Zotero/Models/API/LibraryResponse.swift b/Zotero/Controllers/API/Responses/LibraryResponse.swift similarity index 100% rename from Zotero/Models/API/LibraryResponse.swift rename to Zotero/Controllers/API/Responses/LibraryResponse.swift diff --git a/Zotero/Models/API/LinksResponse.swift b/Zotero/Controllers/API/Responses/LinksResponse.swift similarity index 100% rename from Zotero/Models/API/LinksResponse.swift rename to Zotero/Controllers/API/Responses/LinksResponse.swift diff --git a/Zotero/Models/API/LoginResponse.swift b/Zotero/Controllers/API/Responses/LoginResponse.swift similarity index 100% rename from Zotero/Models/API/LoginResponse.swift rename to Zotero/Controllers/API/Responses/LoginResponse.swift diff --git a/Zotero/Controllers/API/Responses/RemoteRecognizerResponse.swift b/Zotero/Controllers/API/Responses/RemoteRecognizerResponse.swift new file mode 100644 index 000000000..aacca817b --- /dev/null +++ b/Zotero/Controllers/API/Responses/RemoteRecognizerResponse.swift @@ -0,0 +1,17 @@ +// +// RemoteRecognizerResponse.swift +// Zotero +// +// Created by Miltiadis Vasilakis on 24/2/25. +// Copyright © 2025 Corporation for Digital Scholarship. All rights reserved. +// + +import Foundation + +struct RemoteRecognizerResponse: Decodable { + struct Author: Decodable { + let firstName, lastName: String? + } + let arxiv, doi, isbn, abstract, language, title, type, year, pages, volume, url, issue, issn, container, publisher: String? + let authors: [Author] +} diff --git a/Zotero/Models/API/RemoteStylesResponse.swift b/Zotero/Controllers/API/Responses/RemoteStylesResponse.swift similarity index 100% rename from Zotero/Models/API/RemoteStylesResponse.swift rename to Zotero/Controllers/API/Responses/RemoteStylesResponse.swift diff --git a/Zotero/Models/API/SchemaResponse.swift b/Zotero/Controllers/API/Responses/SchemaResponse.swift similarity index 100% rename from Zotero/Models/API/SchemaResponse.swift rename to Zotero/Controllers/API/Responses/SchemaResponse.swift diff --git a/Zotero/Models/API/SearchesResponse.swift b/Zotero/Controllers/API/Responses/SearchesResponse.swift similarity index 100% rename from Zotero/Models/API/SearchesResponse.swift rename to Zotero/Controllers/API/Responses/SearchesResponse.swift diff --git a/Zotero/Models/API/SettingsResponse.swift b/Zotero/Controllers/API/Responses/SettingsResponse.swift similarity index 100% rename from Zotero/Models/API/SettingsResponse.swift rename to Zotero/Controllers/API/Responses/SettingsResponse.swift diff --git a/Zotero/Models/API/UpdatesResponse.swift b/Zotero/Controllers/API/Responses/UpdatesResponse.swift similarity index 100% rename from Zotero/Models/API/UpdatesResponse.swift rename to Zotero/Controllers/API/Responses/UpdatesResponse.swift diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index bcd7360da..7dce51bc3 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -48,7 +48,7 @@ final class PDFWorkerController { private let accessQueue: DispatchQueue private let disposeBag: DisposeBag - internal weak var webViewProvider: WebViewProvider? + weak var webViewProvider: WebViewProvider? // Accessed only via accessQueue private static let maxConcurrentPDFWorkers: Int = 1 @@ -110,6 +110,7 @@ final class PDFWorkerController { let webView = webViewProvider.addWebView(configuration: configuration) pdfWorkerWebViewHandler = PDFWorkerWebViewHandler(webView: webView) } + pdfWorkerWebViewHandlersByPDFWork[work] = pdfWorkerWebViewHandler } guard let pdfWorkerWebViewHandler else { DDLogError("PDFWorkerController: can't create PDFWorkerWebViewHandler instance") @@ -118,7 +119,7 @@ final class PDFWorkerController { } return } - pdfWorkerWebViewHandlersByPDFWork[work] = pdfWorkerWebViewHandler + setupObserver(for: pdfWorkerWebViewHandler) queue[work] = (.inProgress, observable) observable.on(.next(Update(work: work, kind: .inProgress))) @@ -132,7 +133,10 @@ final class PDFWorkerController { func setupObserver(for pdfWorkerWebViewHandler: PDFWorkerWebViewHandler) { pdfWorkerWebViewHandler.observable - .subscribe(onNext: { process(result: $0) }) + .subscribe(onNext: { [weak self] in + guard let self else { return } + process(result: $0) + }) .disposed(by: disposeBag) func process(result: Result) { diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index 0dcd37c4d..f1261badd 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -48,30 +48,6 @@ final class RecognizerController { } } - struct RemoteRecognizerResponse: Decodable { - struct Author: Decodable { - let firstName, lastName: String? - } - let arxiv, doi, isbn: String? - let abstract, language: String? - let title, type, year, pages, volume, url, issue, issn, container, publisher: String? - let authors: [Author] - } - - struct RecognizerRequest: ApiResponseRequest { - typealias Response = RemoteRecognizerResponse - let parameters: [String: Any]? - - var endpoint: ApiEndpoint { .other(URL(string: "https://services.zotero.org/recognizer/recognize")!) } - var httpMethod: ApiHttpMethod { .post } - var encoding: ApiParameterEncoding { .json } - var headers: [String: String]? { nil } - - init(parameters: [String: Any]) { - self.parameters = parameters - } - } - struct RecognizerTask: Hashable { enum Kind: Hashable { case simple From c71f8dc0dd7901ec9513d2aa717da46d4ec8ab7d Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Mon, 24 Feb 2025 18:14:43 +0200 Subject: [PATCH 16/33] Refactor RecognizerController internal types names --- Zotero/Controllers/RecognizerController.swift | 62 +++++++++---------- .../Items/ViewModels/ItemsActionHandler.swift | 2 +- .../Items/Views/ItemsViewController.swift | 2 +- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index f1261badd..e06d664ef 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -15,7 +15,7 @@ import RxSwift final class RecognizerController { // MARK: Types - enum RemoteRecognizerIdentifier { + enum RecognizerIdentifier { case arXiv(String) case doi(String) case isbn(String) @@ -48,7 +48,7 @@ final class RecognizerController { } } - struct RecognizerTask: Hashable { + struct Task: Hashable { enum Kind: Hashable { case simple case createParentForItem(libraryId: LibraryIdentifier, key: String) @@ -80,15 +80,15 @@ final class RecognizerController { case createdParent(item: RItem) } - let task: RecognizerTask + let task: Task let kind: Kind } - enum RecognizerTaskState { + enum TaskState { case enqueued case recognitionInProgress case remoteRecognitionInProgress(data: [String: Any]) - case identifiersLookupInProgress(response: RemoteRecognizerResponse, currentIdentifier: RemoteRecognizerIdentifier, pendingIdentifiers: [RemoteRecognizerIdentifier]) + case identifiersLookupInProgress(response: RemoteRecognizerResponse, currentIdentifier: RecognizerIdentifier, pendingIdentifiers: [RecognizerIdentifier]) } // MARK: Properties @@ -111,10 +111,10 @@ final class RecognizerController { internal weak var webViewProvider: WebViewProvider? // Accessed only via accessQueue - private static let maxConcurrentRecognizerTasks: Int = 1 - private var queue: OrderedDictionary)> = [:] + private static let maxConcurrentTasks: Int = 1 + private var queue: OrderedDictionary)> = [:] private var latestUpdates: [LibraryIdentifier: [String: Update.Kind]] = [:] - private var lookupWebViewHandlersByRecognizerTask: [RecognizerTask: LookupWebViewHandler] = [:] + private var lookupWebViewHandlersByTask: [Task: LookupWebViewHandler] = [:] // MARK: Object Lifecycle init( @@ -141,7 +141,7 @@ final class RecognizerController { } // MARK: Actions - func queue(task: RecognizerTask, completion: ((_ observable: Observable?) -> Void)? = nil) { + func queue(task: Task, completion: ((_ observable: Observable?) -> Void)? = nil) { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { completion?(nil) @@ -151,7 +151,7 @@ final class RecognizerController { completion?(observable.asObservable()) return } - let state: RecognizerTaskState = .enqueued + let state: TaskState = .enqueued let observable: PublishSubject = PublishSubject() queue[task] = (state, observable) completion?(observable.asObservable()) @@ -164,7 +164,7 @@ final class RecognizerController { } } - private func emmitUpdate(for task: RecognizerTask, observable: PublishSubject, kind: Update.Kind) { + private func emmitUpdate(for task: Task, observable: PublishSubject, kind: Update.Kind) { let update = Update(task: task, kind: kind) if case .createParentForItem(let libraryId, let key) = task.kind { var libraryLatestUpdates = latestUpdates[libraryId, default: [:]] @@ -175,7 +175,7 @@ final class RecognizerController { } private func startRecognitionIfNeeded() { - let runningRecognizerTasksCount = queue.filter({ + let runningTasksCount = queue.filter({ switch $0.value.state { case .enqueued: return false @@ -184,7 +184,7 @@ final class RecognizerController { return true } }).count - guard runningRecognizerTasksCount < Self.maxConcurrentRecognizerTasks else { return } + guard runningTasksCount < Self.maxConcurrentTasks else { return } let tasks = queue.keys for task in tasks { guard let (state, observable) = queue[task] else { continue } @@ -199,7 +199,7 @@ final class RecognizerController { } } - func start(task: RecognizerTask, observable: PublishSubject) { + func start(task: Task, observable: PublishSubject) { queue[task] = (.recognitionInProgress, observable) emmitUpdate(for: task, observable: observable, kind: .recognitionInProgress) @@ -250,7 +250,7 @@ final class RecognizerController { } } - private func startRemoteRecognition(for task: RecognizerTask, with data: [String: Any]) { + private func startRemoteRecognition(for task: Task, with data: [String: Any]) { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } guard let (_, observable) = queue[task] else { @@ -276,7 +276,7 @@ final class RecognizerController { } func process(response: RemoteRecognizerResponse) { - var identifiers: [RemoteRecognizerIdentifier] = [] + var identifiers: [RecognizerIdentifier] = [] if let identifier = response.arxiv { identifiers.append(.arXiv(identifier)) } @@ -299,7 +299,7 @@ final class RecognizerController { } } - private func startIdentifiersLookup(for task: RecognizerTask, with response: RemoteRecognizerResponse, pendingIdentifiers: [RemoteRecognizerIdentifier]) { + private func startIdentifiersLookup(for task: Task, with response: RemoteRecognizerResponse, pendingIdentifiers: [RecognizerIdentifier]) { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } guard let (state, _) = queue[task] else { @@ -316,7 +316,7 @@ final class RecognizerController { } } - private func enqueueNextIdentifierLookup(for task: RecognizerTask) { + private func enqueueNextIdentifierLookup(for task: Task) { if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { _enqueueNextIdentifierLookup(for: task) } else { @@ -325,7 +325,7 @@ final class RecognizerController { } } - func _enqueueNextIdentifierLookup(for task: RecognizerTask) { + func _enqueueNextIdentifierLookup(for task: Task) { guard let (state, _) = queue[task] else { startRecognitionIfNeeded() return @@ -340,7 +340,7 @@ final class RecognizerController { } } - private func lookupNextIdentifier(for task: RecognizerTask, with response: RemoteRecognizerResponse, pendingIdentifiers: [RemoteRecognizerIdentifier]) { + private func lookupNextIdentifier(for task: Task, with response: RemoteRecognizerResponse, pendingIdentifiers: [RecognizerIdentifier]) { DDLogInfo("RecognizerController: \(task) - looking up next identifier from \(pendingIdentifiers)") guard let (_, observable) = queue[task] else { startRecognitionIfNeeded() @@ -364,7 +364,7 @@ final class RecognizerController { use(title: title, with: response) } - func lookup(identifier: String, copyTagsAsAutomatic: Bool, remainingIdentifiers: [RemoteRecognizerIdentifier]) { + func lookup(identifier: String, copyTagsAsAutomatic: Bool, remainingIdentifiers: [RecognizerIdentifier]) { DDLogInfo("RecognizerController: \(task) - looking up identifier \(identifier)") guard let lookupWebViewHandler = getLookupWebViewHandler(for: task) else { enqueueNextIdentifierLookup(for: task) @@ -373,8 +373,8 @@ final class RecognizerController { emmitUpdate(for: task, observable: observable, kind: .identifierLookupInProgress(response: response, identifier: identifier)) lookupWebViewHandler.lookUp(identifier: identifier) - func getLookupWebViewHandler(for task: RecognizerTask) -> LookupWebViewHandler? { - if let lookupWebViewHandler = lookupWebViewHandlersByRecognizerTask[task] { + func getLookupWebViewHandler(for task: Task) -> LookupWebViewHandler? { + if let lookupWebViewHandler = lookupWebViewHandlersByTask[task] { return lookupWebViewHandler } var lookupWebViewHandler: LookupWebViewHandler? @@ -387,7 +387,7 @@ final class RecognizerController { DDLogWarn("RecognizerController: \(task) - can't create LookupWebViewHandler instance") return nil } - lookupWebViewHandlersByRecognizerTask[task] = lookupWebViewHandler + lookupWebViewHandlersByTask[task] = lookupWebViewHandler setupObserver(for: lookupWebViewHandler) return lookupWebViewHandler @@ -492,7 +492,7 @@ final class RecognizerController { createParentIfNeeded(for: task, with: itemResponse, schemaController: schemaController, dateParser: dateParser) } - func createParentIfNeeded(for task: RecognizerTask, with itemResponse: ItemResponse, schemaController: SchemaController, dateParser: DateParser) { + func createParentIfNeeded(for task: Task, with itemResponse: ItemResponse, schemaController: SchemaController, dateParser: DateParser) { switch task.kind { case .simple: cleanupTask(for: task) { observable in @@ -529,7 +529,7 @@ final class RecognizerController { } } - func cancel(task: RecognizerTask) { + func cancel(task: Task) { cleanupTask(for: task) { observable in DDLogInfo("RecognizerController: cancelled \(task)") observable?.on(.next(Update(task: task, kind: .cancelled))) @@ -541,9 +541,9 @@ final class RecognizerController { guard let self else { return } DDLogInfo("RecognizerController: cancel all tasks") // Immediatelly release all lookup web views. - let keys = lookupWebViewHandlersByRecognizerTask.keys + let keys = lookupWebViewHandlersByTask.keys for key in keys { - lookupWebViewHandlersByRecognizerTask.removeValue(forKey: key)?.webViewHandler.removeFromSuperviewAsynchronously() + lookupWebViewHandlersByTask.removeValue(forKey: key)?.webViewHandler.removeFromSuperviewAsynchronously() } // Then cancel actual tasks, and send cancelled event for each queued task. let tasks = queue.keys @@ -553,7 +553,7 @@ final class RecognizerController { } } - private func cleanupTask(for task: RecognizerTask, completion: @escaping (_ observable: PublishSubject?) -> Void) { + private func cleanupTask(for task: Task, completion: @escaping (_ observable: PublishSubject?) -> Void) { if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { cleanup(for: task, completion: completion) } else { @@ -562,14 +562,14 @@ final class RecognizerController { } } - func cleanup(for task: RecognizerTask, completion: @escaping (_ observable: PublishSubject?) -> Void) { + func cleanup(for task: Task, completion: @escaping (_ observable: PublishSubject?) -> Void) { let observable = queue.removeValue(forKey: task).flatMap({ $0.observable }) if case .createParentForItem(let libraryId, let key) = task.kind, var libraryLatestUpdates = latestUpdates[libraryId] { libraryLatestUpdates[key] = nil latestUpdates[libraryId] = libraryLatestUpdates } DDLogInfo("RecognizerController: \(task) - cleaned up") - lookupWebViewHandlersByRecognizerTask.removeValue(forKey: task)?.webViewHandler.removeFromSuperviewAsynchronously() + lookupWebViewHandlersByTask.removeValue(forKey: task)?.webViewHandler.removeFromSuperviewAsynchronously() completion(observable) startRecognitionIfNeeded() } diff --git a/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift b/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift index fd520b564..91b6f0dea 100644 --- a/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift +++ b/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift @@ -512,7 +512,7 @@ final class ItemsActionHandler: BaseItemsActionHandler, ViewModelActionHandler { case .success(let (succeeded, failed)): for attachment in succeeded { if let file = attachment.file as? FileData, file.mimeType == "application/pdf" { - let task = RecognizerController.RecognizerTask(file: file, kind: .createParentForItem(libraryId: libraryId, key: attachment.key)) + let task = RecognizerController.Task(file: file, kind: .createParentForItem(libraryId: libraryId, key: attachment.key)) recognizerController.queue(task: task) { [weak self] observable in guard let self else { return } observable?.subscribe { _ in diff --git a/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift b/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift index 9aba4f822..9318eb712 100644 --- a/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift +++ b/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift @@ -259,7 +259,7 @@ final class ItemsViewController: BaseItemsViewController { file.mimeType == "application/pdf", let recognizerController = controllers.userControllers?.recognizerController else { return } - recognizerController.queue(task: RecognizerController.RecognizerTask(file: file, kind: .createParentForItem(libraryId: library.identifier, key: key))) + recognizerController.queue(task: RecognizerController.Task(file: file, kind: .createParentForItem(libraryId: library.identifier, key: key))) case .duplicate: guard let key = selectedKeys.first else { return } From 4992213bd4a2658a827cbb5097d0a9f06f2c0800 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Mon, 24 Feb 2025 16:27:37 +0200 Subject: [PATCH 17/33] Improve code --- ZShare/ViewModels/ExtensionViewModel.swift | 6 +-- Zotero/Controllers/PDFWorkerController.swift | 33 ++++++++------- Zotero/Controllers/RecognizerController.swift | 41 ++++++++----------- .../Items/ViewModels/ItemsActionHandler.swift | 8 +--- .../Items/Views/ItemsViewController.swift | 2 +- 5 files changed, 39 insertions(+), 51 deletions(-) diff --git a/ZShare/ViewModels/ExtensionViewModel.swift b/ZShare/ViewModels/ExtensionViewModel.swift index 5d7298dc6..86e4ebee7 100644 --- a/ZShare/ViewModels/ExtensionViewModel.swift +++ b/ZShare/ViewModels/ExtensionViewModel.swift @@ -538,9 +538,8 @@ final class ExtensionViewModel { } func recognize(file: FileData) { - recognizerController.queue(task: .init(file: file, kind: .simple)) { [weak self] observable in - guard let self else { return } - observable?.subscribe { [weak self] update in + recognizerController.queue(task: .init(file: file, kind: .simple)) + .subscribe { [weak self] update in guard let self else { return } switch update.kind { case .failed(let error): @@ -567,7 +566,6 @@ final class ExtensionViewModel { } } .disposed(by: disposeBag) - } } } diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index 7dce51bc3..db8ea4d37 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -65,22 +65,25 @@ final class PDFWorkerController { } // MARK: Actions - func queue(work: PDFWork, completion: @escaping (_ observable: Observable?) -> Void) { - accessQueue.async(flags: .barrier) { [weak self] in - guard let self else { - completion(nil) - return - } - if let (_, observable) = queue[work] { - completion(observable.asObservable()) - return - } - let state: PDFWorkState = .enqueued - let observable: PublishSubject = PublishSubject() - queue[work] = (state, observable) - completion(observable.asObservable()) + func queue(work: PDFWork) -> Observable { + return Observable.create { [weak self] subscriber in + guard let self else { return Disposables.create() } + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { return } + if let subject = queue[work]?.1 { + subject.subscribe(subscriber) + .disposed(by: disposeBag) + return + } + let state: PDFWorkState = .enqueued + let subject: PublishSubject = PublishSubject() + queue[work] = (state, subject) + subject.subscribe(subscriber) + .disposed(by: disposeBag) - startWorkIfNeeded() + startWorkIfNeeded() + } + return Disposables.create() } } diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index e06d664ef..0429c42be 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -59,7 +59,6 @@ final class RecognizerController { } enum Error: Swift.Error { - case cantStartPDFWorker case pdfWorkerError case recognizerFailed case remoteRecognizerFailed @@ -141,20 +140,13 @@ final class RecognizerController { } // MARK: Actions - func queue(task: Task, completion: ((_ observable: Observable?) -> Void)? = nil) { + func queue(task: Task) -> Observable { + // Queue task regardless of any subscribers accessQueue.async(flags: .barrier) { [weak self] in - guard let self else { - completion?(nil) - return - } - if let (_, observable) = queue[task] { - completion?(observable.asObservable()) - return - } + guard let self, queue[task] == nil else { return } let state: TaskState = .enqueued let observable: PublishSubject = PublishSubject() queue[task] = (state, observable) - completion?(observable.asObservable()) observable.subscribe(onNext: { [weak self] update in self?.updatesSubject.on(.next(update)) }).disposed(by: disposeBag) @@ -162,6 +154,15 @@ final class RecognizerController { emmitUpdate(for: task, observable: observable, kind: .enqueued) startRecognitionIfNeeded() } + return Observable.create { [weak self] subscriber in + guard let self else { return Disposables.create() } + accessQueue.async(flags: .barrier) { [weak self] in + guard let self, let subject = queue[task]?.1 else { return } + subject.subscribe(subscriber) + .disposed(by: disposeBag) + } + return Disposables.create() + } } private func emmitUpdate(for task: Task, observable: PublishSubject, kind: Update.Kind) { @@ -203,19 +204,11 @@ final class RecognizerController { queue[task] = (.recognitionInProgress, observable) emmitUpdate(for: task, observable: observable, kind: .recognitionInProgress) - pdfWorkerController.queue(work: PDFWorkerController.PDFWork(file: task.file, kind: .recognizer)) { [weak self] pdfWorkerObservable in - guard let self else { return } - guard let pdfWorkerObservable else { - DDLogError("RecognizerController: \(task) - can't create start PDF worker") - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.cantStartPDFWorker)))) - } - return - } - pdfWorkerObservable - .subscribe(onNext: { process(update: $0) }) - .disposed(by: disposeBag) - } + pdfWorkerController.queue(work: PDFWorkerController.PDFWork(file: task.file, kind: .recognizer)) + .subscribe(onNext: { update in + process(update: update) + }) + .disposed(by: disposeBag) func process(update: PDFWorkerController.Update) { switch update.kind { diff --git a/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift b/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift index 91b6f0dea..bedeb5547 100644 --- a/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift +++ b/Zotero/Scenes/Detail/Items/ViewModels/ItemsActionHandler.swift @@ -513,13 +513,7 @@ final class ItemsActionHandler: BaseItemsActionHandler, ViewModelActionHandler { for attachment in succeeded { if let file = attachment.file as? FileData, file.mimeType == "application/pdf" { let task = RecognizerController.Task(file: file, kind: .createParentForItem(libraryId: libraryId, key: attachment.key)) - recognizerController.queue(task: task) { [weak self] observable in - guard let self else { return } - observable?.subscribe { _ in - // TODO: Implement update of UI according to updates - } - .disposed(by: disposeBag) - } + _ = recognizerController.queue(task: task) } } guard !failed.isEmpty else { return } diff --git a/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift b/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift index 9318eb712..17ecc180e 100644 --- a/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift +++ b/Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift @@ -259,7 +259,7 @@ final class ItemsViewController: BaseItemsViewController { file.mimeType == "application/pdf", let recognizerController = controllers.userControllers?.recognizerController else { return } - recognizerController.queue(task: RecognizerController.Task(file: file, kind: .createParentForItem(libraryId: library.identifier, key: key))) + _ = recognizerController.queue(task: RecognizerController.Task(file: file, kind: .createParentForItem(libraryId: library.identifier, key: key))) case .duplicate: guard let key = selectedKeys.first else { return } From 6ac3b32987b9ecd90c6254ead13b6adc57dffbbd Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Mon, 24 Feb 2025 21:01:51 +0200 Subject: [PATCH 18/33] Fix PDFWorkerControllerSpec.swift tests --- ZoteroTests/PDFWorkerControllerSpec.swift | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ZoteroTests/PDFWorkerControllerSpec.swift b/ZoteroTests/PDFWorkerControllerSpec.swift index 11fd2437f..c47c989d0 100644 --- a/ZoteroTests/PDFWorkerControllerSpec.swift +++ b/ZoteroTests/PDFWorkerControllerSpec.swift @@ -58,10 +58,8 @@ final class PDFWorkerControllerSpec: QuickSpec { var emittedUpdates: [PDFWorkerController.Update.Kind] = [] waitUntil(timeout: .seconds(10)) { completion in - pdfWorkerController.queue(work: work) { observable in - expect(observable).toNot(beNil()) - guard let observable else { return } - observable.subscribe(onNext: { update in + pdfWorkerController.queue(work: work) + .subscribe(onNext: { update in expect(update.work).to(equal(work)) emittedUpdates.append(update.kind) switch update.kind { @@ -73,7 +71,6 @@ final class PDFWorkerControllerSpec: QuickSpec { } }) .disposed(by: disposeBag) - } } expect(emittedUpdates.count).toEventually(equal(2), timeout: .seconds(20)) @@ -100,10 +97,8 @@ final class PDFWorkerControllerSpec: QuickSpec { var emittedUpdates: [PDFWorkerController.Update.Kind] = [] waitUntil(timeout: .seconds(10)) { completion in - pdfWorkerController.queue(work: work) { observable in - expect(observable).toNot(beNil()) - guard let observable else { return } - observable.subscribe(onNext: { update in + pdfWorkerController.queue(work: work) + .subscribe(onNext: { update in expect(update.work).to(equal(work)) emittedUpdates.append(update.kind) switch update.kind { @@ -115,7 +110,6 @@ final class PDFWorkerControllerSpec: QuickSpec { } }) .disposed(by: disposeBag) - } } expect(emittedUpdates.count).toEventually(equal(2), timeout: .seconds(20)) From 7b0dfa50e493df0f8100d7a4d45f7b887877480e Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Tue, 25 Feb 2025 17:58:29 +0200 Subject: [PATCH 19/33] Improve queuing & cleanup in PDFWorkerController & RecognizerController --- Zotero/Controllers/PDFWorkerController.swift | 76 ++++++------ Zotero/Controllers/RecognizerController.swift | 113 +++++++++--------- 2 files changed, 95 insertions(+), 94 deletions(-) diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index db8ea4d37..f8421e17d 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -66,25 +66,20 @@ final class PDFWorkerController { // MARK: Actions func queue(work: PDFWork) -> Observable { - return Observable.create { [weak self] subscriber in - guard let self else { return Disposables.create() } - accessQueue.async(flags: .barrier) { [weak self] in - guard let self else { return } - if let subject = queue[work]?.1 { - subject.subscribe(subscriber) - .disposed(by: disposeBag) - return - } - let state: PDFWorkState = .enqueued - let subject: PublishSubject = PublishSubject() - queue[work] = (state, subject) - subject.subscribe(subscriber) + let subject = PublishSubject() + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { return } + if let existingSubject = queue[work]?.1 { + existingSubject.subscribe(subject) .disposed(by: disposeBag) - - startWorkIfNeeded() + return } - return Disposables.create() + let state: PDFWorkState = .enqueued + queue[work] = (state, subject) + + startWorkIfNeeded() } + return subject.asObservable() } private func startWorkIfNeeded() { @@ -117,9 +112,8 @@ final class PDFWorkerController { } guard let pdfWorkerWebViewHandler else { DDLogError("PDFWorkerController: can't create PDFWorkerWebViewHandler instance") - cleanupPDFWorker(for: work) { observable in - observable?.on(.next(Update(work: work, kind: .failed))) - } + cleanupPDFWorker(for: work).subscribe(onSuccess: { $0.on(.next(Update(work: work, kind: .failed))) }) + .disposed(by: disposeBag) return } @@ -147,16 +141,14 @@ final class PDFWorkerController { case .success(let data): switch data { case .recognizerData(let data), .fullText(let data): - cleanupPDFWorker(for: work) { observable in - observable?.on(.next(Update(work: work, kind: .extractedData(data: data)))) - } + cleanupPDFWorker(for: work).subscribe(onSuccess: { $0.on(.next(Update(work: work, kind: .extractedData(data: data)))) }) + .disposed(by: disposeBag) } case .failure(let error): DDLogError("PDFWorkerController: recognizer failed - \(error)") - cleanupPDFWorker(for: work) { observable in - observable?.on(.next(Update(work: work, kind: .failed))) - } + cleanupPDFWorker(for: work).subscribe(onSuccess: { $0.on(.next(Update(work: work, kind: .failed))) }) + .disposed(by: disposeBag) } } } @@ -164,10 +156,9 @@ final class PDFWorkerController { } func cancel(work: PDFWork) { - cleanupPDFWorker(for: work) { observable in - DDLogInfo("PDFWorkerController: cancelled \(work)") - observable?.on(.next(Update(work: work, kind: .cancelled))) - } + DDLogInfo("PDFWorkerController: cancelled \(work)") + cleanupPDFWorker(for: work).subscribe(onSuccess: { $0.on(.next(Update(work: work, kind: .cancelled))) }) + .disposed(by: disposeBag) } func cancellAllWorks() { @@ -187,20 +178,31 @@ final class PDFWorkerController { } } - private func cleanupPDFWorker(for work: PDFWork, completion: @escaping (_ observable: PublishSubject?) -> Void) { - if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { - cleanup(for: work, completion: completion) - } else { - accessQueue.async(flags: .barrier) { - cleanup(for: work, completion: completion) + private func cleanupPDFWorker(for work: PDFWork) -> Maybe> { + return Maybe.create { [weak self] maybe in + guard let self else { + maybe(.completed) + return Disposables.create() } + if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { + cleanup(for: work, maybe: maybe) + } else { + accessQueue.async(flags: .barrier) { + cleanup(for: work, maybe: maybe) + } + } + return Disposables.create() } - func cleanup(for work: PDFWork, completion: @escaping (_ observable: PublishSubject?) -> Void) { + func cleanup(for work: PDFWork, maybe: (MaybeEvent>) -> Void) { let observable = queue.removeValue(forKey: work).flatMap({ $0.observable }) DDLogInfo("PDFWorkerController: cleaned up for \(work)") pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: work)?.webViewHandler.removeFromSuperviewAsynchronously() - completion(observable) + if let observable { + maybe(.success(observable)) + } else { + maybe(.completed) + } startWorkIfNeeded() } } diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index 0429c42be..719f31fa4 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -141,28 +141,24 @@ final class RecognizerController { // MARK: Actions func queue(task: Task) -> Observable { - // Queue task regardless of any subscribers + let subject = PublishSubject() accessQueue.async(flags: .barrier) { [weak self] in - guard let self, queue[task] == nil else { return } + guard let self else { return } + if let existingSubject = queue[task]?.1 { + existingSubject.subscribe(subject) + .disposed(by: disposeBag) + return + } let state: TaskState = .enqueued - let observable: PublishSubject = PublishSubject() - queue[task] = (state, observable) - observable.subscribe(onNext: { [weak self] update in + queue[task] = (state, subject) + subject.subscribe(onNext: { [weak self] update in self?.updatesSubject.on(.next(update)) }).disposed(by: disposeBag) - emmitUpdate(for: task, observable: observable, kind: .enqueued) + emmitUpdate(for: task, observable: subject, kind: .enqueued) startRecognitionIfNeeded() } - return Observable.create { [weak self] subscriber in - guard let self else { return Disposables.create() } - accessQueue.async(flags: .barrier) { [weak self] in - guard let self, let subject = queue[task]?.1 else { return } - subject.subscribe(subscriber) - .disposed(by: disposeBag) - } - return Disposables.create() - } + return subject.asObservable() } private func emmitUpdate(for task: Task, observable: PublishSubject, kind: Update.Kind) { @@ -214,14 +210,12 @@ final class RecognizerController { switch update.kind { case .failed: DDLogError("RecognizerController: \(task) - recognizer failed") - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.recognizerFailed)))) - } + cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.recognizerFailed)))) }) + .disposed(by: disposeBag) case .cancelled: - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .cancelled))) - } + cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .cancelled))) }) + .disposed(by: disposeBag) case .inProgress: break @@ -234,9 +228,8 @@ final class RecognizerController { case .fullText: DDLogError("RecognizerController: \(task) - PDF worker error") - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.pdfWorkerError)))) - } + cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.pdfWorkerError)))) }) + .disposed(by: disposeBag) } } } @@ -259,10 +252,10 @@ final class RecognizerController { process(response: response.0) }, onFailure: { [weak self] error in + guard let self else { return } DDLogError("RecognizerController: \(task) - remote recognizer request failed: \(error)") - self?.cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(error as! Error)))) - } + cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(error as! Error)))) }) + .disposed(by: disposeBag) } ) .disposed(by: disposeBag) @@ -283,9 +276,8 @@ final class RecognizerController { identifiers.append(.title(identifier)) } guard !identifiers.isEmpty else { - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.remoteRecognizerFailed)))) - } + cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.remoteRecognizerFailed)))) }) + .disposed(by: disposeBag) return } startIdentifiersLookup(for: task, with: response, pendingIdentifiers: identifiers) @@ -300,9 +292,8 @@ final class RecognizerController { return } guard case .remoteRecognitionInProgress = state else { - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) - } + cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) }) + .disposed(by: disposeBag) return } lookupNextIdentifier(for: task, with: response, pendingIdentifiers: pendingIdentifiers) @@ -324,9 +315,8 @@ final class RecognizerController { return } guard case .identifiersLookupInProgress(let response, _, let pendingIdentifiers) = state else { - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) - } + cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) }) + .disposed(by: disposeBag) return } lookupNextIdentifier(for: task, with: response, pendingIdentifiers: pendingIdentifiers) @@ -340,9 +330,8 @@ final class RecognizerController { return } guard !pendingIdentifiers.isEmpty else { - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .failed(.noRemainingIdentifiersForLookup)))) - } + cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.noRemainingIdentifiersForLookup)))) }) + .disposed(by: disposeBag) return } var remainingIdentifiers = pendingIdentifiers @@ -488,9 +477,8 @@ final class RecognizerController { func createParentIfNeeded(for task: Task, with itemResponse: ItemResponse, schemaController: SchemaController, dateParser: DateParser) { switch task.kind { case .simple: - cleanupTask(for: task) { observable in - observable?.on(.next(Update(task: task, kind: .translated(itemResponse: itemResponse)))) - } + cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .translated(itemResponse: itemResponse)))) }) + .disposed(by: disposeBag) case .createParentForItem(let libraryId, let key): backgroundQueue.async { [weak self] in @@ -512,21 +500,21 @@ final class RecognizerController { DDLogError("RecognizerController: can't create parent for item - \(error)") update = Update(task: task, kind: .failed(error as! Error)) } - cleanupTask(for: task) { observable in + cleanupTask(for: task).subscribe(onSuccess: { if let update { - observable?.on(.next(update)) + $0.on(.next(update)) } - } + }) + .disposed(by: disposeBag) } } } } func cancel(task: Task) { - cleanupTask(for: task) { observable in - DDLogInfo("RecognizerController: cancelled \(task)") - observable?.on(.next(Update(task: task, kind: .cancelled))) - } + DDLogInfo("RecognizerController: cancelled \(task)") + cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .cancelled))) }) + .disposed(by: disposeBag) } func cancellAllTasks() { @@ -546,16 +534,23 @@ final class RecognizerController { } } - private func cleanupTask(for task: Task, completion: @escaping (_ observable: PublishSubject?) -> Void) { - if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { - cleanup(for: task, completion: completion) - } else { - accessQueue.async(flags: .barrier) { - cleanup(for: task, completion: completion) + private func cleanupTask(for task: Task) -> Maybe> { + return Maybe.create { [weak self] maybe in + guard let self else { + maybe(.completed) + return Disposables.create() } + if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { + cleanup(for: task, maybe: maybe) + } else { + accessQueue.async(flags: .barrier) { + cleanup(for: task, maybe: maybe) + } + } + return Disposables.create() } - func cleanup(for task: Task, completion: @escaping (_ observable: PublishSubject?) -> Void) { + func cleanup(for task: Task, maybe: (MaybeEvent>) -> Void) { let observable = queue.removeValue(forKey: task).flatMap({ $0.observable }) if case .createParentForItem(let libraryId, let key) = task.kind, var libraryLatestUpdates = latestUpdates[libraryId] { libraryLatestUpdates[key] = nil @@ -563,7 +558,11 @@ final class RecognizerController { } DDLogInfo("RecognizerController: \(task) - cleaned up") lookupWebViewHandlersByTask.removeValue(forKey: task)?.webViewHandler.removeFromSuperviewAsynchronously() - completion(observable) + if let observable { + maybe(.success(observable)) + } else { + maybe(.completed) + } startRecognitionIfNeeded() } } From d0578c6f9e163f53f843673490341463d6dbf8e1 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Tue, 25 Feb 2025 18:42:19 +0200 Subject: [PATCH 20/33] Refactor PDFWorkerWebViewHandler --- Zotero.xcodeproj/project.pbxproj | 6 ++ .../PDFWorkerWebViewHandler.swift | 97 +++---------------- .../Controllers/Web View Handling/worker.html | 80 +++++++++++++++ 3 files changed, 98 insertions(+), 85 deletions(-) create mode 100644 Zotero/Controllers/Web View Handling/worker.html diff --git a/Zotero.xcodeproj/project.pbxproj b/Zotero.xcodeproj/project.pbxproj index c68fdbbe8..fa66d9e1a 100644 --- a/Zotero.xcodeproj/project.pbxproj +++ b/Zotero.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 6102D2B92D6CCDD300505E6A /* RemoteRecognizerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6102D2B72D6CCDD300505E6A /* RemoteRecognizerResponse.swift */; }; 6102D2BC2D6CCF7600505E6A /* RecognizerRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6102D2BB2D6CCF7600505E6A /* RecognizerRequest.swift */; }; 6102D2BD2D6CCF7600505E6A /* RecognizerRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6102D2BB2D6CCF7600505E6A /* RecognizerRequest.swift */; }; + 6102D2BF2D6E23D100505E6A /* worker.html in Resources */ = {isa = PBXBuildFile; fileRef = 6102D2BE2D6E23C400505E6A /* worker.html */; }; + 6102D2C02D6E23D100505E6A /* worker.html in Resources */ = {isa = PBXBuildFile; fileRef = 6102D2BE2D6E23C400505E6A /* worker.html */; }; 61099E6E2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m in Sources */ = {isa = PBXBuildFile; fileRef = 61099E6D2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m */; }; 61099E6F2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m in Sources */ = {isa = PBXBuildFile; fileRef = 61099E6D2C91BAF300EDD92C /* NSDecimalNumber+Rounding.m */; }; 61168F612D50D4B6005495E8 /* PDFWorkerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61168F602D50D4B6005495E8 /* PDFWorkerController.swift */; }; @@ -1314,6 +1316,7 @@ 6102D2B52D68B5DE00505E6A /* bitcoin_pdf_full_text.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitcoin_pdf_full_text.json; sourceTree = ""; }; 6102D2B72D6CCDD300505E6A /* RemoteRecognizerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteRecognizerResponse.swift; sourceTree = ""; }; 6102D2BB2D6CCF7600505E6A /* RecognizerRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecognizerRequest.swift; sourceTree = ""; }; + 6102D2BE2D6E23C400505E6A /* worker.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = worker.html; sourceTree = ""; }; 61099E6A2C91BAF300EDD92C /* Zotero-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Zotero-Bridging-Header.h"; sourceTree = ""; }; 61099E6B2C91BAF300EDD92C /* ZShare-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ZShare-Bridging-Header.h"; sourceTree = ""; }; 61099E6C2C91BAF300EDD92C /* NSDecimalNumber+Rounding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDecimalNumber+Rounding.h"; sourceTree = ""; }; @@ -3633,6 +3636,7 @@ B39E0130283276470091CE4A /* Web View Handling */ = { isa = PBXGroup; children = ( + 6102D2BE2D6E23C400505E6A /* worker.html */, B323703F2C0DC65600170779 /* LookupWebViewHandler.swift */, 612361062D439D6A007FA575 /* PDFWorkerWebViewHandler.swift */, B39E0131283276830091CE4A /* WebViewHandler.swift */, @@ -4586,6 +4590,7 @@ B37C5B6F26454130009A37E5 /* NoteEditorViewController.xib in Resources */, B3593F27241A61C700760E20 /* ItemDetailFieldContentView.xib in Resources */, B34DF1BE2576956F0019CCD1 /* SwitchCell.xib in Resources */, + 6102D2C02D6E23D100505E6A /* worker.html in Resources */, B30B550324B85A3A00F94B59 /* Colors.xcassets in Resources */, B31CC57D286468C80055C114 /* ManualLookupViewController.xib in Resources */, B35DC8E6261D9E7C00ED30F4 /* ItemDetailFieldMultilineEditContentView.xib in Resources */, @@ -4654,6 +4659,7 @@ B36459DD2644118800A0C2C0 /* TagPickerCell.xib in Resources */, B337A5AE244F35A900AFD13D /* Localizable.strings in Resources */, B35F476A24F8F55600FE328D /* webview_extraction.js in Resources */, + 6102D2BF2D6E23D100505E6A /* worker.html in Resources */, B3AB38B8238ED023008A1ABB /* Assets.xcassets in Resources */, B36459DE2644119100A0C2C0 /* TagPickerViewController.xib in Resources */, 618D83E82BAAC88C00E7966B /* PrivacyInfo.xcprivacy in Resources */, diff --git a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift index 27cd4529e..11d8dff38 100644 --- a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift @@ -24,6 +24,10 @@ final class PDFWorkerWebViewHandler { case log = "logHandler" } + enum Error: Swift.Error { + case cantFindFile + } + enum PDFWorkerData { case recognizerData(data: [String: Any]) case fullText(data: [String: Any]) @@ -65,11 +69,17 @@ final class PDFWorkerWebViewHandler { func initialize() -> Single { DDLogInfo("PDFWorkerWebViewHandler: initialize web view") - let baseURL = Bundle.main.bundleURL.appendingPathComponent("Bundled/pdf_worker", isDirectory: true) - return webViewHandler.loadHTMLString(Self.htmlString, baseURL: baseURL) + return loadIndex() .flatMap { _ in Single.just(Void()) } + + func loadIndex() -> Single<()> { + guard let indexUrl = Bundle.main.url(forResource: "worker", withExtension: "html") else { + return .error(Error.cantFindFile) + } + return webViewHandler.load(fileUrl: indexUrl) + } } } @@ -165,87 +175,4 @@ final class PDFWorkerWebViewHandler { DDLogInfo("JSLOG: \(body)") } } - - static let htmlString: String = """ - - - - - PDF Worker - - - - - -""" } diff --git a/Zotero/Controllers/Web View Handling/worker.html b/Zotero/Controllers/Web View Handling/worker.html new file mode 100644 index 000000000..c30e2a838 --- /dev/null +++ b/Zotero/Controllers/Web View Handling/worker.html @@ -0,0 +1,80 @@ + + + + + PDF Worker + + + + + From 74093a9f3af5f7e489134061d98e3bfddde27060 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Tue, 25 Feb 2025 19:38:34 +0200 Subject: [PATCH 21/33] Merge RecognizerController identifiers lookup methods --- Zotero/Controllers/RecognizerController.swift | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index 719f31fa4..e2aef09f5 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -280,31 +280,25 @@ final class RecognizerController { .disposed(by: disposeBag) return } - startIdentifiersLookup(for: task, with: response, pendingIdentifiers: identifiers) - } - } - - private func startIdentifiersLookup(for task: Task, with response: RemoteRecognizerResponse, pendingIdentifiers: [RecognizerIdentifier]) { - accessQueue.async(flags: .barrier) { [weak self] in - guard let self else { return } - guard let (state, _) = queue[task] else { - startRecognitionIfNeeded() - return + enqueueNextIdentifierLookup(for: task) { state in + guard case .remoteRecognitionInProgress = state else { return nil } + return (response, identifiers) } - guard case .remoteRecognitionInProgress = state else { - cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) }) - .disposed(by: disposeBag) - return - } - lookupNextIdentifier(for: task, with: response, pendingIdentifiers: pendingIdentifiers) } } - private func enqueueNextIdentifierLookup(for task: Task) { + private func enqueueNextIdentifierLookup( + for task: Task, + getResponseAndIdentifiers: @escaping (TaskState) -> (RemoteRecognizerResponse, [RecognizerIdentifier])? = { state -> (RemoteRecognizerResponse, [RecognizerIdentifier])? in + guard case .identifiersLookupInProgress(let response, _, let pendingIdentifiers) = state else { return nil } + return (response, pendingIdentifiers) + } + ) { if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { _enqueueNextIdentifierLookup(for: task) } else { - accessQueue.async(flags: .barrier) { + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { return } _enqueueNextIdentifierLookup(for: task) } } @@ -314,7 +308,7 @@ final class RecognizerController { startRecognitionIfNeeded() return } - guard case .identifiersLookupInProgress(let response, _, let pendingIdentifiers) = state else { + guard let (response, pendingIdentifiers) = getResponseAndIdentifiers(state) else { cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) }) .disposed(by: disposeBag) return From 143b0c07485d3118bbf7b20035c792f8006f8fbe Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Wed, 26 Feb 2025 07:27:31 +0200 Subject: [PATCH 22/33] Improve PDFWorkerController --- Zotero/Controllers/PDFWorkerController.swift | 53 +++++++++----------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index f8421e17d..e7b6ec3f5 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -52,8 +52,11 @@ final class PDFWorkerController { // Accessed only via accessQueue private static let maxConcurrentPDFWorkers: Int = 1 - private var queue: OrderedDictionary)> = [:] + // Using an OrderedDictionary instead of an Array, so we can O(1) when cancelling a work that is still queued. + private var queue: OrderedDictionary> = [:] + private var subjectsByPDFWork: [PDFWork: PublishSubject] = [:] private var pdfWorkerWebViewHandlersByPDFWork: [PDFWork: PDFWorkerWebViewHandler] = [:] + private var statesByPDFWork: [PDFWork: PDFWorkState] = [:] // MARK: Object Lifecycle init() { @@ -69,13 +72,14 @@ final class PDFWorkerController { let subject = PublishSubject() accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } - if let existingSubject = queue[work]?.1 { + if let existingSubject = subjectsByPDFWork[work] { existingSubject.subscribe(subject) .disposed(by: disposeBag) return } - let state: PDFWorkState = .enqueued - queue[work] = (state, subject) + queue[work] = subject + subjectsByPDFWork[work] = subject + statesByPDFWork[work] = .enqueued startWorkIfNeeded() } @@ -83,22 +87,12 @@ final class PDFWorkerController { } private func startWorkIfNeeded() { - guard pdfWorkerWebViewHandlersByPDFWork.count < Self.maxConcurrentPDFWorkers else { return } - let works = queue.keys - for work in works { - guard let (state, observable) = queue[work] else { continue } - switch state { - case .enqueued: - start(work: work, observable: observable) - startWorkIfNeeded() - return + guard pdfWorkerWebViewHandlersByPDFWork.count < Self.maxConcurrentPDFWorkers, !queue.isEmpty else { return } + let (work, subject) = queue.removeFirst() + start(work: work, subject: subject) + startWorkIfNeeded() - case .inProgress: - break - } - } - - func start(work: PDFWork, observable: PublishSubject) { + func start(work: PDFWork, subject: PublishSubject) { var pdfWorkerWebViewHandler = pdfWorkerWebViewHandlersByPDFWork[work] if pdfWorkerWebViewHandler == nil { DispatchQueue.main.sync { [weak webViewProvider] in @@ -117,9 +111,9 @@ final class PDFWorkerController { return } + statesByPDFWork[work] = .inProgress setupObserver(for: pdfWorkerWebViewHandler) - queue[work] = (.inProgress, observable) - observable.on(.next(Update(work: work, kind: .inProgress))) + subject.on(.next(Update(work: work, kind: .inProgress))) switch work.kind { case .recognizer: pdfWorkerWebViewHandler.recognize(file: work.file) @@ -166,12 +160,10 @@ final class PDFWorkerController { guard let self else { return } DDLogInfo("PDFWorkerController: cancel all works") // Immediatelly release all PDFWorker web views. - let keys = pdfWorkerWebViewHandlersByPDFWork.keys - for key in keys { - pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: key)?.webViewHandler.removeFromSuperviewAsynchronously() - } + pdfWorkerWebViewHandlersByPDFWork.values.forEach { $0.webViewHandler.removeFromSuperviewAsynchronously() } + pdfWorkerWebViewHandlersByPDFWork = [:] // Then cancel actual works, and send cancelled event for each queued work. - let works = queue.keys + let works = subjectsByPDFWork.keys + Array(queue.keys) for work in works { cancel(work: work) } @@ -195,11 +187,14 @@ final class PDFWorkerController { } func cleanup(for work: PDFWork, maybe: (MaybeEvent>) -> Void) { - let observable = queue.removeValue(forKey: work).flatMap({ $0.observable }) + let subject = queue[work] ?? subjectsByPDFWork[work] + queue[work] = nil + subjectsByPDFWork[work] = nil + statesByPDFWork[work] = nil DDLogInfo("PDFWorkerController: cleaned up for \(work)") pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: work)?.webViewHandler.removeFromSuperviewAsynchronously() - if let observable { - maybe(.success(observable)) + if let subject { + maybe(.success(subject)) } else { maybe(.completed) } From b61031c2a981888ef9ce6c8149e9376eb3bb40dd Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Wed, 26 Feb 2025 07:54:21 +0200 Subject: [PATCH 23/33] Improve RecognizerController --- Zotero/Controllers/RecognizerController.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index e2aef09f5..f656cdca2 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -151,9 +151,6 @@ final class RecognizerController { } let state: TaskState = .enqueued queue[task] = (state, subject) - subject.subscribe(onNext: { [weak self] update in - self?.updatesSubject.on(.next(update)) - }).disposed(by: disposeBag) emmitUpdate(for: task, observable: subject, kind: .enqueued) startRecognitionIfNeeded() @@ -169,6 +166,7 @@ final class RecognizerController { latestUpdates[libraryId] = libraryLatestUpdates } observable.on(.next(update)) + updatesSubject.on(.next(update)) } private func startRecognitionIfNeeded() { From 93723417e5a47e48904dc811ac0ea47ebbf53417 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Wed, 26 Feb 2025 14:34:51 +0200 Subject: [PATCH 24/33] Improve RecognizerController --- Zotero/Controllers/RecognizerController.swift | 83 ++++++++----------- 1 file changed, 34 insertions(+), 49 deletions(-) diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index f656cdca2..5ed5e7117 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -111,9 +111,12 @@ final class RecognizerController { // Accessed only via accessQueue private static let maxConcurrentTasks: Int = 1 - private var queue: OrderedDictionary)> = [:] - private var latestUpdates: [LibraryIdentifier: [String: Update.Kind]] = [:] + // Using an OrderedDictionary instead of an Array, so we can O(1) when cancelling a work that is still queued. + private var queue: OrderedDictionary> = [:] + private var subjectsByTask: [Task: PublishSubject] = [:] private var lookupWebViewHandlersByTask: [Task: LookupWebViewHandler] = [:] + private var statesByTask: [Task: TaskState] = [:] + private var latestUpdates: [LibraryIdentifier: [String: Update.Kind]] = [:] // MARK: Object Lifecycle init( @@ -144,59 +147,40 @@ final class RecognizerController { let subject = PublishSubject() accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } - if let existingSubject = queue[task]?.1 { + if let existingSubject = subjectsByTask[task] { existingSubject.subscribe(subject) .disposed(by: disposeBag) return } - let state: TaskState = .enqueued - queue[task] = (state, subject) + queue[task] = subject + statesByTask[task] = .enqueued - emmitUpdate(for: task, observable: subject, kind: .enqueued) + emmitUpdate(for: task, subject: subject, kind: .enqueued) startRecognitionIfNeeded() } return subject.asObservable() } - private func emmitUpdate(for task: Task, observable: PublishSubject, kind: Update.Kind) { + private func emmitUpdate(for task: Task, subject: PublishSubject, kind: Update.Kind) { let update = Update(task: task, kind: kind) if case .createParentForItem(let libraryId, let key) = task.kind { var libraryLatestUpdates = latestUpdates[libraryId, default: [:]] libraryLatestUpdates[key] = kind latestUpdates[libraryId] = libraryLatestUpdates } - observable.on(.next(update)) + subject.on(.next(update)) updatesSubject.on(.next(update)) } private func startRecognitionIfNeeded() { - let runningTasksCount = queue.filter({ - switch $0.value.state { - case .enqueued: - return false - - case .recognitionInProgress, .remoteRecognitionInProgress, .identifiersLookupInProgress: - return true - } - }).count - guard runningTasksCount < Self.maxConcurrentTasks else { return } - let tasks = queue.keys - for task in tasks { - guard let (state, observable) = queue[task] else { continue } - switch state { - case .enqueued: - start(task: task, observable: observable) - startRecognitionIfNeeded() - return - - case .recognitionInProgress, .remoteRecognitionInProgress, .identifiersLookupInProgress: - break - } - } + guard subjectsByTask.count < Self.maxConcurrentTasks, !queue.isEmpty else { return } + let (task, subject) = queue.removeFirst() + start(task: task, subject: subject) - func start(task: Task, observable: PublishSubject) { - queue[task] = (.recognitionInProgress, observable) - emmitUpdate(for: task, observable: observable, kind: .recognitionInProgress) + func start(task: Task, subject: PublishSubject) { + subjectsByTask[task] = subject + statesByTask[task] = .recognitionInProgress + emmitUpdate(for: task, subject: subject, kind: .recognitionInProgress) pdfWorkerController.queue(work: PDFWorkerController.PDFWork(file: task.file, kind: .recognizer)) .subscribe(onNext: { update in @@ -237,12 +221,12 @@ final class RecognizerController { private func startRemoteRecognition(for task: Task, with data: [String: Any]) { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } - guard let (_, observable) = queue[task] else { + guard let subject = subjectsByTask[task] else { startRecognitionIfNeeded() return } - queue[task] = (.remoteRecognitionInProgress(data: data), observable) - emmitUpdate(for: task, observable: observable, kind: .remoteRecognitionInProgress(data: data)) + statesByTask[task] = .remoteRecognitionInProgress(data: data) + emmitUpdate(for: task, subject: subject, kind: .remoteRecognitionInProgress(data: data)) apiClient.send(request: RecognizerRequest(parameters: data)).subscribe( onSuccess: { (response: (RemoteRecognizerResponse, HTTPURLResponse)) in @@ -302,7 +286,7 @@ final class RecognizerController { } func _enqueueNextIdentifierLookup(for task: Task) { - guard let (state, _) = queue[task] else { + guard let state = statesByTask[task] else { startRecognitionIfNeeded() return } @@ -317,7 +301,7 @@ final class RecognizerController { private func lookupNextIdentifier(for task: Task, with response: RemoteRecognizerResponse, pendingIdentifiers: [RecognizerIdentifier]) { DDLogInfo("RecognizerController: \(task) - looking up next identifier from \(pendingIdentifiers)") - guard let (_, observable) = queue[task] else { + guard let subject = subjectsByTask[task] else { startRecognitionIfNeeded() return } @@ -328,7 +312,7 @@ final class RecognizerController { } var remainingIdentifiers = pendingIdentifiers let identifier = remainingIdentifiers.removeFirst() - queue[task] = (.identifiersLookupInProgress(response: response, currentIdentifier: identifier, pendingIdentifiers: remainingIdentifiers), observable) + statesByTask[task] = .identifiersLookupInProgress(response: response, currentIdentifier: identifier, pendingIdentifiers: remainingIdentifiers) switch identifier { case .arXiv, .doi, .isbn: @@ -344,7 +328,7 @@ final class RecognizerController { enqueueNextIdentifierLookup(for: task) return } - emmitUpdate(for: task, observable: observable, kind: .identifierLookupInProgress(response: response, identifier: identifier)) + emmitUpdate(for: task, subject: subject, kind: .identifierLookupInProgress(response: response, identifier: identifier)) lookupWebViewHandler.lookUp(identifier: identifier) func getLookupWebViewHandler(for task: Task) -> LookupWebViewHandler? { @@ -514,12 +498,10 @@ final class RecognizerController { guard let self else { return } DDLogInfo("RecognizerController: cancel all tasks") // Immediatelly release all lookup web views. - let keys = lookupWebViewHandlersByTask.keys - for key in keys { - lookupWebViewHandlersByTask.removeValue(forKey: key)?.webViewHandler.removeFromSuperviewAsynchronously() - } + lookupWebViewHandlersByTask.values.forEach { $0.webViewHandler.removeFromSuperviewAsynchronously() } + lookupWebViewHandlersByTask = [:] // Then cancel actual tasks, and send cancelled event for each queued task. - let tasks = queue.keys + let tasks = subjectsByTask.keys + Array(queue.keys) for task in tasks { cancel(task: task) } @@ -543,15 +525,18 @@ final class RecognizerController { } func cleanup(for task: Task, maybe: (MaybeEvent>) -> Void) { - let observable = queue.removeValue(forKey: task).flatMap({ $0.observable }) + let subject = queue[task] ?? subjectsByTask[task] + queue[task] = nil + subjectsByTask[task] = nil + statesByTask[task] = nil if case .createParentForItem(let libraryId, let key) = task.kind, var libraryLatestUpdates = latestUpdates[libraryId] { libraryLatestUpdates[key] = nil latestUpdates[libraryId] = libraryLatestUpdates } DDLogInfo("RecognizerController: \(task) - cleaned up") lookupWebViewHandlersByTask.removeValue(forKey: task)?.webViewHandler.removeFromSuperviewAsynchronously() - if let observable { - maybe(.success(observable)) + if let subject { + maybe(.success(subject)) } else { maybe(.completed) } From 028cc93aa9ddb872018ea66be361ba0ea11f7f57 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Wed, 26 Feb 2025 15:06:02 +0200 Subject: [PATCH 25/33] Improve PDFWorkerController & RecognizerController relay bindings --- Zotero/Controllers/PDFWorkerController.swift | 3 +-- Zotero/Controllers/RecognizerController.swift | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index e7b6ec3f5..c2fc22cd4 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -73,8 +73,7 @@ final class PDFWorkerController { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } if let existingSubject = subjectsByPDFWork[work] { - existingSubject.subscribe(subject) - .disposed(by: disposeBag) + existingSubject.bind(to: subject).disposed(by: disposeBag) return } queue[work] = subject diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index 5ed5e7117..26aba09e6 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -148,12 +148,12 @@ final class RecognizerController { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } if let existingSubject = subjectsByTask[task] { - existingSubject.subscribe(subject) - .disposed(by: disposeBag) + existingSubject.bind(to: subject).disposed(by: disposeBag) return } queue[task] = subject statesByTask[task] = .enqueued + subject.bind(to: updatesSubject).disposed(by: disposeBag) emmitUpdate(for: task, subject: subject, kind: .enqueued) startRecognitionIfNeeded() @@ -169,7 +169,6 @@ final class RecognizerController { latestUpdates[libraryId] = libraryLatestUpdates } subject.on(.next(update)) - updatesSubject.on(.next(update)) } private func startRecognitionIfNeeded() { From 0c3c2a35c8a29ca51a3acb76c04dd4d4f2930c93 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Wed, 26 Feb 2025 15:23:28 +0200 Subject: [PATCH 26/33] Simplify RecognizerController.Update.Kind --- ZShare/ViewModels/ExtensionViewModel.swift | 2 +- Zotero/Controllers/RecognizerController.swift | 8 ++------ Zotero/Scenes/Detail/Items/Models/ItemCellModel.swift | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/ZShare/ViewModels/ExtensionViewModel.swift b/ZShare/ViewModels/ExtensionViewModel.swift index 86e4ebee7..fa4cdc9e2 100644 --- a/ZShare/ViewModels/ExtensionViewModel.swift +++ b/ZShare/ViewModels/ExtensionViewModel.swift @@ -549,7 +549,7 @@ final class ExtensionViewModel { case .cancelled: updateState(with: .processed) - case .recognitionInProgress, .remoteRecognitionInProgress, .identifierLookupInProgress: + case .inProgress: updateState(with: .decoding) case .translated(let item): diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index 26aba09e6..ae32fb1b1 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -72,9 +72,7 @@ final class RecognizerController { case failed(Error) case cancelled case enqueued - case recognitionInProgress - case remoteRecognitionInProgress(data: [String: Any]) - case identifierLookupInProgress(response: RemoteRecognizerResponse, identifier: String) + case inProgress case translated(itemResponse: ItemResponse) case createdParent(item: RItem) } @@ -179,7 +177,7 @@ final class RecognizerController { func start(task: Task, subject: PublishSubject) { subjectsByTask[task] = subject statesByTask[task] = .recognitionInProgress - emmitUpdate(for: task, subject: subject, kind: .recognitionInProgress) + emmitUpdate(for: task, subject: subject, kind: .inProgress) pdfWorkerController.queue(work: PDFWorkerController.PDFWork(file: task.file, kind: .recognizer)) .subscribe(onNext: { update in @@ -225,7 +223,6 @@ final class RecognizerController { return } statesByTask[task] = .remoteRecognitionInProgress(data: data) - emmitUpdate(for: task, subject: subject, kind: .remoteRecognitionInProgress(data: data)) apiClient.send(request: RecognizerRequest(parameters: data)).subscribe( onSuccess: { (response: (RemoteRecognizerResponse, HTTPURLResponse)) in @@ -327,7 +324,6 @@ final class RecognizerController { enqueueNextIdentifierLookup(for: task) return } - emmitUpdate(for: task, subject: subject, kind: .identifierLookupInProgress(response: response, identifier: identifier)) lookupWebViewHandler.lookUp(identifier: identifier) func getLookupWebViewHandler(for task: Task) -> LookupWebViewHandler? { diff --git a/Zotero/Scenes/Detail/Items/Models/ItemCellModel.swift b/Zotero/Scenes/Detail/Items/Models/ItemCellModel.swift index ea86f895c..bdc0e076f 100644 --- a/Zotero/Scenes/Detail/Items/Models/ItemCellModel.swift +++ b/Zotero/Scenes/Detail/Items/Models/ItemCellModel.swift @@ -142,7 +142,7 @@ struct ItemCellModel { text = creatorSummary(for: item) animated = false - case .enqueued, .recognitionInProgress, .remoteRecognitionInProgress, .identifierLookupInProgress, .translated: + case .enqueued, .inProgress, .translated: text = L10n.Items.retrievingMetadata animated = true } From 7f2925b003dd7a39880e0ff4b70b6583f4a72efe Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Tue, 4 Mar 2025 16:37:30 +0200 Subject: [PATCH 27/33] Improve code --- .../TranslationWebViewHandler.swift | 2 +- Zotero/Controllers/PDFWorkerController.swift | 39 ++++++++----------- .../LookupWebViewHandler.swift | 16 ++++---- .../PDFWorkerWebViewHandler.swift | 35 ++++++++--------- .../Web View Handling/WebViewHandler.swift | 9 ----- 5 files changed, 43 insertions(+), 58 deletions(-) diff --git a/ZShare/Controllers/TranslationWebViewHandler.swift b/ZShare/Controllers/TranslationWebViewHandler.swift index 075cecdf2..34eb3a1fe 100644 --- a/ZShare/Controllers/TranslationWebViewHandler.swift +++ b/ZShare/Controllers/TranslationWebViewHandler.swift @@ -269,7 +269,7 @@ final class TranslationWebViewHandler { self.observable.on(.error(Error.noSuccessfulTranslators)) case .log: - DDLogInfo("JSLOG: \(body)") + DDLogInfo("TranslationWebViewHandler: JSLOG - \(body)") } } } diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index c2fc22cd4..431730e79 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -37,11 +37,6 @@ final class PDFWorkerController { let kind: Kind } - enum PDFWorkState { - case enqueued - case inProgress - } - // MARK: Properties private let dispatchSpecificKey: DispatchSpecificKey private let accessQueueLabel: String @@ -52,11 +47,9 @@ final class PDFWorkerController { // Accessed only via accessQueue private static let maxConcurrentPDFWorkers: Int = 1 - // Using an OrderedDictionary instead of an Array, so we can O(1) when cancelling a work that is still queued. - private var queue: OrderedDictionary> = [:] + private var queue: [PDFWork] = [] private var subjectsByPDFWork: [PDFWork: PublishSubject] = [:] private var pdfWorkerWebViewHandlersByPDFWork: [PDFWork: PDFWorkerWebViewHandler] = [:] - private var statesByPDFWork: [PDFWork: PDFWorkState] = [:] // MARK: Object Lifecycle init() { @@ -76,9 +69,8 @@ final class PDFWorkerController { existingSubject.bind(to: subject).disposed(by: disposeBag) return } - queue[work] = subject + queue.append(work) subjectsByPDFWork[work] = subject - statesByPDFWork[work] = .enqueued startWorkIfNeeded() } @@ -87,7 +79,11 @@ final class PDFWorkerController { private func startWorkIfNeeded() { guard pdfWorkerWebViewHandlersByPDFWork.count < Self.maxConcurrentPDFWorkers, !queue.isEmpty else { return } - let (work, subject) = queue.removeFirst() + let work = queue.removeFirst() + guard let subject = subjectsByPDFWork[work] else { + startWorkIfNeeded() + return + } start(work: work, subject: subject) startWorkIfNeeded() @@ -110,7 +106,6 @@ final class PDFWorkerController { return } - statesByPDFWork[work] = .inProgress setupObserver(for: pdfWorkerWebViewHandler) subject.on(.next(Update(work: work, kind: .inProgress))) switch work.kind { @@ -125,11 +120,11 @@ final class PDFWorkerController { pdfWorkerWebViewHandler.observable .subscribe(onNext: { [weak self] in guard let self else { return } - process(result: $0) + process(result: $0, self: self) }) .disposed(by: disposeBag) - func process(result: Result) { + func process(result: Result, self: PDFWorkerController) { switch result { case .success(let data): switch data { @@ -162,7 +157,7 @@ final class PDFWorkerController { pdfWorkerWebViewHandlersByPDFWork.values.forEach { $0.webViewHandler.removeFromSuperviewAsynchronously() } pdfWorkerWebViewHandlersByPDFWork = [:] // Then cancel actual works, and send cancelled event for each queued work. - let works = subjectsByPDFWork.keys + Array(queue.keys) + let works = subjectsByPDFWork.keys + queue for work in works { cancel(work: work) } @@ -176,20 +171,20 @@ final class PDFWorkerController { return Disposables.create() } if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { - cleanup(for: work, maybe: maybe) + cleanup(for: work, maybe: maybe, self: self) } else { - accessQueue.async(flags: .barrier) { - cleanup(for: work, maybe: maybe) + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { return } + cleanup(for: work, maybe: maybe, self: self) } } return Disposables.create() } - func cleanup(for work: PDFWork, maybe: (MaybeEvent>) -> Void) { - let subject = queue[work] ?? subjectsByPDFWork[work] - queue[work] = nil + func cleanup(for work: PDFWork, maybe: (MaybeEvent>) -> Void, self: PDFWorkerController) { + let subject = subjectsByPDFWork[work] + queue.removeAll(where: { $0 == work }) subjectsByPDFWork[work] = nil - statesByPDFWork[work] = nil DDLogInfo("PDFWorkerController: cleaned up for \(work)") pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: work)?.webViewHandler.removeFromSuperviewAsynchronously() if let subject { diff --git a/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift b/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift index 18f399be3..736bff5de 100644 --- a/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift @@ -40,7 +40,7 @@ final class LookupWebViewHandler { case item([String: Any]) } - private enum InitializationResult { + private enum InitializationState { case initialized case inProgress case failed(Swift.Error) @@ -51,14 +51,14 @@ final class LookupWebViewHandler { private let disposeBag: DisposeBag let observable: PublishSubject> - private var isLoading: BehaviorRelay + private var initializationState: BehaviorRelay init(webView: WKWebView, translatorsController: TranslatorsAndStylesController) { self.translatorsController = translatorsController webViewHandler = WebViewHandler(webView: webView, javascriptHandlers: JSHandlers.allCases.map({ $0.rawValue })) observable = PublishSubject() disposeBag = DisposeBag() - isLoading = BehaviorRelay(value: .inProgress) + initializationState = BehaviorRelay(value: .inProgress) webViewHandler.receivedMessageHandler = { [weak self] name, body in self?.receiveMessage(name: name, body: body) @@ -69,10 +69,10 @@ final class LookupWebViewHandler { .observe(on: MainScheduler.instance) .subscribe(onSuccess: { [weak self] _ in DDLogInfo("LookupWebViewHandler: initialization succeeded") - self?.isLoading.accept(.initialized) + self?.initializationState.accept(.initialized) }, onFailure: { [weak self] error in DDLogInfo("LookupWebViewHandler: initialization failed - \(error)") - self?.isLoading.accept(.failed(error)) + self?.initializationState.accept(.failed(error)) }) .disposed(by: disposeBag) @@ -134,7 +134,7 @@ final class LookupWebViewHandler { } func lookUp(identifier: String) { - switch isLoading.value { + switch initializationState.value { case .failed(let error): observable.on(.next(.failure(error))) @@ -142,7 +142,7 @@ final class LookupWebViewHandler { performLookUp(for: identifier) case .inProgress: - isLoading.filter { result in + initializationState.filter { result in switch result { case .inProgress: return false @@ -210,7 +210,7 @@ final class LookupWebViewHandler { observable.on(.next(.success(.identifiers(rawData)))) case .log: - DDLogInfo("JSLOG: \(body)") + DDLogInfo("LookupWebViewHandler: JSLOG - \(body)") case .request: guard let body = body as? [String: Any], diff --git a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift index 11d8dff38..eee49a3b7 100644 --- a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift @@ -33,7 +33,7 @@ final class PDFWorkerWebViewHandler { case fullText(data: [String: Any]) } - private enum InitializationResult { + private enum InitializationState { case initialized case inProgress case failed(Swift.Error) @@ -43,13 +43,13 @@ final class PDFWorkerWebViewHandler { private let disposeBag: DisposeBag let observable: PublishSubject> - private var isLoading: BehaviorRelay + private var initializationState: BehaviorRelay init(webView: WKWebView) { webViewHandler = WebViewHandler(webView: webView, javascriptHandlers: JSHandlers.allCases.map({ $0.rawValue })) observable = PublishSubject() disposeBag = DisposeBag() - isLoading = BehaviorRelay(value: .inProgress) + initializationState = BehaviorRelay(value: .inProgress) webViewHandler.receivedMessageHandler = { [weak self] name, body in self?.receiveMessage(name: name, body: body) @@ -60,19 +60,16 @@ final class PDFWorkerWebViewHandler { .observe(on: MainScheduler.instance) .subscribe(onSuccess: { [weak self] _ in DDLogInfo("PDFWorkerWebViewHandler: initialization succeeded") - self?.isLoading.accept(.initialized) + self?.initializationState.accept(.initialized) }, onFailure: { [weak self] error in DDLogInfo("PDFWorkerWebViewHandler: initialization failed - \(error)") - self?.isLoading.accept(.failed(error)) + self?.initializationState.accept(.failed(error)) }) .disposed(by: disposeBag) - func initialize() -> Single { + func initialize() -> Single<()> { DDLogInfo("PDFWorkerWebViewHandler: initialize web view") return loadIndex() - .flatMap { _ in - Single.just(Void()) - } func loadIndex() -> Single<()> { guard let indexUrl = Bundle.main.url(forResource: "worker", withExtension: "html") else { @@ -84,7 +81,7 @@ final class PDFWorkerWebViewHandler { } private func performCall(completion: @escaping () -> Void) { - switch isLoading.value { + switch initializationState.value { case .failed(let error): observable.on(.next(.failure(error))) @@ -92,7 +89,7 @@ final class PDFWorkerWebViewHandler { completion() case .inProgress: - isLoading.filter { result in + initializationState.filter { result in switch result { case .inProgress: return false @@ -121,11 +118,12 @@ final class PDFWorkerWebViewHandler { func recognize(file: FileData) { let filePath = file.createUrl().path - performCall { - performRecognize(for: filePath) + performCall { [weak self] in + guard let self else { return } + performRecognize(for: filePath, self: self) } - func performRecognize(for path: String) { + func performRecognize(for path: String, self: PDFWorkerWebViewHandler) { DDLogInfo("PDFWorkerWebViewHandler: call recognize js") return webViewHandler.call(javascript: "recognize('\(path)');") .subscribe(on: MainScheduler.instance) @@ -140,11 +138,12 @@ final class PDFWorkerWebViewHandler { func getFullText(file: FileData) { let filePath = file.createUrl().path - performCall { - performGetFullText(for: filePath) + performCall { [weak self] in + guard let self else { return } + performGetFullText(for: filePath, self: self) } - func performGetFullText(for path: String) { + func performGetFullText(for path: String, self: PDFWorkerWebViewHandler) { DDLogInfo("PDFWorkerWebViewHandler: call getFullText js") return webViewHandler.call(javascript: "getFullText('\(path)');") .subscribe(on: MainScheduler.instance) @@ -172,7 +171,7 @@ final class PDFWorkerWebViewHandler { observable.on(.next(.success(.recognizerData(data: data)))) case .log: - DDLogInfo("JSLOG: \(body)") + DDLogInfo("PDFWorkerWebViewHandler: JSLOG - \(body)") } } } diff --git a/Zotero/Controllers/Web View Handling/WebViewHandler.swift b/Zotero/Controllers/Web View Handling/WebViewHandler.swift index e9e246c5e..ad480d9a7 100644 --- a/Zotero/Controllers/Web View Handling/WebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/WebViewHandler.swift @@ -78,15 +78,6 @@ final class WebViewHandler: NSObject { return createWebLoadedSingle() } - func loadHTMLString(_ string: String, baseURL: URL?) -> Single<()> { - guard let webView else { - DDLogError("WebViewHandler: web view is nil") - return .error(Error.webViewMissing) - } - webView.loadHTMLString(string, baseURL: baseURL) - return createWebLoadedSingle() - } - func load(webUrl: URL) -> Single<()> { guard let webView else { DDLogError("WebViewHandler: web view is nil") From 2dfdcfccc11e7fff2fe352ae8932d45aa3ceb469 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Tue, 4 Mar 2025 17:50:14 +0200 Subject: [PATCH 28/33] Improve RecognizerController code --- Zotero/Controllers/RecognizerController.swift | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index ae32fb1b1..15a9d233f 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -180,12 +180,13 @@ final class RecognizerController { emmitUpdate(for: task, subject: subject, kind: .inProgress) pdfWorkerController.queue(work: PDFWorkerController.PDFWork(file: task.file, kind: .recognizer)) - .subscribe(onNext: { update in - process(update: update) + .subscribe(onNext: { [weak self] update in + guard let self else { return } + process(update: update, self: self) }) .disposed(by: disposeBag) - func process(update: PDFWorkerController.Update) { + func process(update: PDFWorkerController.Update, self: RecognizerController) { switch update.kind { case .failed: DDLogError("RecognizerController: \(task) - recognizer failed") @@ -218,16 +219,17 @@ final class RecognizerController { private func startRemoteRecognition(for task: Task, with data: [String: Any]) { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } - guard let subject = subjectsByTask[task] else { + guard subjectsByTask[task] != nil else { startRecognitionIfNeeded() return } statesByTask[task] = .remoteRecognitionInProgress(data: data) apiClient.send(request: RecognizerRequest(parameters: data)).subscribe( - onSuccess: { (response: (RemoteRecognizerResponse, HTTPURLResponse)) in + onSuccess: { [weak self] (response: (RemoteRecognizerResponse, HTTPURLResponse)) in + guard let self else { return } DDLogInfo("RecognizerController: \(task) - remote recognizer response received") - process(response: response.0) + process(response: response.0, self: self) }, onFailure: { [weak self] error in guard let self else { return } @@ -239,7 +241,7 @@ final class RecognizerController { .disposed(by: disposeBag) } - func process(response: RemoteRecognizerResponse) { + func process(response: RemoteRecognizerResponse, self: RecognizerController) { var identifiers: [RecognizerIdentifier] = [] if let identifier = response.arxiv { identifiers.append(.arXiv(identifier)) @@ -273,15 +275,15 @@ final class RecognizerController { } ) { if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { - _enqueueNextIdentifierLookup(for: task) + _enqueueNextIdentifierLookup(for: task, self: self) } else { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } - _enqueueNextIdentifierLookup(for: task) + _enqueueNextIdentifierLookup(for: task, self: self) } } - func _enqueueNextIdentifierLookup(for task: Task) { + func _enqueueNextIdentifierLookup(for task: Task, self: RecognizerController) { guard let state = statesByTask[task] else { startRecognitionIfNeeded() return @@ -297,7 +299,7 @@ final class RecognizerController { private func lookupNextIdentifier(for task: Task, with response: RemoteRecognizerResponse, pendingIdentifiers: [RecognizerIdentifier]) { DDLogInfo("RecognizerController: \(task) - looking up next identifier from \(pendingIdentifiers)") - guard let subject = subjectsByTask[task] else { + guard subjectsByTask[task] != nil else { startRecognitionIfNeeded() return } @@ -346,10 +348,13 @@ final class RecognizerController { func setupObserver(for lookupWebViewHandler: LookupWebViewHandler) { lookupWebViewHandler.observable - .subscribe(onNext: { process(result: $0) }) + .subscribe(onNext: { [weak self] in + guard let self else { return } + process(result: $0, self: self) + }) .disposed(by: disposeBag) - func process(result: Result) { + func process(result: Result, self: RecognizerController) { switch result { case .success(let data): switch data { @@ -510,16 +515,17 @@ final class RecognizerController { return Disposables.create() } if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { - cleanup(for: task, maybe: maybe) + cleanup(for: task, maybe: maybe, self: self) } else { - accessQueue.async(flags: .barrier) { - cleanup(for: task, maybe: maybe) + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { return } + cleanup(for: task, maybe: maybe, self: self) } } return Disposables.create() } - func cleanup(for task: Task, maybe: (MaybeEvent>) -> Void) { + func cleanup(for task: Task, maybe: (MaybeEvent>) -> Void, self: RecognizerController) { let subject = queue[task] ?? subjectsByTask[task] queue[task] = nil subjectsByTask[task] = nil From 72cfdfdc4e5572c3b8ef5229be6421fe451d9e7a Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Tue, 4 Mar 2025 18:13:14 +0200 Subject: [PATCH 29/33] Improve code --- Zotero/Controllers/PDFWorkerController.swift | 41 ++++-------- Zotero/Controllers/RecognizerController.swift | 63 +++++++------------ 2 files changed, 34 insertions(+), 70 deletions(-) diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index 431730e79..c925a04f7 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -101,8 +101,7 @@ final class PDFWorkerController { } guard let pdfWorkerWebViewHandler else { DDLogError("PDFWorkerController: can't create PDFWorkerWebViewHandler instance") - cleanupPDFWorker(for: work).subscribe(onSuccess: { $0.on(.next(Update(work: work, kind: .failed))) }) - .disposed(by: disposeBag) + cleanupPDFWorker(for: work) { $0?.on(.next(Update(work: work, kind: .failed))) } return } @@ -129,14 +128,12 @@ final class PDFWorkerController { case .success(let data): switch data { case .recognizerData(let data), .fullText(let data): - cleanupPDFWorker(for: work).subscribe(onSuccess: { $0.on(.next(Update(work: work, kind: .extractedData(data: data)))) }) - .disposed(by: disposeBag) + cleanupPDFWorker(for: work) { $0?.on(.next(Update(work: work, kind: .extractedData(data: data)))) } } case .failure(let error): DDLogError("PDFWorkerController: recognizer failed - \(error)") - cleanupPDFWorker(for: work).subscribe(onSuccess: { $0.on(.next(Update(work: work, kind: .failed))) }) - .disposed(by: disposeBag) + cleanupPDFWorker(for: work) { $0?.on(.next(Update(work: work, kind: .failed))) } } } } @@ -145,8 +142,7 @@ final class PDFWorkerController { func cancel(work: PDFWork) { DDLogInfo("PDFWorkerController: cancelled \(work)") - cleanupPDFWorker(for: work).subscribe(onSuccess: { $0.on(.next(Update(work: work, kind: .cancelled))) }) - .disposed(by: disposeBag) + cleanupPDFWorker(for: work) { $0?.on(.next(Update(work: work, kind: .cancelled))) } } func cancellAllWorks() { @@ -164,34 +160,23 @@ final class PDFWorkerController { } } - private func cleanupPDFWorker(for work: PDFWork) -> Maybe> { - return Maybe.create { [weak self] maybe in - guard let self else { - maybe(.completed) - return Disposables.create() - } - if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { - cleanup(for: work, maybe: maybe, self: self) - } else { - accessQueue.async(flags: .barrier) { [weak self] in - guard let self else { return } - cleanup(for: work, maybe: maybe, self: self) - } + private func cleanupPDFWorker(for work: PDFWork, completion: ((_ subject: PublishSubject?) -> Void)?) { + if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { + cleanup(for: work, completion: completion, self: self) + } else { + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { return } + cleanup(for: work, completion: completion, self: self) } - return Disposables.create() } - func cleanup(for work: PDFWork, maybe: (MaybeEvent>) -> Void, self: PDFWorkerController) { + func cleanup(for work: PDFWork, completion: ((_ subject: PublishSubject?) -> Void)?, self: PDFWorkerController) { let subject = subjectsByPDFWork[work] queue.removeAll(where: { $0 == work }) subjectsByPDFWork[work] = nil DDLogInfo("PDFWorkerController: cleaned up for \(work)") pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: work)?.webViewHandler.removeFromSuperviewAsynchronously() - if let subject { - maybe(.success(subject)) - } else { - maybe(.completed) - } + completion?(subject) startWorkIfNeeded() } } diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index 15a9d233f..9baa16365 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -190,12 +190,10 @@ final class RecognizerController { switch update.kind { case .failed: DDLogError("RecognizerController: \(task) - recognizer failed") - cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.recognizerFailed)))) }) - .disposed(by: disposeBag) + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.recognizerFailed)))) } case .cancelled: - cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .cancelled))) }) - .disposed(by: disposeBag) + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .cancelled))) } case .inProgress: break @@ -208,8 +206,7 @@ final class RecognizerController { case .fullText: DDLogError("RecognizerController: \(task) - PDF worker error") - cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.pdfWorkerError)))) }) - .disposed(by: disposeBag) + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.pdfWorkerError)))) } } } } @@ -234,8 +231,7 @@ final class RecognizerController { onFailure: { [weak self] error in guard let self else { return } DDLogError("RecognizerController: \(task) - remote recognizer request failed: \(error)") - cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(error as! Error)))) }) - .disposed(by: disposeBag) + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(error as! Error)))) } } ) .disposed(by: disposeBag) @@ -256,8 +252,7 @@ final class RecognizerController { identifiers.append(.title(identifier)) } guard !identifiers.isEmpty else { - cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.remoteRecognizerFailed)))) }) - .disposed(by: disposeBag) + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.remoteRecognizerFailed)))) } return } enqueueNextIdentifierLookup(for: task) { state in @@ -289,8 +284,7 @@ final class RecognizerController { return } guard let (response, pendingIdentifiers) = getResponseAndIdentifiers(state) else { - cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) }) - .disposed(by: disposeBag) + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) } return } lookupNextIdentifier(for: task, with: response, pendingIdentifiers: pendingIdentifiers) @@ -304,8 +298,7 @@ final class RecognizerController { return } guard !pendingIdentifiers.isEmpty else { - cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .failed(.noRemainingIdentifiersForLookup)))) }) - .disposed(by: disposeBag) + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.noRemainingIdentifiersForLookup)))) } return } var remainingIdentifiers = pendingIdentifiers @@ -453,8 +446,7 @@ final class RecognizerController { func createParentIfNeeded(for task: Task, with itemResponse: ItemResponse, schemaController: SchemaController, dateParser: DateParser) { switch task.kind { case .simple: - cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .translated(itemResponse: itemResponse)))) }) - .disposed(by: disposeBag) + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .translated(itemResponse: itemResponse)))) } case .createParentForItem(let libraryId, let key): backgroundQueue.async { [weak self] in @@ -476,12 +468,11 @@ final class RecognizerController { DDLogError("RecognizerController: can't create parent for item - \(error)") update = Update(task: task, kind: .failed(error as! Error)) } - cleanupTask(for: task).subscribe(onSuccess: { + cleanupTask(for: task) { if let update { - $0.on(.next(update)) + $0?.on(.next(update)) } - }) - .disposed(by: disposeBag) + } } } } @@ -489,8 +480,7 @@ final class RecognizerController { func cancel(task: Task) { DDLogInfo("RecognizerController: cancelled \(task)") - cleanupTask(for: task).subscribe(onSuccess: { $0.on(.next(Update(task: task, kind: .cancelled))) }) - .disposed(by: disposeBag) + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .cancelled))) } } func cancellAllTasks() { @@ -508,24 +498,17 @@ final class RecognizerController { } } - private func cleanupTask(for task: Task) -> Maybe> { - return Maybe.create { [weak self] maybe in - guard let self else { - maybe(.completed) - return Disposables.create() - } - if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { - cleanup(for: task, maybe: maybe, self: self) - } else { - accessQueue.async(flags: .barrier) { [weak self] in - guard let self else { return } - cleanup(for: task, maybe: maybe, self: self) - } + private func cleanupTask(for task: Task, completion: ((_ subject: PublishSubject?) -> Void)?) { + if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { + cleanup(for: task, completion: completion, self: self) + } else { + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { return } + cleanup(for: task, completion: completion, self: self) } - return Disposables.create() } - func cleanup(for task: Task, maybe: (MaybeEvent>) -> Void, self: RecognizerController) { + func cleanup(for task: Task, completion: ((_ subject: PublishSubject?) -> Void)?, self: RecognizerController) { let subject = queue[task] ?? subjectsByTask[task] queue[task] = nil subjectsByTask[task] = nil @@ -536,11 +519,7 @@ final class RecognizerController { } DDLogInfo("RecognizerController: \(task) - cleaned up") lookupWebViewHandlersByTask.removeValue(forKey: task)?.webViewHandler.removeFromSuperviewAsynchronously() - if let subject { - maybe(.success(subject)) - } else { - maybe(.completed) - } + completion?(subject) startRecognitionIfNeeded() } } From cc6f493c9692aad3c0ec45eb18fc61e81dab5448 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Tue, 4 Mar 2025 18:58:27 +0200 Subject: [PATCH 30/33] Improve PDFWorkerWebViewHandler code --- .../PDFWorkerWebViewHandler.swift | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift index eee49a3b7..65fa7fc69 100644 --- a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift @@ -80,17 +80,17 @@ final class PDFWorkerWebViewHandler { } } - private func performCall(completion: @escaping () -> Void) { + private func performCall() -> Single<()> { switch initializationState.value { case .failed(let error): - observable.on(.next(.failure(error))) + return .error(error) case .initialized: - completion() + return .just(()) case .inProgress: - initializationState.filter { result in - switch result { + return initializationState.filter { state in + switch state { case .inProgress: return false @@ -99,29 +99,31 @@ final class PDFWorkerWebViewHandler { } } .first() - .subscribe(onSuccess: { [weak self] result in - guard let self, let result else { return } - switch result { + .flatMap({ state -> Single<()> in + switch state { case .failed(let error): - observable.on(.next(.failure(error))) + return .error(error) case .initialized: - completion() + return .just(()) - case .inProgress: - break + case .inProgress, .none: + // Should never happen. + return .never() } }) - .disposed(by: disposeBag) } } func recognize(file: FileData) { let filePath = file.createUrl().path - performCall { [weak self] in + performCall().subscribe(onSuccess: { [weak self] in guard let self else { return } performRecognize(for: filePath, self: self) - } + }, onFailure: { [weak self] error in + self?.observable.on(.next(.failure(error))) + }) + .disposed(by: disposeBag) func performRecognize(for path: String, self: PDFWorkerWebViewHandler) { DDLogInfo("PDFWorkerWebViewHandler: call recognize js") @@ -138,10 +140,13 @@ final class PDFWorkerWebViewHandler { func getFullText(file: FileData) { let filePath = file.createUrl().path - performCall { [weak self] in + performCall().subscribe(onSuccess: { [weak self] in guard let self else { return } performGetFullText(for: filePath, self: self) - } + }, onFailure: { [weak self] error in + self?.observable.on(.next(.failure(error))) + }) + .disposed(by: disposeBag) func performGetFullText(for path: String, self: PDFWorkerWebViewHandler) { DDLogInfo("PDFWorkerWebViewHandler: call getFullText js") From 05d319b7e911806ec7a7cf1d4a0c5997c924fa4f Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Wed, 5 Mar 2025 12:51:15 +0200 Subject: [PATCH 31/33] Improve code --- .../LookupWebViewHandler.swift | 51 ++++++++----------- .../PDFWorkerWebViewHandler.swift | 49 ++++++++---------- 2 files changed, 41 insertions(+), 59 deletions(-) diff --git a/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift b/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift index 736bff5de..004c14409 100644 --- a/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift @@ -134,39 +134,30 @@ final class LookupWebViewHandler { } func lookUp(identifier: String) { - switch initializationState.value { - case .failed(let error): - observable.on(.next(.failure(error))) + initializationState.filter { result in + switch result { + case .inProgress: + return false - case .initialized: - performLookUp(for: identifier) - - case .inProgress: - initializationState.filter { result in - switch result { - case .inProgress: - return false - - case .initialized, .failed: - return true - } + case .initialized, .failed: + return true } - .first() - .subscribe(onSuccess: { [weak self] result in - guard let self, let result else { return } - switch result { - case .failed(let error): - observable.on(.next(.failure(error))) - - case .initialized: - performLookUp(for: identifier) - - case .inProgress: - break - } - }) - .disposed(by: disposeBag) } + .first() + .subscribe(onSuccess: { [weak self] result in + guard let self, let result else { return } + switch result { + case .failed(let error): + observable.on(.next(.failure(error))) + + case .initialized: + performLookUp(for: identifier) + + case .inProgress: + break + } + }) + .disposed(by: disposeBag) func performLookUp(for identifier: String) { DDLogInfo("LookupWebViewHandler: call translate js") diff --git a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift index 65fa7fc69..0ce242b4a 100644 --- a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift @@ -81,37 +81,28 @@ final class PDFWorkerWebViewHandler { } private func performCall() -> Single<()> { - switch initializationState.value { - case .failed(let error): - return .error(error) + return initializationState.filter { state in + switch state { + case .inProgress: + return false - case .initialized: - return .just(()) - - case .inProgress: - return initializationState.filter { state in - switch state { - case .inProgress: - return false - - case .initialized, .failed: - return true - } + case .initialized, .failed: + return true + } + } + .first() + .flatMap { state -> Single<()> in + switch state { + case .failed(let error): + return .error(error) + + case .initialized: + return .just(()) + + case .inProgress, .none: + // Should never happen. + return .never() } - .first() - .flatMap({ state -> Single<()> in - switch state { - case .failed(let error): - return .error(error) - - case .initialized: - return .just(()) - - case .inProgress, .none: - // Should never happen. - return .never() - } - }) } } From e4b5490fcdf65fbc2574317b826969ee98f8f417 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Wed, 5 Mar 2025 14:36:31 +0200 Subject: [PATCH 32/33] Improve code --- Zotero/Controllers/PDFWorkerController.swift | 31 ++-- Zotero/Controllers/RecognizerController.swift | 146 ++++++++---------- 2 files changed, 80 insertions(+), 97 deletions(-) diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index c925a04f7..68ddea675 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -116,26 +116,21 @@ final class PDFWorkerController { } func setupObserver(for pdfWorkerWebViewHandler: PDFWorkerWebViewHandler) { - pdfWorkerWebViewHandler.observable - .subscribe(onNext: { [weak self] in - guard let self else { return } - process(result: $0, self: self) - }) - .disposed(by: disposeBag) - - func process(result: Result, self: PDFWorkerController) { + pdfWorkerWebViewHandler.observable.subscribe(onNext: { [weak self] result in + guard let self else { return } switch result { case .success(let data): switch data { case .recognizerData(let data), .fullText(let data): cleanupPDFWorker(for: work) { $0?.on(.next(Update(work: work, kind: .extractedData(data: data)))) } } - + case .failure(let error): DDLogError("PDFWorkerController: recognizer failed - \(error)") cleanupPDFWorker(for: work) { $0?.on(.next(Update(work: work, kind: .failed))) } } - } + }) + .disposed(by: disposeBag) } } } @@ -162,22 +157,22 @@ final class PDFWorkerController { private func cleanupPDFWorker(for work: PDFWork, completion: ((_ subject: PublishSubject?) -> Void)?) { if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { - cleanup(for: work, completion: completion, self: self) + cleanup(for: work, completion: completion, controller: self) } else { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } - cleanup(for: work, completion: completion, self: self) + cleanup(for: work, completion: completion, controller: self) } } - func cleanup(for work: PDFWork, completion: ((_ subject: PublishSubject?) -> Void)?, self: PDFWorkerController) { - let subject = subjectsByPDFWork[work] - queue.removeAll(where: { $0 == work }) - subjectsByPDFWork[work] = nil + func cleanup(for work: PDFWork, completion: ((_ subject: PublishSubject?) -> Void)?, controller: PDFWorkerController) { + let subject = controller.subjectsByPDFWork[work] + controller.queue.removeAll(where: { $0 == work }) + controller.subjectsByPDFWork[work] = nil DDLogInfo("PDFWorkerController: cleaned up for \(work)") - pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: work)?.webViewHandler.removeFromSuperviewAsynchronously() + controller.pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: work)?.webViewHandler.removeFromSuperviewAsynchronously() completion?(subject) - startWorkIfNeeded() + controller.startWorkIfNeeded() } } } diff --git a/Zotero/Controllers/RecognizerController.swift b/Zotero/Controllers/RecognizerController.swift index 9baa16365..fde27cf79 100644 --- a/Zotero/Controllers/RecognizerController.swift +++ b/Zotero/Controllers/RecognizerController.swift @@ -182,34 +182,30 @@ final class RecognizerController { pdfWorkerController.queue(work: PDFWorkerController.PDFWork(file: task.file, kind: .recognizer)) .subscribe(onNext: { [weak self] update in guard let self else { return } - process(update: update, self: self) + switch update.kind { + case .failed: + DDLogError("RecognizerController: \(task) - recognizer failed") + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.recognizerFailed)))) } + + case .cancelled: + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .cancelled))) } + + case .inProgress: + break + + case .extractedData(let data): + switch update.work.kind { + case .recognizer: + DDLogInfo("RecognizerController: \(task) - extracted recognizer data") + startRemoteRecognition(for: task, with: data) + + case .fullText: + DDLogError("RecognizerController: \(task) - PDF worker error") + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.pdfWorkerError)))) } + } + } }) .disposed(by: disposeBag) - - func process(update: PDFWorkerController.Update, self: RecognizerController) { - switch update.kind { - case .failed: - DDLogError("RecognizerController: \(task) - recognizer failed") - cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.recognizerFailed)))) } - - case .cancelled: - cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .cancelled))) } - - case .inProgress: - break - - case .extractedData(let data): - switch update.work.kind { - case .recognizer: - DDLogInfo("RecognizerController: \(task) - extracted recognizer data") - startRemoteRecognition(for: task, with: data) - - case .fullText: - DDLogError("RecognizerController: \(task) - PDF worker error") - cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.pdfWorkerError)))) } - } - } - } } } @@ -223,10 +219,31 @@ final class RecognizerController { statesByTask[task] = .remoteRecognitionInProgress(data: data) apiClient.send(request: RecognizerRequest(parameters: data)).subscribe( - onSuccess: { [weak self] (response: (RemoteRecognizerResponse, HTTPURLResponse)) in + onSuccess: { [weak self] (responseTuple: (RemoteRecognizerResponse, HTTPURLResponse)) in guard let self else { return } DDLogInfo("RecognizerController: \(task) - remote recognizer response received") - process(response: response.0, self: self) + let response = responseTuple.0 + var identifiers: [RecognizerIdentifier] = [] + if let identifier = response.arxiv { + identifiers.append(.arXiv(identifier)) + } + if let identifier = response.doi { + identifiers.append(.doi(identifier)) + } + if let identifier = response.isbn { + identifiers.append(.isbn(identifier)) + } + if let identifier = response.title { + identifiers.append(.title(identifier)) + } + guard !identifiers.isEmpty else { + cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.remoteRecognizerFailed)))) } + return + } + enqueueNextIdentifierLookup(for: task) { state in + guard case .remoteRecognitionInProgress = state else { return nil } + return (response, identifiers) + } }, onFailure: { [weak self] error in guard let self else { return } @@ -236,30 +253,6 @@ final class RecognizerController { ) .disposed(by: disposeBag) } - - func process(response: RemoteRecognizerResponse, self: RecognizerController) { - var identifiers: [RecognizerIdentifier] = [] - if let identifier = response.arxiv { - identifiers.append(.arXiv(identifier)) - } - if let identifier = response.doi { - identifiers.append(.doi(identifier)) - } - if let identifier = response.isbn { - identifiers.append(.isbn(identifier)) - } - if let identifier = response.title { - identifiers.append(.title(identifier)) - } - guard !identifiers.isEmpty else { - cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.remoteRecognizerFailed)))) } - return - } - enqueueNextIdentifierLookup(for: task) { state in - guard case .remoteRecognitionInProgress = state else { return nil } - return (response, identifiers) - } - } } private func enqueueNextIdentifierLookup( @@ -270,24 +263,24 @@ final class RecognizerController { } ) { if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { - _enqueueNextIdentifierLookup(for: task, self: self) + _enqueueNextIdentifierLookup(for: task, controller: self) } else { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } - _enqueueNextIdentifierLookup(for: task, self: self) + _enqueueNextIdentifierLookup(for: task, controller: self) } } - func _enqueueNextIdentifierLookup(for task: Task, self: RecognizerController) { - guard let state = statesByTask[task] else { - startRecognitionIfNeeded() + func _enqueueNextIdentifierLookup(for task: Task, controller: RecognizerController) { + guard let state = controller.statesByTask[task] else { + controller.startRecognitionIfNeeded() return } guard let (response, pendingIdentifiers) = getResponseAndIdentifiers(state) else { - cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) } + controller.cleanupTask(for: task) { $0?.on(.next(Update(task: task, kind: .failed(.unexpectedState)))) } return } - lookupNextIdentifier(for: task, with: response, pendingIdentifiers: pendingIdentifiers) + controller.lookupNextIdentifier(for: task, with: response, pendingIdentifiers: pendingIdentifiers) } } @@ -340,14 +333,8 @@ final class RecognizerController { return lookupWebViewHandler func setupObserver(for lookupWebViewHandler: LookupWebViewHandler) { - lookupWebViewHandler.observable - .subscribe(onNext: { [weak self] in - guard let self else { return } - process(result: $0, self: self) - }) - .disposed(by: disposeBag) - - func process(result: Result, self: RecognizerController) { + lookupWebViewHandler.observable.subscribe(onNext: { [weak self] result in + guard let self else { return } switch result { case .success(let data): switch data { @@ -391,7 +378,8 @@ final class RecognizerController { DDLogError("RecognizerController: \(task) - identifier lookup failed - \(error)") enqueueNextIdentifierLookup(for: task) } - } + }) + .disposed(by: disposeBag) } } } @@ -500,27 +488,27 @@ final class RecognizerController { private func cleanupTask(for task: Task, completion: ((_ subject: PublishSubject?) -> Void)?) { if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { - cleanup(for: task, completion: completion, self: self) + cleanup(for: task, completion: completion, controller: self) } else { accessQueue.async(flags: .barrier) { [weak self] in guard let self else { return } - cleanup(for: task, completion: completion, self: self) + cleanup(for: task, completion: completion, controller: self) } } - func cleanup(for task: Task, completion: ((_ subject: PublishSubject?) -> Void)?, self: RecognizerController) { - let subject = queue[task] ?? subjectsByTask[task] - queue[task] = nil - subjectsByTask[task] = nil - statesByTask[task] = nil - if case .createParentForItem(let libraryId, let key) = task.kind, var libraryLatestUpdates = latestUpdates[libraryId] { + func cleanup(for task: Task, completion: ((_ subject: PublishSubject?) -> Void)?, controller: RecognizerController) { + let subject = controller.queue[task] ?? controller.subjectsByTask[task] + controller.queue[task] = nil + controller.subjectsByTask[task] = nil + controller.statesByTask[task] = nil + if case .createParentForItem(let libraryId, let key) = task.kind, var libraryLatestUpdates = controller.latestUpdates[libraryId] { libraryLatestUpdates[key] = nil - latestUpdates[libraryId] = libraryLatestUpdates + controller.latestUpdates[libraryId] = libraryLatestUpdates } DDLogInfo("RecognizerController: \(task) - cleaned up") - lookupWebViewHandlersByTask.removeValue(forKey: task)?.webViewHandler.removeFromSuperviewAsynchronously() + controller.lookupWebViewHandlersByTask.removeValue(forKey: task)?.webViewHandler.removeFromSuperviewAsynchronously() completion?(subject) - startRecognitionIfNeeded() + controller.startRecognitionIfNeeded() } } From 7d85b6ac5f61dbfdc8f2cb8c5363e285ca20d6e9 Mon Sep 17 00:00:00 2001 From: Miltiadis Vasilakis Date: Wed, 5 Mar 2025 15:23:39 +0200 Subject: [PATCH 33/33] Improve code --- .../PDFWorkerWebViewHandler.swift | 49 +++++-------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift index 0ce242b4a..926d87113 100644 --- a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift @@ -80,7 +80,7 @@ final class PDFWorkerWebViewHandler { } } - private func performCall() -> Single<()> { + private func performAfterInitialization() -> Single<()> { return initializationState.filter { state in switch state { case .inProgress: @@ -106,50 +106,25 @@ final class PDFWorkerWebViewHandler { } } - func recognize(file: FileData) { + private func performPDFWorkerOperation(file: FileData, operationName: String, jsFunction: String) { let filePath = file.createUrl().path - performCall().subscribe(onSuccess: { [weak self] in - guard let self else { return } - performRecognize(for: filePath, self: self) - }, onFailure: { [weak self] error in + performAfterInitialization().flatMap({ + DDLogInfo("PDFWorkerWebViewHandler: call \(operationName) js") + return self.webViewHandler.call(javascript: "\(jsFunction)('\(filePath)');") + }) + .subscribe(onFailure: { [weak self] error in + DDLogError("PDFWorkerWebViewHandler: \(operationName) failed - \(error)") self?.observable.on(.next(.failure(error))) }) .disposed(by: disposeBag) + } - func performRecognize(for path: String, self: PDFWorkerWebViewHandler) { - DDLogInfo("PDFWorkerWebViewHandler: call recognize js") - return webViewHandler.call(javascript: "recognize('\(path)');") - .subscribe(on: MainScheduler.instance) - .observe(on: MainScheduler.instance) - .subscribe(onFailure: { [weak self] error in - DDLogError("PDFWorkerWebViewHandler: recognize failed - \(error)") - self?.observable.on(.next(.failure(error))) - }) - .disposed(by: disposeBag) - } + func recognize(file: FileData) { + performPDFWorkerOperation(file: file, operationName: "recognize", jsFunction: "recognize") } func getFullText(file: FileData) { - let filePath = file.createUrl().path - performCall().subscribe(onSuccess: { [weak self] in - guard let self else { return } - performGetFullText(for: filePath, self: self) - }, onFailure: { [weak self] error in - self?.observable.on(.next(.failure(error))) - }) - .disposed(by: disposeBag) - - func performGetFullText(for path: String, self: PDFWorkerWebViewHandler) { - DDLogInfo("PDFWorkerWebViewHandler: call getFullText js") - return webViewHandler.call(javascript: "getFullText('\(path)');") - .subscribe(on: MainScheduler.instance) - .observe(on: MainScheduler.instance) - .subscribe(onFailure: { [weak self] error in - DDLogError("PDFWorkerWebViewHandler: getFullText failed - \(error)") - self?.observable.on(.next(.failure(error))) - }) - .disposed(by: disposeBag) - } + performPDFWorkerOperation(file: file, operationName: "getFullText", jsFunction: "getFullText") } /// Communication with JS in `webView`. The `webView` sends a message through one of the registered `JSHandlers`, which is received here.