From d997ee38d2ffefdd75b81f7b5dbe41f07c82c1e7 Mon Sep 17 00:00:00 2001 From: redstrate Date: Wed, 11 Mar 2020 10:24:52 -0400 Subject: [PATCH] Overhaul silica document parsing Also adds layer count to the info sheet --- ProcreateViewer.xcodeproj/project.pbxproj | 12 +++ ProcreateViewer/Base.lproj/Main.storyboard | 37 ++++--- ProcreateViewer/Document.swift | 99 +++++++++++++++++-- ProcreateViewer/InfoViewController.swift | 9 +- .../ProcreateViewer-Bridging-Header.h | 8 ++ ProcreateViewer/cbridge.c | 9 ++ 6 files changed, 151 insertions(+), 23 deletions(-) create mode 100644 ProcreateViewer/ProcreateViewer-Bridging-Header.h create mode 100644 ProcreateViewer/cbridge.c diff --git a/ProcreateViewer.xcodeproj/project.pbxproj b/ProcreateViewer.xcodeproj/project.pbxproj index f9ec4e1..9905dcb 100644 --- a/ProcreateViewer.xcodeproj/project.pbxproj +++ b/ProcreateViewer.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 036AFC16241800350075400A /* Thumbnail.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 036AFC0B241800350075400A /* Thumbnail.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 036AFC1B241800850075400A /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 036AFC1A241800850075400A /* ZIPFoundation */; }; 037B4042241821D200392452 /* InfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037B4041241821D200392452 /* InfoViewController.swift */; }; + 03CB382424191F620078B3E5 /* cbridge.c in Sources */ = {isa = PBXBuildFile; fileRef = 03CB382324191F620078B3E5 /* cbridge.c */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -79,6 +80,8 @@ 036AFC12241800350075400A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 036AFC13241800350075400A /* Thumbnail.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Thumbnail.entitlements; sourceTree = ""; }; 037B4041241821D200392452 /* InfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoViewController.swift; sourceTree = ""; }; + 03CB382224191F610078B3E5 /* ProcreateViewer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ProcreateViewer-Bridging-Header.h"; sourceTree = ""; }; + 03CB382324191F620078B3E5 /* cbridge.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cbridge.c; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -144,6 +147,8 @@ 030F6FFE2415C5E400A43F01 /* ProcreateViewer.entitlements */, 036AFBB924168C030075400A /* ViewController.swift */, 037B4041241821D200392452 /* InfoViewController.swift */, + 03CB382324191F620078B3E5 /* cbridge.c */, + 03CB382224191F610078B3E5 /* ProcreateViewer-Bridging-Header.h */, ); path = ProcreateViewer; sourceTree = ""; @@ -256,6 +261,7 @@ TargetAttributes = { 030F6FED2415C5E300A43F01 = { CreatedOnToolsVersion = 11.3.1; + LastSwiftMigration = 1130; }; 030F70072415C6B500A43F01 = { CreatedOnToolsVersion = 11.3.1; @@ -321,6 +327,7 @@ buildActionMask = 2147483647; files = ( 036AFBBA24168C030075400A /* ViewController.swift in Sources */, + 03CB382424191F620078B3E5 /* cbridge.c in Sources */, 030F6FF42415C5E300A43F01 /* Document.swift in Sources */, 037B4042241821D200392452 /* InfoViewController.swift in Sources */, 030F6FF22415C5E300A43F01 /* AppDelegate.swift in Sources */, @@ -496,6 +503,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ProcreateViewer/ProcreateViewer.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -507,6 +515,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.github.redstrate.ProcreateViewer; PRODUCT_NAME = "Procreate Viewer"; + SWIFT_OBJC_BRIDGING_HEADER = "ProcreateViewer/ProcreateViewer-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; @@ -516,6 +526,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ProcreateViewer/ProcreateViewer.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -527,6 +538,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.github.redstrate.ProcreateViewer; PRODUCT_NAME = "Procreate Viewer"; + SWIFT_OBJC_BRIDGING_HEADER = "ProcreateViewer/ProcreateViewer-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Release; diff --git a/ProcreateViewer/Base.lproj/Main.storyboard b/ProcreateViewer/Base.lproj/Main.storyboard index eb2e8b3..31b305c 100644 --- a/ProcreateViewer/Base.lproj/Main.storyboard +++ b/ProcreateViewer/Base.lproj/Main.storyboard @@ -149,7 +149,7 @@ - + @@ -159,19 +159,30 @@ - + - + - + + + + + + +Dk51bWJlciBvZiBMYXllcnM6A + + + + + - - - - - + + + + + + + + + - + @@ -226,7 +241,7 @@ DQ - + diff --git a/ProcreateViewer/Document.swift b/ProcreateViewer/Document.swift index 48db52b..1c2b10a 100644 --- a/ProcreateViewer/Document.swift +++ b/ProcreateViewer/Document.swift @@ -1,12 +1,32 @@ import Cocoa import ZIPFoundation +import CoreFoundation -struct DocumentInfo { - var tracked_time: Int = 0 +struct SilicaLayer { + +} + +struct SilicaDocument { + var trackedTime: Int = 0 + + var layers: [SilicaLayer] = [] +} + +// Since this is a C-function we have to unsafe cast... +func objectRefGetValue(_ objectRef : CFTypeRef) -> UInt32 { + return _CFKeyedArchiverUIDGetValue(unsafeBitCast(objectRef, to: CFKeyedArchiverUIDRef.self)) } class Document: NSDocument { - var info = DocumentInfo() + var dict: NSDictionary? + + let DocumentClassName = "SilicaDocument" + let TrackedTimeKey = "SilicaDocumentTrackedTimeKey" + let LayersKey = "layers" + + let LayerClassName = "SilicaLayer" + + var info = SilicaDocument() var thumbnail: NSImage? = nil @@ -19,6 +39,72 @@ class Document: NSDocument { let windowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as! NSWindowController self.addWindowController(windowController) } + + /* + Pass in an object from the $object array, which always contains a $class key. + */ + func getDocumentClassName(dict: NSDictionary) -> String? { + let objectsArray = self.dict?["$objects"] as! NSArray + + if let value = dict["$class"] { + let classObjectId = objectRefGetValue(value as CFTypeRef) + let classObject = objectsArray[Int(classObjectId)] as! NSDictionary + + return classObject["$classname"] as? String + } + + return nil + } + + func parseSilicaLayer(dict: NSDictionary) { + if getDocumentClassName(dict: dict) == LayerClassName { + let layer = SilicaLayer() + // TODO: fill in layer information + + info.layers.append(layer) + } + } + + func parseSilicaDocument(dict: NSDictionary) { + let objectsArray = self.dict?["$objects"] as! NSArray + + if getDocumentClassName(dict: dict) == DocumentClassName { + info.trackedTime = (dict[TrackedTimeKey] as! NSNumber).intValue + + let layersClassKey = dict[LayersKey] + let layersClassID = objectRefGetValue(layersClassKey as CFTypeRef) + let layersClass = objectsArray[Int(layersClassID)] as! NSDictionary + + let array = layersClass["NS.objects"] as! NSArray + + for object in array { + let layerClassID = objectRefGetValue(object as CFTypeRef) + let layerClass = objectsArray[Int(layerClassID)] as! NSDictionary + + parseSilicaLayer(dict: layerClass) + } + } + } + + func parseDocument(dict: NSDictionary) { + // double check if this archive is really correct + if let value = dict["$version"] { + if (value as! Int) != 100000 { + Swift.print("This is not a valid document!") + } + + self.dict = dict + + let objectsArray = dict["$objects"] as! NSArray + + // let's read the $top class, which is always going to be SilicaDocument type. + let topObject = dict["$top"] as! NSDictionary + let topClassID = objectRefGetValue(topObject["root"] as CFTypeRef) + let topObjectClass = objectsArray[Int(topClassID)] as! NSDictionary + + parseSilicaDocument(dict: topObjectClass) + } + } override func read(from data: Data, ofType typeName: String) throws { guard let archive = Archive(data: data, accessMode: Archive.AccessMode.read) else { @@ -65,14 +151,9 @@ class Document: NSDocument { fatalError("failed to deserialize") } - // this is temporary, as we're just hoping that the keyed archive fits our requirements... let dict = (propertyList as! NSDictionary); - let objects = dict["$objects"] as! NSArray - - let tracked_time = (objects[1] as! NSDictionary)["SilicaDocumentTrackedTimeKey"] - - info.tracked_time = (tracked_time as! NSNumber).intValue + parseDocument(dict: dict) } } diff --git a/ProcreateViewer/InfoViewController.swift b/ProcreateViewer/InfoViewController.swift index b94d3fa..4ca2d68 100644 --- a/ProcreateViewer/InfoViewController.swift +++ b/ProcreateViewer/InfoViewController.swift @@ -5,16 +5,19 @@ class InfoViewController: NSViewController { var document: Document? @IBOutlet weak var timeSpentLabel: NSTextField! - - override func viewDidAppear() { + @IBOutlet weak var layerCountLabel: NSTextField! + + override func viewWillAppear() { super.viewDidAppear() let formatter = DateComponentsFormatter() formatter.allowedUnits = [.hour, .minute, .second] formatter.unitsStyle = .full - let formattedString = formatter.string(from: TimeInterval(document!.info.tracked_time))! + let formattedString = formatter.string(from: TimeInterval(document!.info.trackedTime))! timeSpentLabel.stringValue = "Time Spent: " + formattedString + + layerCountLabel.stringValue = "Number of layers: " + String(document!.info.layers.count) } } diff --git a/ProcreateViewer/ProcreateViewer-Bridging-Header.h b/ProcreateViewer/ProcreateViewer-Bridging-Header.h new file mode 100644 index 0000000..e53d475 --- /dev/null +++ b/ProcreateViewer/ProcreateViewer-Bridging-Header.h @@ -0,0 +1,8 @@ +/* + Here we import Apple's functions to decode __CFKeyedArchiverUID types. + */ +#include + +typedef const struct __CFKeyedArchiverUID * CFKeyedArchiverUIDRef; + +extern uint32_t _CFKeyedArchiverUIDGetValue(CFKeyedArchiverUIDRef uid); diff --git a/ProcreateViewer/cbridge.c b/ProcreateViewer/cbridge.c new file mode 100644 index 0000000..f764970 --- /dev/null +++ b/ProcreateViewer/cbridge.c @@ -0,0 +1,9 @@ +// +// cbridge.c +// ProcreateViewer +// +// Created by Josh on 3/11/20. +// Copyright © 2020 Josh. All rights reserved. +// + +#include