mirror of
https://github.com/exyte/Macaw.git
synced 2024-09-11 13:15:35 +03:00
Merge pull request #163 from exyte/feature/add-macaw-to-svg-serializer
Aadd Macaw to SVG serializer
This commit is contained in:
commit
dd3d1a72b7
@ -144,6 +144,8 @@
|
|||||||
A718CD501F45C28F00966E06 /* MView_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A718CD4C1F45C28F00966E06 /* MView_macOS.swift */; };
|
A718CD501F45C28F00966E06 /* MView_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A718CD4C1F45C28F00966E06 /* MView_macOS.swift */; };
|
||||||
A718CD521F45C2A400966E06 /* MBezierPath+Extension_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A718CD511F45C2A400966E06 /* MBezierPath+Extension_macOS.swift */; };
|
A718CD521F45C2A400966E06 /* MBezierPath+Extension_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A718CD511F45C2A400966E06 /* MBezierPath+Extension_macOS.swift */; };
|
||||||
A7E675561EC4213500BD9ECB /* NodeBoundsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E675551EC4213500BD9ECB /* NodeBoundsTests.swift */; };
|
A7E675561EC4213500BD9ECB /* NodeBoundsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E675551EC4213500BD9ECB /* NodeBoundsTests.swift */; };
|
||||||
|
C4820B181F458D0E008CE0FF /* SVGSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4820B171F458D0E008CE0FF /* SVGSerializer.swift */; };
|
||||||
|
C4820B1A1F458D64008CE0FF /* MacawSVGTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4820B191F458D64008CE0FF /* MacawSVGTests.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -309,6 +311,8 @@
|
|||||||
A718CD4C1F45C28F00966E06 /* MView_macOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MView_macOS.swift; path = Source/platform/macOS/MView_macOS.swift; sourceTree = SOURCE_ROOT; };
|
A718CD4C1F45C28F00966E06 /* MView_macOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MView_macOS.swift; path = Source/platform/macOS/MView_macOS.swift; sourceTree = SOURCE_ROOT; };
|
||||||
A718CD511F45C2A400966E06 /* MBezierPath+Extension_macOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "MBezierPath+Extension_macOS.swift"; path = "Source/platform/macOS/MBezierPath+Extension_macOS.swift"; sourceTree = SOURCE_ROOT; };
|
A718CD511F45C2A400966E06 /* MBezierPath+Extension_macOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "MBezierPath+Extension_macOS.swift"; path = "Source/platform/macOS/MBezierPath+Extension_macOS.swift"; sourceTree = SOURCE_ROOT; };
|
||||||
A7E675551EC4213500BD9ECB /* NodeBoundsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NodeBoundsTests.swift; path = Bounds/NodeBoundsTests.swift; sourceTree = "<group>"; };
|
A7E675551EC4213500BD9ECB /* NodeBoundsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NodeBoundsTests.swift; path = Bounds/NodeBoundsTests.swift; sourceTree = "<group>"; };
|
||||||
|
C4820B171F458D0E008CE0FF /* SVGSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGSerializer.swift; sourceTree = "<group>"; };
|
||||||
|
C4820B191F458D64008CE0FF /* MacawSVGTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacawSVGTests.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -598,6 +602,7 @@
|
|||||||
57E5E1481E3B393900D1CB28 /* SVGParserError.swift */,
|
57E5E1481E3B393900D1CB28 /* SVGParserError.swift */,
|
||||||
57E5E1491E3B393900D1CB28 /* SVGParserRegexHelper.swift */,
|
57E5E1491E3B393900D1CB28 /* SVGParserRegexHelper.swift */,
|
||||||
57E5E14A1E3B393900D1CB28 /* SVGView.swift */,
|
57E5E14A1E3B393900D1CB28 /* SVGView.swift */,
|
||||||
|
C4820B171F458D0E008CE0FF /* SVGSerializer.swift */,
|
||||||
);
|
);
|
||||||
path = svg;
|
path = svg;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -660,6 +665,7 @@
|
|||||||
57CAB1241D7832E000FD8E47 /* svg */,
|
57CAB1241D7832E000FD8E47 /* svg */,
|
||||||
57CAB1221D782DFC00FD8E47 /* TestUtils.swift */,
|
57CAB1221D782DFC00FD8E47 /* TestUtils.swift */,
|
||||||
57FCD27B1D76EA4600CC0FB6 /* MacawTests.swift */,
|
57FCD27B1D76EA4600CC0FB6 /* MacawTests.swift */,
|
||||||
|
C4820B191F458D64008CE0FF /* MacawSVGTests.swift */,
|
||||||
57FCD27D1D76EA4600CC0FB6 /* Info.plist */,
|
57FCD27D1D76EA4600CC0FB6 /* Info.plist */,
|
||||||
);
|
);
|
||||||
path = MacawTests;
|
path = MacawTests;
|
||||||
@ -870,6 +876,7 @@
|
|||||||
57E5E1951E3B393900D1CB28 /* PathSegment.swift in Sources */,
|
57E5E1951E3B393900D1CB28 /* PathSegment.swift in Sources */,
|
||||||
57E5E1A41E3B393900D1CB28 /* ImageRenderer.swift in Sources */,
|
57E5E1A41E3B393900D1CB28 /* ImageRenderer.swift in Sources */,
|
||||||
57E5E1621E3B393900D1CB28 /* PathFunctions.swift in Sources */,
|
57E5E1621E3B393900D1CB28 /* PathFunctions.swift in Sources */,
|
||||||
|
C4820B181F458D0E008CE0FF /* SVGSerializer.swift in Sources */,
|
||||||
57E5E16E1E3B393900D1CB28 /* MorphingAnimation.swift in Sources */,
|
57E5E16E1E3B393900D1CB28 /* MorphingAnimation.swift in Sources */,
|
||||||
57A27BD11E44C5460057BD3A /* ShapeAnimation.swift in Sources */,
|
57A27BD11E44C5460057BD3A /* ShapeAnimation.swift in Sources */,
|
||||||
57E5E15F1E3B393900D1CB28 /* TransformInterpolation.swift in Sources */,
|
57E5E15F1E3B393900D1CB28 /* TransformInterpolation.swift in Sources */,
|
||||||
@ -956,6 +963,7 @@
|
|||||||
files = (
|
files = (
|
||||||
5713C4F71E5C34C700BBA4D9 /* SequenceAnimationTests.swift in Sources */,
|
5713C4F71E5C34C700BBA4D9 /* SequenceAnimationTests.swift in Sources */,
|
||||||
57B7A4E31EE70DC3009D78D7 /* ImageBoundsTests.swift in Sources */,
|
57B7A4E31EE70DC3009D78D7 /* ImageBoundsTests.swift in Sources */,
|
||||||
|
C4820B1A1F458D64008CE0FF /* MacawSVGTests.swift in Sources */,
|
||||||
5713C4F91E5C3FEE00BBA4D9 /* DelayedAnimationTests.swift in Sources */,
|
5713C4F91E5C3FEE00BBA4D9 /* DelayedAnimationTests.swift in Sources */,
|
||||||
5713C4F31E5AD46800BBA4D9 /* ControlStatesTests.swift in Sources */,
|
5713C4F31E5AD46800BBA4D9 /* ControlStatesTests.swift in Sources */,
|
||||||
57FCD27C1D76EA4600CC0FB6 /* MacawTests.swift in Sources */,
|
57FCD27C1D76EA4600CC0FB6 /* MacawTests.swift in Sources */,
|
||||||
|
27
MacawTests/MacawSVGTests.swift
Normal file
27
MacawTests/MacawSVGTests.swift
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import Macaw
|
||||||
|
|
||||||
|
class MacawSVGTests: XCTestCase {
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
super.setUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
super.tearDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSVGEllipse() {
|
||||||
|
let bundle = Bundle(for: type(of: TestUtils()))
|
||||||
|
let ellipseReferenceContent = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><ellipse cy=\"80\" ry=\"50\" rx=\"100\" cx=\"200\" fill=\"yellow\" stroke=\"purple\" stroke-width=\"2\"/></g></svg>"
|
||||||
|
do {
|
||||||
|
let node = try SVGParser.parse(bundle:bundle, path: "ellipse")
|
||||||
|
XCTAssert(SVGSerializer.serialize(node: node) == ellipseReferenceContent)
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
MacawTests/svg/svglist.txt
Normal file
1
MacawTests/svg/svglist.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
ellipse
|
@ -293,6 +293,13 @@ open class SVGConstants {
|
|||||||
"yellowgreen": 0x9acd32
|
"yellowgreen": 0x9acd32
|
||||||
]
|
]
|
||||||
|
|
||||||
|
open static func valueToColor(_ color: Int) -> String? {
|
||||||
|
return SVGConstants.colorList.filter { (k, v) -> Bool in v == color }.map { (k, v) -> String in k }.first
|
||||||
|
}
|
||||||
|
|
||||||
|
open static let defaultStrokeLineCap = LineCap.butt
|
||||||
|
open static let defaultStrokeLineJoin = LineJoin.miter
|
||||||
|
|
||||||
open static let moveToAbsolute = "M"
|
open static let moveToAbsolute = "M"
|
||||||
open static let moveToRelative = "m"
|
open static let moveToRelative = "m"
|
||||||
open static let lineToAbsolute = "L"
|
open static let lineToAbsolute = "L"
|
||||||
|
@ -449,7 +449,7 @@ open class SVGParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func getStrokeCap(_ styleParts: [String: String]) -> LineCap {
|
fileprivate func getStrokeCap(_ styleParts: [String: String]) -> LineCap {
|
||||||
var cap = LineCap.square
|
var cap = LineCap.butt
|
||||||
if let strokeCap = styleParts["stroke-linecap"] {
|
if let strokeCap = styleParts["stroke-linecap"] {
|
||||||
switch strokeCap {
|
switch strokeCap {
|
||||||
case "butt":
|
case "butt":
|
||||||
|
257
Source/svg/SVGSerializer.swift
Normal file
257
Source/svg/SVGSerializer.swift
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
//
|
||||||
|
// SVGSerializer.swift
|
||||||
|
// Macaw
|
||||||
|
//
|
||||||
|
// Created by Yuriy Kashnikov on 8/17/17.
|
||||||
|
// Copyright © 2017 Exyte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
///
|
||||||
|
/// This class serializes Macaw Scene into an SVG String
|
||||||
|
///
|
||||||
|
open class SVGSerializer {
|
||||||
|
|
||||||
|
fileprivate let width: Int?
|
||||||
|
fileprivate let height: Int?
|
||||||
|
fileprivate let id: String?
|
||||||
|
fileprivate let indent: Int
|
||||||
|
|
||||||
|
fileprivate init(width: Int?, height: Int?, id: String?) {
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.id = id
|
||||||
|
self.indent = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// header and footer
|
||||||
|
fileprivate let SVGDefaultHeader = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\""
|
||||||
|
fileprivate static let SVGDefaultId = ""
|
||||||
|
fileprivate static let SVGUndefinedSize = -1
|
||||||
|
fileprivate let SVGFooter = "</svg>"
|
||||||
|
|
||||||
|
// groups
|
||||||
|
fileprivate let SVGGroupOpenTag = "<g"
|
||||||
|
fileprivate let SVGGroupCloseTag = "</g>"
|
||||||
|
|
||||||
|
// shapes
|
||||||
|
fileprivate let SVGRectOpenTag = "<rect "
|
||||||
|
fileprivate let SVGCircleOpenTag = "<circle "
|
||||||
|
fileprivate let SVGEllipseOpenTag = "<ellipse "
|
||||||
|
fileprivate let SVGLineOpenTag = "<line "
|
||||||
|
fileprivate let SVGPolylineOpenTag = "<polyline "
|
||||||
|
fileprivate let SVGPolygonOpenTag = "<polygon "
|
||||||
|
fileprivate let SVGPathOpenTag = "<path "
|
||||||
|
fileprivate let SVGImageOpenTag = "<image "
|
||||||
|
fileprivate let SVGTextOpenTag = "<text "
|
||||||
|
|
||||||
|
fileprivate let SVGGenericEndTag = ">"
|
||||||
|
fileprivate let SVGGenericCloseTag = "/>"
|
||||||
|
|
||||||
|
fileprivate let SVGUndefinedTag = "<UNDEFINED "
|
||||||
|
fileprivate let indentPrefixSymbol = " "
|
||||||
|
|
||||||
|
|
||||||
|
fileprivate func indentTextWithOffset(_ text: String, _ offset: Int) -> String {
|
||||||
|
if self.indent != 0 {
|
||||||
|
let prefix = String(repeating: indentPrefixSymbol, count:self.indent)
|
||||||
|
return "\n\(String(repeating: prefix, count:offset))\(text)"
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func att(_ a: Double) -> String {
|
||||||
|
return String(Int(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func tag(_ tag: String, _ args: [String:String], close: Bool=false) -> String {
|
||||||
|
let attrs = args.map { "\($0)=\"\($1)\"" }.joined(separator: " ")
|
||||||
|
let closeTag = close ? " />" : ""
|
||||||
|
return "\(tag) \(attrs) \(closeTag)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func arcToSVG(_ arc: Macaw.Arc) -> String {
|
||||||
|
if (arc.shift == 0.0) {
|
||||||
|
return tag(SVGEllipseOpenTag, ["cx":att(arc.ellipse.cx), "cy":att(arc.ellipse.cy), "rx":att(arc.ellipse.rx), "ry":att(arc.ellipse.ry)])
|
||||||
|
} else {
|
||||||
|
// Convert arc to SVG format with x axis rotation, arc flag, and sweep flag
|
||||||
|
return "\(SVGUndefinedTag) arc is not implemented yet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fileprivate func polygonToSVG(_ polygon: Macaw.Polygon) -> String {
|
||||||
|
let points = polygon.points.flatMap { String($0) }.joined(separator: ",")
|
||||||
|
return tag(SVGPolygonOpenTag, ["points":points])
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func polylineToSVG(_ polyline: Macaw.Polyline) -> String {
|
||||||
|
let points = polyline.points.flatMap { String($0) }.joined(separator: ",")
|
||||||
|
return tag(SVGPolylineOpenTag, ["points":points])
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func pathToSVG(_ path: Macaw.Path) -> String {
|
||||||
|
var d = ""
|
||||||
|
for segment in path.segments {
|
||||||
|
d += "\(segment.type) \(segment.data.flatMap { String(Int($0)) }.joined(separator: " "))"
|
||||||
|
}
|
||||||
|
return tag(SVGPathOpenTag, ["d":d])
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func lineToSVG(_ line: Macaw.Line) -> String {
|
||||||
|
return tag(SVGLineOpenTag, ["x1":String(Int(line.x1)), "y1":att(line.y1), "x2":att(line.x2), "y2":att(line.y2)])
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func ellipseToSVG(_ ellipse: Macaw.Ellipse) -> String {
|
||||||
|
return tag(SVGEllipseOpenTag, ["cx":att(ellipse.cx), "cy":att(ellipse.cy), "rx":att(ellipse.rx), "ry":att(ellipse.ry)])
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func circleToSVG(_ circle: Macaw.Circle) -> String {
|
||||||
|
return tag(SVGCircleOpenTag, ["cx":att(circle.cx), "cy":att(circle.cy), "r":att(circle.r)])
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func roundRectToSVG(_ roundRect: Macaw.RoundRect) -> String {
|
||||||
|
return tag(SVGRectOpenTag, ["rx":att(roundRect.rx), "ry":att(roundRect.ry), "width":att(roundRect.rect.w), "height":att(roundRect.rect.h)])
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func rectToSVG(_ rect: Macaw.Rect) -> String {
|
||||||
|
return tag(SVGRectOpenTag, ["x":att(rect.x), "y":att(rect.y), "width":att(rect.w), "height":att(rect.h)])
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func imageToSVG(_ image: Macaw.Image) -> String {
|
||||||
|
return tag(SVGImageOpenTag, ["xlink:href":image.src, "x":att(image.place.dx), "y":att(image.place.dy), "width":String(image.w), "height":String(image.h)], close: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func textToSVG(_ text: Macaw.Text) -> String {
|
||||||
|
var result = tag(SVGTextOpenTag, ["x":att(text.place.dx), "y":att(text.place.dy)])
|
||||||
|
if let font = text.font {
|
||||||
|
result += "font-family=\"\(font.name)\" font-size=\"\(font.size)\""
|
||||||
|
}
|
||||||
|
result += SVGGenericEndTag
|
||||||
|
result += text.text
|
||||||
|
result += "</text>"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func fillToSVG(_ shape: Shape) -> String {
|
||||||
|
if let fillColor = shape.fillVar.value as? Color {
|
||||||
|
if let fill = SVGConstants.valueToColor(fillColor.val) {
|
||||||
|
return " fill=\"\(fill)\""
|
||||||
|
} else {
|
||||||
|
return " fill=\"#\(String(format:"%6X", fillColor.val))\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return " fill=\"none\""
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func strokeToSVG(_ shape: Shape) -> String {
|
||||||
|
var result = ""
|
||||||
|
if let strokeColor = shape.strokeVar.value?.fill as? Color {
|
||||||
|
if let stroke = SVGConstants.valueToColor(strokeColor.val) {
|
||||||
|
result += " stroke=\"\(stroke)\""
|
||||||
|
} else {
|
||||||
|
result += " stroke=\"#\(String(format:"%6X", strokeColor.val))\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let strokeWidth = shape.strokeVar.value?.width {
|
||||||
|
result += " stroke-width=\"\(Int(strokeWidth))\""
|
||||||
|
}
|
||||||
|
if let strokeCap = shape.strokeVar.value?.cap {
|
||||||
|
if strokeCap != SVGConstants.defaultStrokeLineCap {
|
||||||
|
result += " stroke-linecap=\"\(strokeCap)\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let strokeJoin = shape.strokeVar.value?.join {
|
||||||
|
if strokeJoin != SVGConstants.defaultStrokeLineJoin {
|
||||||
|
result += " stroke-linejoin=\"\(strokeJoin)\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func macawShapeToSvgShape (macawShape: Shape) -> String {
|
||||||
|
var result = ""
|
||||||
|
let locus = macawShape.formVar.value
|
||||||
|
switch locus {
|
||||||
|
case let arc as Macaw.Arc:
|
||||||
|
result += arcToSVG(arc)
|
||||||
|
case let polygon as Macaw.Polygon:
|
||||||
|
result += polygonToSVG(polygon)
|
||||||
|
case let polyline as Macaw.Polyline:
|
||||||
|
result += polylineToSVG(polyline)
|
||||||
|
case let path as Macaw.Path:
|
||||||
|
result += pathToSVG(path)
|
||||||
|
case let line as Macaw.Line:
|
||||||
|
result += lineToSVG(line)
|
||||||
|
case let ellipse as Macaw.Ellipse:
|
||||||
|
result += ellipseToSVG(ellipse)
|
||||||
|
case let circle as Macaw.Circle:
|
||||||
|
result += circleToSVG(circle)
|
||||||
|
case let roundRect as Macaw.RoundRect:
|
||||||
|
result += roundRectToSVG(roundRect)
|
||||||
|
case let rect as Macaw.Rect:
|
||||||
|
result += rectToSVG(rect)
|
||||||
|
default:
|
||||||
|
result += "\(SVGUndefinedTag) locus:\(locus)"
|
||||||
|
}
|
||||||
|
result += fillToSVG(macawShape)
|
||||||
|
result += strokeToSVG(macawShape)
|
||||||
|
|
||||||
|
result += SVGGenericCloseTag
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fileprivate func serialize(node: Node, offset: Int) -> String {
|
||||||
|
if let shape = node as? Shape {
|
||||||
|
return indentTextWithOffset(macawShapeToSvgShape(macawShape: shape), offset)
|
||||||
|
}
|
||||||
|
if let group = node as? Group {
|
||||||
|
var result = indentTextWithOffset(SVGGroupOpenTag, offset)
|
||||||
|
if ([group.place.dx, group.place.dy].map{ Int($0) } != [0, 0]) {
|
||||||
|
if ([group.place.m11, group.place.m12, group.place.m21, group.place.m22].map { Int($0) } == [1, 0, 0, 1]) {
|
||||||
|
result += " transform=\"translate(\(Int(group.place.dx)),\(Int(group.place.dy)))\""
|
||||||
|
} else {
|
||||||
|
let matrixArgs = [group.place.m11, group.place.m12, group.place.m21, group.place.m22, group.place.dx, group.place.dy].map{ String($0) }.joined(separator: ",")
|
||||||
|
result += " transform=\"matrix(\(matrixArgs))\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += SVGGenericEndTag
|
||||||
|
for child in group.contentsVar.value {
|
||||||
|
result += serialize(node: child, offset: offset + 1)
|
||||||
|
}
|
||||||
|
result += indentTextWithOffset(SVGGroupCloseTag, offset)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if let image = node as? Image {
|
||||||
|
return imageToSVG(image)
|
||||||
|
}
|
||||||
|
if let text = node as? Macaw.Text {
|
||||||
|
return textToSVG(text)
|
||||||
|
}
|
||||||
|
return "SVGUndefinedTag \(node)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func serialize(node:Node) -> String {
|
||||||
|
var optionalSection = ""
|
||||||
|
if let w = width {
|
||||||
|
optionalSection += "width=\"\(w)\""
|
||||||
|
}
|
||||||
|
if let h = height {
|
||||||
|
optionalSection += "height=\"\(h)\""
|
||||||
|
}
|
||||||
|
if let i = id {
|
||||||
|
optionalSection += "id=\"\(i)\""
|
||||||
|
}
|
||||||
|
var result = [SVGDefaultHeader, optionalSection, SVGGenericEndTag].joined(separator: " ")
|
||||||
|
result += serialize(node: node, offset: 1)
|
||||||
|
result += indentTextWithOffset(SVGFooter, 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
open class func serialize(node: Node, width: Int? = nil, height: Int? = nil, id: String? = nil) -> String {
|
||||||
|
return SVGSerializer(width: width, height:height, id: id).serialize(node: node)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user