1
Fork 0

Overhaul silica document parsing

Also adds layer count to the info sheet
This commit is contained in:
Joshua Goins 2020-03-11 10:24:52 -04:00 committed by redstrate
parent 1a9e952f37
commit d997ee38d2
6 changed files with 151 additions and 23 deletions

View file

@ -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 = "<group>"; };
036AFC13241800350075400A /* Thumbnail.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Thumbnail.entitlements; sourceTree = "<group>"; };
037B4041241821D200392452 /* InfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoViewController.swift; sourceTree = "<group>"; };
03CB382224191F610078B3E5 /* ProcreateViewer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ProcreateViewer-Bridging-Header.h"; sourceTree = "<group>"; };
03CB382324191F620078B3E5 /* cbridge.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cbridge.c; sourceTree = "<group>"; };
/* 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 = "<group>";
@ -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;

View file

@ -149,7 +149,7 @@
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="ProcreateViewer" customModuleProvider="target"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Procreate_Viewer" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<userDefaultsController representsSharedInstance="YES" id="JdS-Pg-8N9"/>
@ -159,19 +159,30 @@
<!--Info View Controller-->
<scene sceneID="nJy-a4-E0d">
<objects>
<viewController showSeguePresentationStyle="single" id="wda-Mt-beD" customClass="InfoViewController" customModule="ProcreateViewer" customModuleProvider="target" sceneMemberID="viewController">
<viewController showSeguePresentationStyle="single" id="wda-Mt-beD" customClass="InfoViewController" customModule="Procreate_Viewer" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="3vu-Kd-l73">
<rect key="frame" x="0.0" y="0.0" width="480" height="85"/>
<rect key="frame" x="0.0" y="0.0" width="480" height="109"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="u9u-el-oA4">
<rect key="frame" x="18" y="49" width="444" height="16"/>
<rect key="frame" x="18" y="73" width="444" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" placeholderString="Time Spent" id="UBd-vS-gNL">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8HM-na-o1E">
<rect key="frame" x="18" y="49" width="444" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" id="cl0-u1-l9k">
<font key="font" usesAppearanceFont="YES"/>
<string key="placeholderString" base64-UTF8="YES">
Dk51bWJlciBvZiBMYXllcnM6A
</string>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2oe-to-Rcz">
<rect key="frame" x="407" y="13" width="59" height="32"/>
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="BnS-yc-3ej">
@ -187,20 +198,24 @@ DQ
</button>
</subviews>
<constraints>
<constraint firstItem="2oe-to-Rcz" firstAttribute="top" secondItem="u9u-el-oA4" secondAttribute="bottom" constant="8" symbolic="YES" id="BYm-qe-WxD"/>
<constraint firstItem="u9u-el-oA4" firstAttribute="top" secondItem="3vu-Kd-l73" secondAttribute="top" constant="20" symbolic="YES" id="HaC-Tk-Y3y"/>
<constraint firstAttribute="trailing" secondItem="u9u-el-oA4" secondAttribute="trailing" constant="20" symbolic="YES" id="Qfn-KK-5Ya"/>
<constraint firstItem="u9u-el-oA4" firstAttribute="leading" secondItem="3vu-Kd-l73" secondAttribute="leading" constant="20" symbolic="YES" id="TpT-D9-UgA"/>
<constraint firstItem="2oe-to-Rcz" firstAttribute="trailing" secondItem="u9u-el-oA4" secondAttribute="trailing" id="ZND-Qt-9wv"/>
<constraint firstItem="2oe-to-Rcz" firstAttribute="top" secondItem="8HM-na-o1E" secondAttribute="bottom" constant="8" symbolic="YES" id="85g-Dp-ezi"/>
<constraint firstItem="8HM-na-o1E" firstAttribute="top" secondItem="u9u-el-oA4" secondAttribute="bottom" constant="8" symbolic="YES" id="CA1-bq-4Dc"/>
<constraint firstItem="u9u-el-oA4" firstAttribute="trailing" secondItem="8HM-na-o1E" secondAttribute="trailing" id="ILX-8S-pOI"/>
<constraint firstItem="u9u-el-oA4" firstAttribute="top" secondItem="3vu-Kd-l73" secondAttribute="top" constant="20" symbolic="YES" id="IQj-on-T5L"/>
<constraint firstItem="u9u-el-oA4" firstAttribute="leading" secondItem="8HM-na-o1E" secondAttribute="leading" id="KJd-X5-kxh"/>
<constraint firstAttribute="trailing" secondItem="u9u-el-oA4" secondAttribute="trailing" constant="20" symbolic="YES" id="PlA-zS-FpK"/>
<constraint firstItem="2oe-to-Rcz" firstAttribute="trailing" secondItem="8HM-na-o1E" secondAttribute="trailing" id="Whu-Lh-znF"/>
<constraint firstItem="u9u-el-oA4" firstAttribute="leading" secondItem="3vu-Kd-l73" secondAttribute="leading" constant="20" symbolic="YES" id="Xzy-ZR-1ha"/>
</constraints>
</view>
<connections>
<outlet property="layerCountLabel" destination="8HM-na-o1E" id="Vnq-ev-JGx"/>
<outlet property="timeSpentLabel" destination="u9u-el-oA4" id="LeI-31-Z8r"/>
</connections>
</viewController>
<customObject id="Tam-yi-Bux" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="786" y="286.5"/>
<point key="canvasLocation" x="786" y="298.5"/>
</scene>
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
@ -226,7 +241,7 @@ DQ
<!--View Controller-->
<scene sceneID="hIz-AP-VOD">
<objects>
<viewController id="5gI-5U-AMq" customClass="ViewController" customModule="ProcreateViewer" customModuleProvider="target" sceneMemberID="viewController">
<viewController id="5gI-5U-AMq" customClass="ViewController" customModule="Procreate_Viewer" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="ERx-hH-rdd">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>

View file

@ -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
@ -20,6 +40,72 @@ class Document: NSDocument {
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 {
return
@ -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)
}
}

View file

@ -5,16 +5,19 @@ class InfoViewController: NSViewController {
var document: Document?
@IBOutlet weak var timeSpentLabel: NSTextField!
@IBOutlet weak var layerCountLabel: NSTextField!
override func viewDidAppear() {
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)
}
}

View file

@ -0,0 +1,8 @@
/*
Here we import Apple's functions to decode __CFKeyedArchiverUID types.
*/
#include <CoreFoundation/CFBase.h>
typedef const struct __CFKeyedArchiverUID * CFKeyedArchiverUIDRef;
extern uint32_t _CFKeyedArchiverUIDGetValue(CFKeyedArchiverUIDRef uid);

View file

@ -0,0 +1,9 @@
//
// cbridge.c
// ProcreateViewer
//
// Created by Josh on 3/11/20.
// Copyright © 2020 Josh. All rights reserved.
//
#include <stdio.h>