diff --git a/SilicaViewer/Document.swift b/SilicaViewer/Document.swift index 50aefd4..b5c4585 100644 --- a/SilicaViewer/Document.swift +++ b/SilicaViewer/Document.swift @@ -118,40 +118,51 @@ class Document: NSDocument { } func parseSilicaLayer(archive: Archive, dict: NSDictionary, isMask: Bool) -> SilicaLayer? { - let objectsArray = self.dict?["$objects"] as! NSArray - if getDocumentClassName(dict: dict) == LayerClassName { var layer = SilicaLayer() + // TODO: when does a layer actually not have a name? i think i hit this case before if let val = dict["name"] { - let NameClassID = getClassID(id: val) - let NameClass = objectsArray[NameClassID] as! NSString - - layer.name = NameClass as String + if let name = parseBasicObject(key: val) as? String { + layer.name = name + } } - let UUIDKey = dict["UUID"] - let UUIDClassID = getClassID(id: UUIDKey) - let UUIDClass = objectsArray[UUIDClassID] as! NSString + guard let uuid = parseBasicObject(key: dict["UUID"]) as? String else { + throwError(.invalid) + return nil + } - let maskKey = dict["mask"] - let maskClassID = getClassID(id: maskKey) - let maskClass = objectsArray[maskClassID] + // sometimes mask is missing + if let maskDict = parseBasicObject(key: dict["mask"]) as? NSDictionary { + layer.mask = parseSilicaLayer(archive: archive, dict: maskDict, isMask: true)?.data + } - layer.data.blendMode = parseRawBlendMode(blendMode: (dict["blend"] as? NSNumber)!.intValue, extendedBlend: (dict["extendedBlend"] as? NSNumber)!.intValue)! + guard let blendMode = (dict["blend"] as? NSNumber)?.intValue, + let extendedBlendMode = (dict["extendedBlend"] as? NSNumber)?.intValue, + let parsedBlendMode = parseRawBlendMode(blendMode: blendMode, extendedBlend: extendedBlendMode) else { + throwError(.invalid) + return nil + } - layer.data.opacity = (dict["opacity"] as? NSNumber)!.doubleValue - layer.data.hidden = (dict["hidden"] as? Bool)! - layer.clipped = (dict["clipped"] as? Bool)! + layer.data.blendMode = parsedBlendMode - if maskClassID != 0 { - layer.mask = parseSilicaLayer(archive: archive, dict: maskClass as! NSDictionary, isMask: true)?.data + if let opacity = (dict["opacity"] as? NSNumber)?.doubleValue { + layer.data.opacity = opacity + } + + if let hidden = dict["hidden"] as? Bool { + layer.data.hidden = hidden + } + + if let clipped = dict["clipped"] as? Bool { + layer.clipped = clipped } var chunkPaths: [String] = [] archive.forEach { (entry: Entry) in - if entry.path.contains(String(UUIDClass)) { + if entry.path.contains(uuid) { chunkPaths.append(entry.path) } } @@ -243,28 +254,52 @@ class Document: NSDocument { } } + func parseBasicObject(key: Any?) -> Any? { + if let objectsArray = self.dict?["$objects"] as? NSArray { + let classID = getClassID(id: key) + + return objectsArray[classID] + } else { + return nil + } + } + func parseSilicaDocument(archive: Archive, dict: NSDictionary) { - let objectsArray = self.dict?["$objects"] as! NSArray - if getDocumentClassName(dict: dict) == DocumentClassName { - info.trackedTime = (dict[TrackedTimeKey] as! NSNumber).intValue - info.tileSize = (dict[TileSizeKey] as! NSNumber).intValue - info.orientation = (dict[OrientationKey] as! NSNumber).intValue - info.flippedHorizontally = (dict[FlippedHorizontallyKey] as! NSNumber).boolValue - info.flippedVertically = (dict[FlippedVerticallyKey] as! NSNumber).boolValue + // failing to parse these isn't a failing case, thus we don't throw errors here. + if let trackedTime = (dict[TrackedTimeKey] as? NSNumber)?.intValue { + info.trackedTime = trackedTime + } - let videoResolutionClassKey = dict["SilicaDocumentVideoSegmentInfoKey"] - let videoResolutionClassID = getClassID(id: videoResolutionClassKey) - let videoResolution = objectsArray[videoResolutionClassID] as! NSDictionary + if let tileSize = (dict[TileSizeKey] as? NSNumber)?.intValue { + info.tileSize = tileSize + } - let frameSizeClassKey = videoResolution["frameSize"] - let frameSizeClassID = getClassID(id: frameSizeClassKey) - let frameSize = objectsArray[frameSizeClassID] as! String + if let orientation = (dict[OrientationKey] as? NSNumber)?.intValue { + info.orientation = orientation + } - info.videoFrame = parsePairString(frameSize)! + if let flippedHorizontally = (dict[FlippedHorizontallyKey] as? NSNumber)?.boolValue { + info.flippedHorizontally = flippedHorizontally + } - let colorProfileClassKey = dict["colorProfile"] - if colorProfileClassKey != nil { + if let flippedVertically = (dict[FlippedVerticallyKey] as? NSNumber)?.boolValue { + info.flippedVertically = flippedVertically + } + + if let videoResolution = parseBasicObject(key: dict["SilicaDocumentVideoSegmentInfoKey"]) as? NSDictionary { + guard let frameSize = parseBasicObject(key: videoResolution["frameSize"]) as? String, + let videoFrame = parsePairString(frameSize) else { + throwError(.invalid) + return + } + + info.videoFrame = videoFrame + } + + if let colorProfileClassKey = dict["colorProfile"] { + let objectsArray = self.dict?["$objects"] as! NSArray + let colorProfileClassID = getClassID(id: colorProfileClassKey) let colorProfile = objectsArray[colorProfileClassID] as! NSDictionary @@ -280,52 +315,38 @@ class Document: NSDocument { info.colorSpace = CGColorSpace(name: CGColorSpace.displayP3)! } - let backgroundClassKey = dict["backgroundColor"] - let backgroundClassID = getClassID(id: backgroundClassKey) - let background = objectsArray[backgroundClassID] as! NSData - - var backgroundArray: [Float] = [0.0, 0.0, 0.0, 0.0] - - background.getBytes(&backgroundArray, length: 16) - let backgroundCgArray: [CGFloat] = [CGFloat(backgroundArray[0]), CGFloat(backgroundArray[1]), CGFloat(backgroundArray[2]), CGFloat(backgroundArray[3])] - - info.backgroundColor = CGColor(colorSpace: info.colorSpace, components: backgroundCgArray)! - - let strokeClassKey = dict[StrokeCountKey] - let strokeClassID = getClassID(id: strokeClassKey) - let strokeCount = objectsArray[strokeClassID] as! NSNumber - - info.strokeCount = Int(truncating: strokeCount) - - let nameClassKey = dict[NameKey] - let nameClassID = getClassID(id: nameClassKey) - let nameString = objectsArray[nameClassID] as! NSString - - if nameString != "$null" { - info.name = nameString as String - } - - let authorClassKey = dict[AuthorNameKey] - if authorClassKey != nil { - let authorClassID = getClassID(id: authorClassKey) - let authorString = objectsArray[authorClassID] as! String + if let background = parseBasicObject(key: dict["backgroundColor"]) as? NSData { + var backgroundArray: [Float] = [0.0, 0.0, 0.0, 0.0] - if authorString != "$null" { - info.authorName = authorString - } + background.getBytes(&backgroundArray, length: 16) + let backgroundCgArray: [CGFloat] = [CGFloat(backgroundArray[0]), CGFloat(backgroundArray[1]), CGFloat(backgroundArray[2]), CGFloat(backgroundArray[3])] + + info.backgroundColor = CGColor(colorSpace: info.colorSpace, components: backgroundCgArray)! } - let sizeClassKey = dict[SizeKey] - let sizeClassID = getClassID(id: sizeClassKey) - let sizeString = objectsArray[sizeClassID] as! String + if let strokeCount = parseBasicObject(key: dict[StrokeCountKey]) as? NSNumber { + info.strokeCount = Int(truncating: strokeCount) + } - guard let (parsedWidth, parsedHeight) = parsePairString(sizeString) else { + if let name = parseBasicObject(key: dict[NameKey]) as? String, + name != "$null" { + info.name = name + } + + if let authorName = parseBasicObject(key: dict[AuthorNameKey]) as? String, + authorName != "$null" { + info.authorName = authorName + } + + guard let size = parseBasicObject(key: dict[SizeKey]) as? String, + let (parsedWidth, parsedHeight) = parsePairString(size) else { + throwError(.invalid) return } info.width = parsedWidth info.height = parsedHeight - + info.columns = Int(ceil(Float(info.width) / Float(info.tileSize))) info.rows = Int(ceil(Float(info.height) / Float(info.tileSize))) + 1 // TODO: lol why @@ -337,17 +358,18 @@ class Document: NSDocument { info.remainderHeight = (info.rows * info.tileSize) - info.height } - let layersClassKey = dict[LayersKey] - let layersClassID = getClassID(id: layersClassKey) - let layersClass = objectsArray[layersClassID] as! NSDictionary + guard let layersDict = parseBasicObject(key: dict[LayersKey]) as? NSDictionary, + let layersArray = layersDict["NS.objects"] as? NSArray else { + throwError(.invalid) + return + } - let array = layersClass["NS.objects"] as! NSArray - - for object in array { - let layerClassID = getClassID(id: object) - let layerClass = objectsArray[layerClassID] as! NSDictionary - - guard let layer = parseSilicaLayer(archive: archive, dict: layerClass, isMask: false) else { return } + for object in layersArray { + guard let layerDict = parseBasicObject(key: object) as? NSDictionary, + let layer = parseSilicaLayer(archive: archive, dict: layerDict, isMask: false) else { + throwError(.invalid) + return + } info.layers.append(layer) } @@ -364,12 +386,24 @@ class Document: NSDocument { self.dict = dict - let objectsArray = dict["$objects"] as! NSArray + guard let objectsArray = dict["$objects"] as? NSArray else { + throwError(.invalid) + return + } + + // let's begin by reading $top, which is always going to be SilicaDocument type. + + guard let topObject = dict["$top"] as? NSDictionary else { + throwError(.invalid) + return + } - // let's read the $top class, which is always going to be SilicaDocument type. - let topObject = dict["$top"] as! NSDictionary let topClassID = objectRefGetValue2(topObject["root"] as CFTypeRef) - let topObjectClass = objectsArray[Int(topClassID)] as! NSDictionary + + guard let topObjectClass = objectsArray[Int(topClassID)] as? NSDictionary else { + throwError(.invalid) + return + } parseSilicaDocument(archive: archive, dict: topObjectClass) }