From 9e8f739b3f6e106aa5e0200cbe5132ddaaaf32f9 Mon Sep 17 00:00:00 2001 From: Yuriy Kashnikov Date: Thu, 17 Aug 2017 16:08:36 +0700 Subject: [PATCH 1/7] Implement Macaw to SVG serializer - Add Macaw to SVG serializer - Add basic test for serializer - Fix default LineCap in Parses - Add default linecap and linejoin values to SVGConstants --- MacawTests/MacawSVGTests.swift | 30 +++++ Source/svg/SVGConstants.swift | 7 ++ Source/svg/SVGParser.swift | 2 +- Source/svg/SVGSerializer.swift | 220 +++++++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 MacawTests/MacawSVGTests.swift create mode 100644 Source/svg/SVGSerializer.swift diff --git a/MacawTests/MacawSVGTests.swift b/MacawTests/MacawSVGTests.swift new file mode 100644 index 00000000..d33e07ad --- /dev/null +++ b/MacawTests/MacawSVGTests.swift @@ -0,0 +1,30 @@ +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 testSVGSerializeEllipse() { + let bundle = Bundle(for: type(of: TestUtils())) + let ellipseReferenceContent = " " + + + let name = "ellipse" + do { + let rootNode = try SVGParser.parse(bundle:bundle, path: name) + let svg = SVGSerializer.serialize(node: rootNode) + print(svg) + XCTAssertTrue(svg == ellipseReferenceContent) + } catch _ {} + } + +} diff --git a/Source/svg/SVGConstants.swift b/Source/svg/SVGConstants.swift index 00946795..0d35c29e 100644 --- a/Source/svg/SVGConstants.swift +++ b/Source/svg/SVGConstants.swift @@ -151,6 +151,13 @@ open class SVGConstants { "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 moveToRelative = "m" open static let lineToAbsolute = "L" diff --git a/Source/svg/SVGParser.swift b/Source/svg/SVGParser.swift index 86b6c367..b1a09e14 100644 --- a/Source/svg/SVGParser.swift +++ b/Source/svg/SVGParser.swift @@ -411,7 +411,7 @@ open class SVGParser { } fileprivate func getStrokeCap(_ styleParts: [String: String]) -> LineCap { - var cap = LineCap.square + var cap = LineCap.butt if let strokeCap = styleParts["stroke-linecap"] { switch strokeCap { case "butt": diff --git a/Source/svg/SVGSerializer.swift b/Source/svg/SVGSerializer.swift new file mode 100644 index 00000000..be0d4b65 --- /dev/null +++ b/Source/svg/SVGSerializer.swift @@ -0,0 +1,220 @@ +// +// 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 init(width: Int, height: Int, id: String) { + self.width = width + self.height = height + self.id = id + } + + fileprivate init() { + self.width = SVGDefaultWidth + self.height = SVGDefaultHeight + self.id = SVGDefaultId + } + + // header and footer + fileprivate let SVGDefaultHeader = " String { + var prefix = "" + for _ in 1.. String { + return String(Int(a)) + } + + fileprivate func tag(_ tag: String, _ args: [String:String]) -> String { + let attrs = args.map { "\($0)=\"\($1)\"" }.joined(separator: " ") + return "\(tag) \(attrs)" + } + + 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 + } + } + + + 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 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 + } + 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(text: macawShapeToSvgShape(macawShape: shape), offset: offset) + } + if let group = node as? Group { + var result = indentTextWithOffset(text: SVGGroupOpenTag, offset: offset) + for child in group.contentsVar.value { + result += serialize(node: child, offset: offset + 1) + } + result += indentTextWithOffset(text: SVGGroupCloseTag, offset: offset) + return result + } + return SVGUndefinedTag + } + + fileprivate func serialize(node:Node) -> String { + var result = [SVGDefaultHeader, "id=\"\(self.id)\"", "width=\"\(self.width)\"", "height=\"\(self.height)\"", SVGGenericEndTag].joined(separator: " ") + result += serialize(node: node, offset: 1) + result += SVGFooter + return result + } + + open class func serialize(node: Node) -> String { + return SVGSerializer().serialize(node: node) + } + +} From 6be05cb99d613e6a40708de5d4d13bc2baf3b50e Mon Sep 17 00:00:00 2001 From: Yuriy Kashnikov Date: Thu, 17 Aug 2017 16:42:48 +0700 Subject: [PATCH 2/7] Minor fixes - Same style for string interpolation - Indentation control --- Macaw.xcodeproj/project.pbxproj | 8 ++++++++ MacawTests/MacawSVGTests.swift | 4 ++-- Source/svg/SVGSerializer.swift | 36 ++++++++++++++++++++------------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/Macaw.xcodeproj/project.pbxproj b/Macaw.xcodeproj/project.pbxproj index 117c2893..f9fd4283 100644 --- a/Macaw.xcodeproj/project.pbxproj +++ b/Macaw.xcodeproj/project.pbxproj @@ -133,6 +133,8 @@ 57FCD2771D76EA4600CC0FB6 /* Macaw.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57FCD26C1D76EA4600CC0FB6 /* Macaw.framework */; }; 57FCD27C1D76EA4600CC0FB6 /* MacawTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FCD27B1D76EA4600CC0FB6 /* MacawTests.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 */ /* Begin PBXContainerItemProxy section */ @@ -287,6 +289,8 @@ 57FCD27B1D76EA4600CC0FB6 /* MacawTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacawTests.swift; sourceTree = ""; }; 57FCD27D1D76EA4600CC0FB6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A7E675551EC4213500BD9ECB /* NodeBoundsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NodeBoundsTests.swift; path = Bounds/NodeBoundsTests.swift; sourceTree = ""; }; + C4820B171F458D0E008CE0FF /* SVGSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGSerializer.swift; sourceTree = ""; }; + C4820B191F458D64008CE0FF /* MacawSVGTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacawSVGTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -576,6 +580,7 @@ 57E5E1481E3B393900D1CB28 /* SVGParserError.swift */, 57E5E1491E3B393900D1CB28 /* SVGParserRegexHelper.swift */, 57E5E14A1E3B393900D1CB28 /* SVGView.swift */, + C4820B171F458D0E008CE0FF /* SVGSerializer.swift */, ); path = svg; sourceTree = ""; @@ -635,6 +640,7 @@ 57CAB1241D7832E000FD8E47 /* svg */, 57CAB1221D782DFC00FD8E47 /* TestUtils.swift */, 57FCD27B1D76EA4600CC0FB6 /* MacawTests.swift */, + C4820B191F458D64008CE0FF /* MacawSVGTests.swift */, 57FCD27D1D76EA4600CC0FB6 /* Info.plist */, ); path = MacawTests; @@ -808,6 +814,7 @@ 57E5E1951E3B393900D1CB28 /* PathSegment.swift in Sources */, 57E5E1A41E3B393900D1CB28 /* ImageRenderer.swift in Sources */, 57E5E1621E3B393900D1CB28 /* PathFunctions.swift in Sources */, + C4820B181F458D0E008CE0FF /* SVGSerializer.swift in Sources */, 57E5E16E1E3B393900D1CB28 /* MorphingAnimation.swift in Sources */, 57A27BD11E44C5460057BD3A /* ShapeAnimation.swift in Sources */, 57E5E15F1E3B393900D1CB28 /* TransformInterpolation.swift in Sources */, @@ -886,6 +893,7 @@ files = ( 5713C4F71E5C34C700BBA4D9 /* SequenceAnimationTests.swift in Sources */, 57B7A4E31EE70DC3009D78D7 /* ImageBoundsTests.swift in Sources */, + C4820B1A1F458D64008CE0FF /* MacawSVGTests.swift in Sources */, 5713C4F91E5C3FEE00BBA4D9 /* DelayedAnimationTests.swift in Sources */, 5713C4F31E5AD46800BBA4D9 /* ControlStatesTests.swift in Sources */, 57FCD27C1D76EA4600CC0FB6 /* MacawTests.swift in Sources */, diff --git a/MacawTests/MacawSVGTests.swift b/MacawTests/MacawSVGTests.swift index d33e07ad..4496e1f7 100644 --- a/MacawTests/MacawSVGTests.swift +++ b/MacawTests/MacawSVGTests.swift @@ -15,13 +15,13 @@ class MacawSVGTests: XCTestCase { func testSVGSerializeEllipse() { let bundle = Bundle(for: type(of: TestUtils())) - let ellipseReferenceContent = " " + let ellipseReferenceContent = "" let name = "ellipse" do { let rootNode = try SVGParser.parse(bundle:bundle, path: name) - let svg = SVGSerializer.serialize(node: rootNode) + let svg = SVGSerializer.serialize(node: rootNode, indent:0) print(svg) XCTAssertTrue(svg == ellipseReferenceContent) } catch _ {} diff --git a/Source/svg/SVGSerializer.swift b/Source/svg/SVGSerializer.swift index be0d4b65..b0640770 100644 --- a/Source/svg/SVGSerializer.swift +++ b/Source/svg/SVGSerializer.swift @@ -16,19 +16,29 @@ 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) { + fileprivate init(width: Int, height: Int, id: String, indent: Int) { self.width = width self.height = height self.id = id + self.indent = indent } + fileprivate init(indent:Int) { + self.width = SVGDefaultWidth + self.height = SVGDefaultHeight + self.id = SVGDefaultId + self.indent = indent + } + fileprivate init() { self.width = SVGDefaultWidth self.height = SVGDefaultHeight self.id = SVGDefaultId + self.indent = 1 } - + // header and footer fileprivate let SVGDefaultHeader = " String { - var prefix = "" - for _ in 1.. String { @@ -119,27 +129,25 @@ open class SVGSerializer { 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 fillToSVG(_ shape: Shape) -> String { if let fillColor = shape.fillVar.value as? Color { if let fill = SVGConstants.valueToColor(fillColor.val) { - return " fill=\"" + fill + "\"" + 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 + "\"" + result += " stroke=\"\(stroke)\"" } else { - result += " stroke=\"#" + String(format:"%6X", strokeColor.val) + "\"" + result += " stroke=\"#\(String(format:"%6X", strokeColor.val))\"" } } if let strokeWidth = shape.strokeVar.value?.width { @@ -209,12 +217,12 @@ open class SVGSerializer { fileprivate func serialize(node:Node) -> String { var result = [SVGDefaultHeader, "id=\"\(self.id)\"", "width=\"\(self.width)\"", "height=\"\(self.height)\"", SVGGenericEndTag].joined(separator: " ") result += serialize(node: node, offset: 1) - result += SVGFooter + result += indentTextWithOffset(text: SVGFooter, offset: 0) return result } - open class func serialize(node: Node) -> String { - return SVGSerializer().serialize(node: node) + open class func serialize(node: Node, indent: Int = 1) -> String { + return SVGSerializer(indent:indent).serialize(node: node) } } From 2c9cf915f835cfae195ec301a0c2ad438feefcb4 Mon Sep 17 00:00:00 2001 From: Yuriy Kashnikov Date: Thu, 17 Aug 2017 16:56:46 +0700 Subject: [PATCH 3/7] Minor fixes --- Source/svg/SVGSerializer.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/svg/SVGSerializer.swift b/Source/svg/SVGSerializer.swift index b0640770..c8951463 100644 --- a/Source/svg/SVGSerializer.swift +++ b/Source/svg/SVGSerializer.swift @@ -65,7 +65,7 @@ open class SVGSerializer { fileprivate let indentPrefixSymbol = " " - fileprivate func indentTextWithOffset(text: String, offset: Int) -> String { + 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)" @@ -201,14 +201,14 @@ open class SVGSerializer { fileprivate func serialize(node: Node, offset: Int) -> String { if let shape = node as? Shape { - return indentTextWithOffset(text: macawShapeToSvgShape(macawShape: shape), offset: offset) + return indentTextWithOffset(macawShapeToSvgShape(macawShape: shape), offset) } if let group = node as? Group { - var result = indentTextWithOffset(text: SVGGroupOpenTag, offset: offset) + var result = indentTextWithOffset(SVGGroupOpenTag, offset) for child in group.contentsVar.value { result += serialize(node: child, offset: offset + 1) } - result += indentTextWithOffset(text: SVGGroupCloseTag, offset: offset) + result += indentTextWithOffset(SVGGroupCloseTag, offset) return result } return SVGUndefinedTag @@ -217,7 +217,7 @@ open class SVGSerializer { fileprivate func serialize(node:Node) -> String { var result = [SVGDefaultHeader, "id=\"\(self.id)\"", "width=\"\(self.width)\"", "height=\"\(self.height)\"", SVGGenericEndTag].joined(separator: " ") result += serialize(node: node, offset: 1) - result += indentTextWithOffset(text: SVGFooter, offset: 0) + result += indentTextWithOffset(SVGFooter, 0) return result } From cd4d3ce8f695eddb4ef1ef8b0e4ef68c53f50bb2 Mon Sep 17 00:00:00 2001 From: Yuriy Kashnikov Date: Fri, 18 Aug 2017 12:18:47 +0700 Subject: [PATCH 4/7] Support Macaw.Image in serializer --- MacawTests/MacawSVGTests.swift | 40 ++++++++++++++++++++++++---------- MacawTests/svg/svglist.txt | 1 + Source/svg/SVGSerializer.swift | 19 +++++++++++----- 3 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 MacawTests/svg/svglist.txt diff --git a/MacawTests/MacawSVGTests.swift b/MacawTests/MacawSVGTests.swift index 4496e1f7..56647a04 100644 --- a/MacawTests/MacawSVGTests.swift +++ b/MacawTests/MacawSVGTests.swift @@ -13,18 +13,36 @@ class MacawSVGTests: XCTestCase { super.tearDown() } - func testSVGSerializeEllipse() { + @available(iOS 10.0, *) + func testSVGFromList() { let bundle = Bundle(for: type(of: TestUtils())) - let ellipseReferenceContent = "" - - - let name = "ellipse" - do { - let rootNode = try SVGParser.parse(bundle:bundle, path: name) - let svg = SVGSerializer.serialize(node: rootNode, indent:0) - print(svg) - XCTAssertTrue(svg == ellipseReferenceContent) - } catch _ {} + var count = 0 + if let path = bundle.path(forResource: "svglist", ofType: "txt") { + do { + let data = try String(contentsOfFile: path, encoding: .utf8) + let myStrings = data.components(separatedBy: .newlines) + for name in myStrings { + count += 1 + print ("PROCESSING ", count, " -- ", name) + let dst = "/Users/ykashnikov/exyte/svg-test-suite/macaw-svg/" + name + ".svg" + do { + let rootNode = try SVGParser.parse(bundle:bundle, path: name) + let svgContent = SVGSerializer.serialize(node: rootNode, indent: 1) + do { + try svgContent.write(toFile: dst, atomically: false, encoding:String.Encoding.utf8) + } + catch let error as NSError { + print("Write failed for:\(name) error:\(error)") + } + print (count, name, " PASSED") + } catch _ { + print (count, name, " FAILED") + } + } + } catch { + print(error) + } + } } } diff --git a/MacawTests/svg/svglist.txt b/MacawTests/svg/svglist.txt new file mode 100644 index 00000000..33a7050c --- /dev/null +++ b/MacawTests/svg/svglist.txt @@ -0,0 +1 @@ +ellipse diff --git a/Source/svg/SVGSerializer.swift b/Source/svg/SVGSerializer.swift index c8951463..88dd09bc 100644 --- a/Source/svg/SVGSerializer.swift +++ b/Source/svg/SVGSerializer.swift @@ -58,6 +58,7 @@ open class SVGSerializer { fileprivate let SVGPolylineOpenTag = " String { + fileprivate func tag(_ tag: String, _ args: [String:String], close: Bool=false) -> String { let attrs = args.map { "\($0)=\"\($1)\"" }.joined(separator: " ") - return "\(tag) \(attrs)" + let closeTag = close ? " />" : "" + return "\(tag) \(attrs) \(closeTag)" } fileprivate func arcToSVG(_ arc: Macaw.Arc) -> String { @@ -87,7 +89,7 @@ open class SVGSerializer { 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 + return "\(SVGUndefinedTag) arc is not implemented yet" } } @@ -130,6 +132,10 @@ open class SVGSerializer { 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 fillToSVG(_ shape: Shape) -> String { if let fillColor = shape.fillVar.value as? Color { if let fill = SVGConstants.valueToColor(fillColor.val) { @@ -189,7 +195,7 @@ open class SVGSerializer { case let rect as Macaw.Rect: result += rectToSVG(rect) default: - result += SVGUndefinedTag + result += "\(SVGUndefinedTag) locus:\(locus)" } result += fillToSVG(macawShape) result += strokeToSVG(macawShape) @@ -211,7 +217,10 @@ open class SVGSerializer { result += indentTextWithOffset(SVGGroupCloseTag, offset) return result } - return SVGUndefinedTag + if let image = node as? Image { + return imageToSVG(image) + } + return "SVGUndefinedTag \(node)" } fileprivate func serialize(node:Node) -> String { From 2845efb93ce594b13d4455912d635cc4f1d4915b Mon Sep 17 00:00:00 2001 From: Yuriy Kashnikov Date: Fri, 18 Aug 2017 12:34:46 +0700 Subject: [PATCH 5/7] Support Macaw.Text in serializer --- Source/svg/SVGSerializer.swift | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Source/svg/SVGSerializer.swift b/Source/svg/SVGSerializer.swift index 88dd09bc..4f864c61 100644 --- a/Source/svg/SVGSerializer.swift +++ b/Source/svg/SVGSerializer.swift @@ -59,6 +59,8 @@ open class SVGSerializer { fileprivate let SVGPolygonOpenTag = " 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) + 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 += "" + return result } fileprivate func fillToSVG(_ shape: Shape) -> String { @@ -220,6 +233,9 @@ open class SVGSerializer { if let image = node as? Image { return imageToSVG(image) } + if let text = node as? Macaw.Text { + return textToSVG(text) + } return "SVGUndefinedTag \(node)" } From 524b734fc11d34c8f04a750a0eb3ff459fa1d59c Mon Sep 17 00:00:00 2001 From: Yuriy Kashnikov Date: Wed, 13 Sep 2017 11:54:54 +0300 Subject: [PATCH 6/7] Improve SVG Serializer - Support transform and placing - Expose height and width --- Source/svg/SVGSerializer.swift | 45 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/Source/svg/SVGSerializer.swift b/Source/svg/SVGSerializer.swift index 4f864c61..11e9f74c 100644 --- a/Source/svg/SVGSerializer.swift +++ b/Source/svg/SVGSerializer.swift @@ -25,29 +25,14 @@ open class SVGSerializer { self.indent = indent } - fileprivate init(indent:Int) { - self.width = SVGDefaultWidth - self.height = SVGDefaultHeight - self.id = SVGDefaultId - self.indent = indent - } - - fileprivate init() { - self.width = SVGDefaultWidth - self.height = SVGDefaultHeight - self.id = SVGDefaultId - self.indent = 1 - } - // header and footer fileprivate let SVGDefaultHeader = " String { - var result = [SVGDefaultHeader, "id=\"\(self.id)\"", "width=\"\(self.width)\"", "height=\"\(self.height)\"", SVGGenericEndTag].joined(separator: " ") + var sizes = "" + if width != -1 { + sizes += "width=\"\(self.width)\"" + } + if height != -1 { + sizes += "height=\"\(self.height)\"" + } + var result = [SVGDefaultHeader, "id=\"\(self.id)\"", sizes, SVGGenericEndTag].joined(separator: " ") result += serialize(node: node, offset: 1) result += indentTextWithOffset(SVGFooter, 0) return result } - open class func serialize(node: Node, indent: Int = 1) -> String { - return SVGSerializer(indent:indent).serialize(node: node) + open class func serialize(node: Node, width: Int = SVGUndefinedSize, height: Int = SVGUndefinedSize, id: String = SVGDefaultId, indent: Int = 1) -> String { + return SVGSerializer(width: width, height:height, id: id, indent:indent).serialize(node: node) } - + } From d27e20e1f54048f6955c25d2ee12d60afef1aeb8 Mon Sep 17 00:00:00 2001 From: Yuriy Kashnikov Date: Wed, 13 Sep 2017 16:02:04 +0300 Subject: [PATCH 7/7] Fix review comments - Update SVG tests: added basic test for ellipse.svg - Handle optional params via Optional --- MacawTests/MacawSVGTests.swift | 35 +++++++--------------------------- Source/svg/SVGSerializer.swift | 29 +++++++++++++++------------- 2 files changed, 23 insertions(+), 41 deletions(-) diff --git a/MacawTests/MacawSVGTests.swift b/MacawTests/MacawSVGTests.swift index 56647a04..9c973f97 100644 --- a/MacawTests/MacawSVGTests.swift +++ b/MacawTests/MacawSVGTests.swift @@ -13,35 +13,14 @@ class MacawSVGTests: XCTestCase { super.tearDown() } - @available(iOS 10.0, *) - func testSVGFromList() { + func testSVGEllipse() { let bundle = Bundle(for: type(of: TestUtils())) - var count = 0 - if let path = bundle.path(forResource: "svglist", ofType: "txt") { - do { - let data = try String(contentsOfFile: path, encoding: .utf8) - let myStrings = data.components(separatedBy: .newlines) - for name in myStrings { - count += 1 - print ("PROCESSING ", count, " -- ", name) - let dst = "/Users/ykashnikov/exyte/svg-test-suite/macaw-svg/" + name + ".svg" - do { - let rootNode = try SVGParser.parse(bundle:bundle, path: name) - let svgContent = SVGSerializer.serialize(node: rootNode, indent: 1) - do { - try svgContent.write(toFile: dst, atomically: false, encoding:String.Encoding.utf8) - } - catch let error as NSError { - print("Write failed for:\(name) error:\(error)") - } - print (count, name, " PASSED") - } catch _ { - print (count, name, " FAILED") - } - } - } catch { - print(error) - } + let ellipseReferenceContent = "" + do { + let node = try SVGParser.parse(bundle:bundle, path: "ellipse") + XCTAssert(SVGSerializer.serialize(node: node) == ellipseReferenceContent) + } catch { + print(error) } } diff --git a/Source/svg/SVGSerializer.swift b/Source/svg/SVGSerializer.swift index 11e9f74c..6873f09d 100644 --- a/Source/svg/SVGSerializer.swift +++ b/Source/svg/SVGSerializer.swift @@ -13,16 +13,16 @@ import Foundation /// open class SVGSerializer { - fileprivate let width: Int - fileprivate let height: Int - fileprivate let id: String + fileprivate let width: Int? + fileprivate let height: Int? + fileprivate let id: String? fileprivate let indent: Int - fileprivate init(width: Int, height: Int, id: String, indent: Int) { + fileprivate init(width: Int?, height: Int?, id: String?) { self.width = width self.height = height self.id = id - self.indent = indent + self.indent = 0 } // header and footer @@ -234,21 +234,24 @@ open class SVGSerializer { } fileprivate func serialize(node:Node) -> String { - var sizes = "" - if width != -1 { - sizes += "width=\"\(self.width)\"" + var optionalSection = "" + if let w = width { + optionalSection += "width=\"\(w)\"" } - if height != -1 { - sizes += "height=\"\(self.height)\"" + if let h = height { + optionalSection += "height=\"\(h)\"" } - var result = [SVGDefaultHeader, "id=\"\(self.id)\"", sizes, SVGGenericEndTag].joined(separator: " ") + 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 = SVGUndefinedSize, height: Int = SVGUndefinedSize, id: String = SVGDefaultId, indent: Int = 1) -> String { - return SVGSerializer(width: width, height:height, id: id, indent:indent).serialize(node: node) + 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) } }