1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-09-11 13:15:35 +03:00
Macaw/MacawTests/SceneSerialization.swift
2019-05-30 13:59:09 +07:00

632 lines
18 KiB
Swift

//
// SceneSerialization.swift
// MacawTests
//
// Created by Alisa Mylnikova on 23/04/2018.
// Copyright © 2018 Exyte. All rights reserved.
//
#if os(OSX)
import Foundation
@testable import MacawOSX
#endif
#if os(iOS)
import UIKit
@testable import Macaw
#endif
protocol Initializable {
init()
}
extension Double : Initializable {}
extension Bool : Initializable {}
extension Int : Initializable {}
func parse<T: Initializable>(_ from: Any?) -> T {
return from as? T ?? T()
}
protocol Serializable {
func toDictionary() -> [String:Any]
}
class NodeSerializer {
var factories = [String:([String:Any]) -> Node]()
let locusSerializer = LocusSerializer()
init() {
factories["Shape"] = { dictionary in
let locusDict = dictionary["form"] as! [String:Any]
let locus = self.locusSerializer.instance(dictionary: locusDict)
let shape = Shape(form: locus)
if let fillDict = dictionary["fill"] as? [String:Any], let fillType = fillDict["type"] as? String, fillType == "Color" {
shape.fill = Color(dictionary: fillDict)
}
if let strokeDict = dictionary["stroke"] as? [String:Any] {
shape.stroke = Stroke(dictionary: strokeDict)
}
return shape
}
factories["Text"] = { dictionary in
let textString = dictionary["text"] as! String
let text = Text(text: textString)
if let fontDict = dictionary["font"] as? [String:Any] {
text.font = Font(dictionary: fontDict)
}
if let fillDict = dictionary["fill"] as? [String:Any],
let fillType = fillDict["type"] as? String,
fillType == "Color",
let color = Color(dictionary: fillDict) {
text.fill = color
}
if let strokeDict = dictionary["stroke"] as? [String:Any] {
text.stroke = Stroke(dictionary: strokeDict)
}
if let alignString = dictionary["align"] as? String {
text.align = Align.instantiate(string: alignString)
}
if let baselineString = dictionary["baseline"] as? String {
text.baseline = baselineForString(baselineString)
}
return text
}
factories["Group"] = { dictionary in
let contents = dictionary["contents"] as! [[String:Any]]
var nodes = [Node]()
for dict in contents {
nodes.append(self.instance(dictionary: dict))
}
return Group(contents: nodes)
}
factories["Canvas"] = { dictionary in
let layoutDict = dictionary["layout"] as! [String : Any]
let viewBoxDict = layoutDict["viewBox"] as! [String:Any]
let layout = SVGNodeLayout(
svgSize: SVGSize(dictionary: layoutDict["svgSize"] as! [String : Any]),
viewBox: self.locusSerializer.instance(dictionary: viewBoxDict) as? Rect,
scaling: AspectRatio.instantiate(string: layoutDict["scalingMode"] as! String),
xAlign: Align.instantiate(string: layoutDict["xAligningMode"] as! String),
yAlign: Align.instantiate(string: layoutDict["yAligningMode"] as! String))
let contents = dictionary["contents"] as! [[String:Any]]
var nodes = [Node]()
for dict in contents {
nodes.append(self.instance(dictionary: dict))
}
return SVGCanvas(layout: layout, contents: nodes)
}
}
func instance(dictionary: [String:Any]) -> Node {
let type = dictionary["node"] as! String
let node = factories[type]!(dictionary)
node.place = Transform(string: dictionary["place"] as? String)
node.opaque = Bool(dictionary["opaque"] as? String ?? "") ?? true
node.opacity = Double(dictionary["opacity"] as? String ?? "") ?? 0
if let locusDict = dictionary["clip"] as? [String:Any] {
node.clip = locusSerializer.instance(dictionary: locusDict)
}
return node
}
}
extension Node {
func baseToDictionary() -> [String:Any] {
var result = [String : Any]()
if place != .identity {
result["place"] = place.toString()
}
if !opaque {
result["opaque"] = String(describing: opaque)
result["opacity"] = String(describing: opacity)
}
if let clip = clip as? Serializable {
result["clip"] = clip.toDictionary()
}
if let mask = mask as? Serializable {
result["mask"] = mask.toDictionary()
}
return result
}
}
extension Shape: Serializable {
func toDictionary() -> [String:Any] {
var result = super.baseToDictionary()
result["node"] = "Shape"
if let form = form as? Serializable {
result["form"] = form.toDictionary()
}
if let fillColor = fill as? Color {
result["fill"] = fillColor.toDictionary()
}
if let stroke = stroke {
result["stroke"] = stroke.toDictionary()
}
return result
}
}
extension Text: Serializable {
func toDictionary() -> [String:Any] {
var result = super.baseToDictionary()
result["node"] = "Text"
result["text"] = text
if let font = font {
result["font"] = font.toDictionary()
}
if let fillColor = fill as? Color {
result["fill"] = fillColor.toDictionary()
}
if let stroke = stroke {
result["stroke"] = stroke.toDictionary()
}
result["align"] = align.toString()
result["baseline"] = "\(baseline)"
return result
}
}
extension Group: Serializable {
func toDictionary() -> [String:Any] {
if let canvas = self as? SVGCanvas {
return canvas.canvasDictionary()
}
var nodes = [[String:Any]]()
for node in contents {
if let node = node as? Serializable {
nodes.append(node.toDictionary())
}
}
var result = super.baseToDictionary()
result["node"] = "Group"
result["contents"] = nodes
return result
}
}
extension SVGCanvas {
func canvasDictionary() -> [String:Any] {
var nodes = [[String:Any]]()
for node in contents {
if let node = node as? Serializable {
nodes.append(node.toDictionary())
}
}
var result = super.baseToDictionary()
result["node"] = "Canvas"
result["layout"] = (layout as! SVGNodeLayout).toDictionary()
result["contents"] = nodes
return result
}
}
extension SVGNodeLayout: Serializable {
func toDictionary() -> [String:Any] {
return ["svgSize" : svgSize.toDictionary() as Any,
"viewBox" : viewBox?.toDictionary() as Any,
"scalingMode" : scaling.toString(),
"xAligningMode" : xAlign.toString(),
"yAligningMode" : yAlign.toString()]
}
}
extension SVGSize: Serializable {
func toDictionary() -> [String:Any] {
return ["width" : width.toString(), "height": height.toString()]
}
convenience init(dictionary: [String:Any]) {
self.init(width: SVGLength(string: dictionary["width"] as? String), height: SVGLength(string: dictionary["height"] as? String))
}
}
extension SVGLength {
func toString() -> String {
switch(self) {
case let .percent(percent):
return "\(percent)%"
case let .pixels(pixels):
return String(describing: pixels)
}
}
init(string: String?) {
self.init(pixels: 0)
guard let string = string else {
return
}
if string.hasSuffix("%") {
self = SVGLength.percent(Double(string.dropLast())!)
} else {
self = SVGLength.pixels(Double(string)!)
}
}
}
class LocusSerializer {
var factories = [String:([String:Any]) -> Locus]()
init() {
factories["Arc"] = { dictionary in
Arc(ellipse: self.instance(dictionary: dictionary["Ellipse"] as! [String:Any]) as! Ellipse,
shift: parse(dictionary["shift"]),
extent: parse(dictionary["extent"]))
}
factories["Circle"] = { dictionary in
Circle(cx: parse(dictionary["cx"]),
cy: parse(dictionary["cy"]),
r: parse(dictionary["r"]))
}
factories["Ellipse"] = { dictionary in
Ellipse(cx: parse(dictionary["cx"]),
cy: parse(dictionary["cy"]),
rx: parse(dictionary["rx"]),
ry: parse(dictionary["rx"]))
}
factories["Line"] = { dictionary in
Line(x1: parse(dictionary["x1"]),
y1: parse(dictionary["y1"]),
x2: parse(dictionary["x2"]),
y2: parse(dictionary["y2"]))
}
factories["Path"] = { dictionary in
let array = dictionary["segments"] as! [[String:Any]]
var pathSegments = [PathSegment]()
for dict in array {
pathSegments.append(PathSegment(
type: typeForString(dict["type"] as! String),
data: dict["data"] as! [Double]))
}
return Path(segments: pathSegments)
}
factories["Polygon"] = { dictionary in
Polygon.init(points: dictionary["points"] as! [Double])
}
factories["Polyline"] = { dictionary in
Polyline.init(points: dictionary["points"] as! [Double])
}
factories["Rect"] = { dictionary in
Rect(x: parse(dictionary["x"]),
y: parse(dictionary["y"]),
w: parse(dictionary["w"]),
h: parse(dictionary["h"]))
}
factories["RoundRect"] = { dictionary in
RoundRect(rect: self.instance(dictionary: dictionary["Rect"] as! [String:Any]) as! Rect,
rx: parse(dictionary["rx"]),
ry: parse(dictionary["ry"]))
}
factories["TransformedLocus"] = { dictionary in
TransformedLocus(locus: self.instance(dictionary: dictionary["locus"] as! [String:Any]),
transform: Transform(string: dictionary["transform"] as? String))
}
}
func instance(dictionary: [String:Any]) -> Locus {
let type = dictionary["type"] as! String
return factories[type]!(dictionary)
}
}
extension TransformedLocus: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "TransformedLocus", "locus": (locus as! Serializable).toDictionary(), "transform": transform.toString()]
}
}
extension Arc: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "Arc", "ellipse": ellipse.toDictionary(), "shift": shift, "extent": extent]
}
}
extension Circle: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "Circle", "cx": cx, "cy": cy, "r": r]
}
}
extension Ellipse: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "Ellipse", "cx": cx, "cy": cy, "rx": rx, "ry": ry]
}
}
extension Line: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "Line", "x1": x1, "y1": y1, "x2": x2, "y2": y2]
}
}
extension Path: Serializable {
func toDictionary() -> [String:Any] {
var pathSegments = [[String:Any]]()
for segment in segments {
pathSegments.append(segment.toDictionary())
}
return ["type": "Path", "segments": pathSegments]
}
}
#if os(iOS)
extension Polygon: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "Polygon", "points": points]
}
}
#endif
#if os(OSX)
extension MacawOSX.Polygon: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "Polygon", "points": points]
}
}
#endif
extension Polyline: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "Polyline", "points": points]
}
}
extension Rect: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "Rect", "x": x, "y": y, "w": w, "h": h]
}
}
extension RoundRect: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "RoundRect", "rect": rect.toDictionary(), "rx": rx, "ry": ry]
}
}
extension PathSegment: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "\(type)", "data": data]
}
}
extension Color: Serializable {
func toDictionary() -> [String:Any] {
return ["type": "Color", "val": val]
}
convenience init?(dictionary: [String:Any]) {
self.init(val: dictionary["val"] as! Int)
}
}
extension Stroke: Serializable {
func toDictionary() -> [String:Any] {
var result = ["width": width, "cap": "\(cap)", "join": "\(join)", "dashes": dashes] as [String : Any]
if let fillColor = fill as? Color {
result["fill"] = fillColor.toDictionary()
}
if miterLimit != 4 {
result["miterLimit"] = miterLimit
}
if offset != 0 {
result["offset"] = offset
}
return result
}
convenience init?(dictionary: [String:Any]) {
guard let fillDict = dictionary["fill"] as? [String:Any], let fillType = fillDict["type"] as? String, fillType == "Color", let fill = Color(dictionary: fillDict) else {
return nil
}
var cap = LineCap.butt
if let lineCapString = dictionary["cap"] as? String {
cap = lineCapForString(lineCapString)
}
var join = LineJoin.miter
if let lineJoinString = dictionary["join"] as? String {
join = lineJoinForString(lineJoinString)
}
var miterLimit: Double?
if let string = dictionary["miterLimit"] as? String {
miterLimit = Double(string)
}
var offset: Double?
if let string = dictionary["offset"] as? String {
offset = Double(string)
}
let dashes = dictionary["dashes"] as? [Double] ?? []
self.init(fill: fill,
width: parse(dictionary["width"]),
cap: cap,
join: join,
miterLimit: miterLimit != nil ? miterLimit! : 4,
dashes: dashes,
offset: offset != nil ? offset! : 0)
}
}
extension Font: Serializable {
func toDictionary() -> [String:Any] {
return ["name": name, "size": size, "weight": weight]
}
convenience init(dictionary: [String:Any]) {
self.init(name: dictionary["name"] as? String ?? "Serif",
size: parse(dictionary["size"]),
weight: dictionary["weight"] as? String ?? "normal")
}
}
extension Transform {
func toString() -> String {
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 6
let nums = [m11, m12, m21, m22, dx, dy]
var result = ""
for num in nums {
result += formatter.string(from: num as NSNumber) ?? "n/a"
result += ", "
}
return result.dropLast(2) + ""
}
convenience init(string: String?) {
guard let string = string else {
self.init()
return
}
let vals = string.components(separatedBy: ", ").map{ Double($0) ?? 0 }
if vals.count == 6 {
self.init(m11: vals[0], m12: vals[1], m21: vals[2], m22: vals[3], dx: vals[4], dy: vals[5])
} else {
self.init()
}
}
}
extension Align {
func toString() -> String {
if self === Align.mid {
return "mid"
}
if self === Align.max {
return "max"
}
return "min"
}
static func instantiate(string: String) -> Align {
switch string {
case "mid":
return .mid
case "max":
return .max
default:
return .min
}
}
}
extension AspectRatio {
func toString() -> String {
if self === AspectRatio.meet {
return "meet"
}
if self === AspectRatio.slice {
return "slice"
}
return "none"
}
static func instantiate(string: String) -> AspectRatio {
switch string {
case "meet":
return .meet
case "slice":
return .slice
default:
return .none
}
}
}
fileprivate func typeForString(_ string: String) -> PathSegmentType {
switch(string) {
case "M": return .M
case "m": return .m
case "L": return .L
case "l": return .l
case "C": return .C
case "c": return .c
case "Q": return .Q
case "q": return .q
case "A": return .A
case "a": return .a
case "z", "Z": return .z
case "H": return .H
case "h": return .h
case "V": return .V
case "v": return .v
case "S": return .S
case "s": return .s
case "T": return .T
case "t": return .t
default: return .M
}
}
fileprivate func lineCapForString(_ string: String) -> LineCap {
switch(string) {
case "butt": return .butt
case "round": return .round
case "square": return .square
default: return .butt
}
}
fileprivate func lineJoinForString(_ string: String) -> LineJoin {
switch(string) {
case "miter": return .miter
case "round": return .round
case "bevel": return .bevel
default: return .miter
}
}
fileprivate func baselineForString(_ string: String) -> Baseline {
switch(string) {
case "top": return .top
case "alphabetic": return .alphabetic
case "bottom": return .bottom
case "mid": return .mid
default: return .top
}
}