diff --git a/Macaw.xcodeproj/project.pbxproj b/Macaw.xcodeproj/project.pbxproj index 55b1dbc9..321cf6c6 100644 --- a/Macaw.xcodeproj/project.pbxproj +++ b/Macaw.xcodeproj/project.pbxproj @@ -270,6 +270,14 @@ C43B06661F99EE7300787A35 /* cubicAbsolute.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B06641F99EE7200787A35 /* cubicAbsolute.svg */; }; C43B06671F99EE7300787A35 /* cubicRelative.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B06651F99EE7300787A35 /* cubicRelative.svg */; }; C43B06691F99FC2300787A35 /* pathbounds4.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B06681F99FC2300787A35 /* pathbounds4.svg */; }; + C43B06511F9866E400787A35 /* Locus+ToPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43B06501F9866E400787A35 /* Locus+ToPath.swift */; }; + C43B06531F989D9300787A35 /* transform.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B06521F989D9300787A35 /* transform.reference */; }; + C43B06551F98A53600787A35 /* group.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B06541F98A53600787A35 /* group.reference */; }; + C43B06571F98A7B700787A35 /* arcsgroup.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B06561F98A7B700787A35 /* arcsgroup.reference */; }; + C43B06591F98A84200787A35 /* style.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B06581F98A84200787A35 /* style.reference */; }; + C43B065B1F98A9E000787A35 /* clipManual.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B065A1F98A9E000787A35 /* clipManual.reference */; }; + C43B065F1F98AAA500787A35 /* clip.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B065E1F98AAA500787A35 /* clip.reference */; }; + C43B06611F98ACFC00787A35 /* textBasicTransform.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B06601F98ACFC00787A35 /* textBasicTransform.reference */; }; C46E83551F94B20E00208037 /* transform.svg in Resources */ = {isa = PBXBuildFile; fileRef = C46E83541F94B20E00208037 /* transform.svg */; }; C4820B181F458D0E008CE0FF /* SVGSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4820B171F458D0E008CE0FF /* SVGSerializer.swift */; }; C4820B1A1F458D64008CE0FF /* MacawSVGTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4820B191F458D64008CE0FF /* MacawSVGTests.swift */; }; @@ -459,6 +467,14 @@ C43B06641F99EE7200787A35 /* cubicAbsolute.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = cubicAbsolute.svg; path = Bounds/cubicAbsolute.svg; sourceTree = ""; }; C43B06651F99EE7300787A35 /* cubicRelative.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = cubicRelative.svg; path = Bounds/cubicRelative.svg; sourceTree = ""; }; C43B06681F99FC2300787A35 /* pathbounds4.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = pathbounds4.svg; path = Bounds/pathbounds4.svg; sourceTree = ""; }; + C43B06501F9866E400787A35 /* Locus+ToPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locus+ToPath.swift"; sourceTree = ""; }; + C43B06521F989D9300787A35 /* transform.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = transform.reference; sourceTree = ""; }; + C43B06541F98A53600787A35 /* group.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = group.reference; sourceTree = ""; }; + C43B06561F98A7B700787A35 /* arcsgroup.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = arcsgroup.reference; sourceTree = ""; }; + C43B06581F98A84200787A35 /* style.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = style.reference; sourceTree = ""; }; + C43B065A1F98A9E000787A35 /* clipManual.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = clipManual.reference; sourceTree = ""; }; + C43B065E1F98AAA500787A35 /* clip.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = clip.reference; sourceTree = ""; }; + C43B06601F98ACFC00787A35 /* textBasicTransform.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = textBasicTransform.reference; sourceTree = ""; }; C46E83541F94B20E00208037 /* transform.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = transform.svg; 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 = ""; }; @@ -554,6 +570,13 @@ 57CAB12B1D7832E000FD8E47 /* rect.svg */, 57CAB12C1D7832E000FD8E47 /* roundRect.svg */, 57CAB12D1D7832E000FD8E47 /* triangle.svg */, + C43B06521F989D9300787A35 /* transform.reference */, + C43B06541F98A53600787A35 /* group.reference */, + C43B06561F98A7B700787A35 /* arcsgroup.reference */, + C43B06581F98A84200787A35 /* style.reference */, + C43B065A1F98A9E000787A35 /* clipManual.reference */, + C43B065E1F98AAA500787A35 /* clip.reference */, + C43B06601F98ACFC00787A35 /* textBasicTransform.reference */, ); path = svg; sourceTree = ""; @@ -723,6 +746,7 @@ 57E5E1281E3B393900D1CB28 /* Insets.swift */, 57E5E1291E3B393900D1CB28 /* Line.swift */, 57E5E12A1E3B393900D1CB28 /* Locus.swift */, + C43B06501F9866E400787A35 /* Locus+ToPath.swift */, 57E5E12B1E3B393900D1CB28 /* MoveTo.swift */, 57E5E12C1E3B393900D1CB28 /* Path.swift */, 57E5E12D1E3B393900D1CB28 /* PathBuilder.swift */, @@ -1039,8 +1063,16 @@ C43B064D1F9738EF00787A35 /* clip.svg in Resources */, 57B7A4E11EE70DA5009D78D7 /* logo_base64.txt in Resources */, C43B06671F99EE7300787A35 /* cubicRelative.svg in Resources */, + C43B06571F98A7B700787A35 /* arcsgroup.reference in Resources */, + C43B06531F989D9300787A35 /* transform.reference in Resources */, + C43B06611F98ACFC00787A35 /* textBasicTransform.reference in Resources */, + C43B064D1F9738EF00787A35 /* clip.svg in Resources */, + 57B7A4E11EE70DA5009D78D7 /* logo_base64.txt in Resources */, + C43B06591F98A84200787A35 /* style.reference in Resources */, + C43B065F1F98AAA500787A35 /* clip.reference in Resources */, C410148E1F834D290022EE44 /* style.svg in Resources */, C4BD40BB1F8F58B0003034F0 /* pathbounds1.svg in Resources */, + C43B065B1F98A9E000787A35 /* clipManual.reference in Resources */, C4BD40BC1F8F58B0003034F0 /* pathbounds2.svg in Resources */, 57CAB1301D7832E000FD8E47 /* group.svg in Resources */, C43B06661F99EE7300787A35 /* cubicAbsolute.svg in Resources */, @@ -1053,6 +1085,7 @@ 57CAB1321D7832E000FD8E47 /* polygon.svg in Resources */, 57CAB12F1D7832E000FD8E47 /* ellipse.svg in Resources */, 57CAB1341D7832E000FD8E47 /* rect.svg in Resources */, + C43B06551F98A53600787A35 /* group.reference in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1218,6 +1251,7 @@ buildActionMask = 2147483647; files = ( 57E5E19E1E3B393900D1CB28 /* Group.swift in Sources */, + C43B06511F9866E400787A35 /* Locus+ToPath.swift in Sources */, 57E5E1A91E3B393900D1CB28 /* TextRenderer.swift in Sources */, 57E5E1B01E3B393900D1CB28 /* CGFloat+Double.swift in Sources */, 57E5E1591E3B393900D1CB28 /* CGExtensions.swift in Sources */, diff --git a/MacawTests/MacawSVGTests.swift b/MacawTests/MacawSVGTests.swift index 5db62085..75a0f3ba 100644 --- a/MacawTests/MacawSVGTests.swift +++ b/MacawTests/MacawSVGTests.swift @@ -13,29 +13,53 @@ class MacawSVGTests: XCTestCase { super.tearDown() } + func validate(node: Node, referenceFile: String) { + let bundle = Bundle(for: type(of: TestUtils())) + + do { + if let path = bundle.path(forResource: referenceFile, ofType: "reference") { + let clipReferenceContent = try String.init(contentsOfFile: path).trimmingCharacters(in: .newlines) + XCTAssertEqual(SVGSerializer.serialize(node: node), clipReferenceContent) + } + } catch { + print(error) + XCTFail() + } + } + + func validate(_ test: String, withReference: Bool = false) { + let bundle = Bundle(for: type(of: TestUtils())) + var ext = "svg" + if withReference { + ext = "reference" + } + do { + if let path = bundle.path(forResource: test, ofType: ext) { + let referenceContent = try String.init(contentsOfFile: path).trimmingCharacters(in: .newlines) + let node = try SVGParser.parse(bundle:bundle, path: test) + let testContent = SVGSerializer.serialize(node: node) + .replacingOccurrences(of: "version=\"1.1\" >", with: "version=\"1.1\" >") + .replacingOccurrences(of: "defs>", with: "defs>") + .replacingOccurrences(of: "", with: "") + XCTAssertEqual(testContent, referenceContent) + } + } catch { + print(error) + XCTFail() + } + } + func testTextBasicTransform() { - let referenceContent = "Point" let text1 = Text(text: "Point") - text1.place = Transform(m11: cos(Double.pi/4.0), m12: -sin(Double.pi/4.0), m21: sin(Double.pi/4.0), m22: cos(Double.pi/4.0), dx: 0, dy: 0) + text1.place = Transform(m11: cos(.pi/4.0), m12: -sin(.pi/4.0), m21: sin(.pi/4.0), m22: cos(.pi/4.0), dx: 0, dy: 0) let group1 = Group(contents: [text1]) group1.place = Transform(dx: 100, dy: 100) let node = Group(contents: [group1]) - XCTAssertEqual(SVGSerializer.serialize(node: node), referenceContent) - } - - func testClipWithParser() { - let bundle = Bundle(for: type(of: TestUtils())) - let clipReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "clip") - XCTAssertEqual(SVGSerializer.serialize(node: node), clipReferenceContent) - } catch { - print(error) - } + + validate(node: node, referenceFile: "testBasicTransform") } func testClipManual() { - let clipReferenceContent = "" let path1 = Rect(x: 10, y: 10, w: 90, h: 90) let circle1 = Circle(cx: 20, cy: 20, r: 20).fill(with: Color.red) circle1.clip = path1 @@ -43,18 +67,8 @@ class MacawSVGTests: XCTestCase { let circle2 = Circle(cx: 120, cy: 120, r: 20).fill(with: Color.green) circle2.clip = path2 let node = Group(contents:[circle1, circle2]) - XCTAssertEqual(SVGSerializer.serialize(node: node), clipReferenceContent) - } - - func testCSSStyleReference() { - let bundle = Bundle(for: type(of: TestUtils())) - let styleReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "style") - XCTAssertEqual(SVGSerializer.serialize(node: node), styleReferenceContent) - } catch { - print(error) - } + + validate(node: node, referenceFile: "clipManual") } func testSVGClearColor() { @@ -69,8 +83,8 @@ class MacawSVGTests: XCTestCase { let g2 = Group(contents:[Ellipse(cx: 20, cy: 20, rx: 20, ry:20).arc(shift: 1.570796251297, extent: 1.57079637050629).stroke(fill: Color.green)], place: Transform(dx:10, dy: 140)) let g3 = Group(contents:[Ellipse(cx: 20, cy: 20, rx: 20, ry:20).arc(shift: 3.14159250259399, extent: 2.67794513702393).stroke(fill: Color.green)], place: Transform(dx:110, dy: 140) ) let group = Group(contents:[g1, g2, g3]) - let arcGroupReference = "" - XCTAssertEqual(SVGSerializer.serialize(node: group), arcGroupReference) + + validate(node: group, referenceFile: "arcsGroup") } func testSVGImage() { @@ -84,113 +98,51 @@ class MacawSVGTests: XCTestCase { } } - func testSVGTransform() { - let bundle = Bundle(for: type(of: TestUtils())) - let transformReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "transform") - XCTAssertEqual(SVGSerializer.serialize(node: node), transformReferenceContent) - } catch { - print(error) - } + func testClipWithParser() { + validate("clip", withReference: true) + } + + func testCSSStyleReference() { + validate("style", withReference: true) + } + + func testSVGTransformSkew() { + validate("transform", withReference: true) } func testSVGEllipse() { - let bundle = Bundle(for: type(of: TestUtils())) - let ellipseReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "ellipse") - XCTAssertEqual(SVGSerializer.serialize(node: node), ellipseReferenceContent) - } catch { - print(error) - } + validate("ellipse") } func testSVGCircle() { - let bundle = Bundle(for: type(of: TestUtils())) - let circleReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "circle") - XCTAssertEqual(SVGSerializer.serialize(node: node), circleReferenceContent) - } catch { - print(error) - } + validate("circle") } func testSVGGroup() { - let bundle = Bundle(for: type(of: TestUtils())) - let groupReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "group") - XCTAssertEqual(SVGSerializer.serialize(node: node), groupReferenceContent) - } catch { - print(error) - } + validate("group", withReference: true) } func testSVGLine() { - let bundle = Bundle(for: type(of: TestUtils())) - let lineReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "line") - XCTAssertEqual(SVGSerializer.serialize(node: node), lineReferenceContent) - } catch { - print(error) - } + validate("line") } func testSVGPolygon() { - let bundle = Bundle(for: type(of: TestUtils())) - let lineReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "polygon") - XCTAssertEqual(SVGSerializer.serialize(node: node), lineReferenceContent) - } catch { - print(error) - } + validate("polygon") } func testSVGPolyline() { - let bundle = Bundle(for: type(of: TestUtils())) - let lineReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "polyline") - XCTAssertEqual(SVGSerializer.serialize(node: node), lineReferenceContent) - } catch { - print(error) - } + validate("polyline") } func testSVGRect() { - let bundle = Bundle(for: type(of: TestUtils())) - let rectReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "rect") - XCTAssertEqual(SVGSerializer.serialize(node: node), rectReferenceContent) - } catch { - print(error) - } + validate("rect") } func testSVGRoundRect() { - let bundle = Bundle(for: type(of: TestUtils())) - let roundRectReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "roundRect") - XCTAssertEqual(SVGSerializer.serialize(node: node), roundRectReferenceContent) - } catch { - print(error) - } + validate("roundRect") } func testSVGTriangle() { - let bundle = Bundle(for: type(of: TestUtils())) - let triangleReferenceContent = "" - do { - let node = try SVGParser.parse(bundle:bundle, path: "triangle") - XCTAssertEqual(SVGSerializer.serialize(node: node), triangleReferenceContent) - } catch { - print(error) - } + validate("triangle") } } diff --git a/MacawTests/svg/arcsgroup.reference b/MacawTests/svg/arcsgroup.reference new file mode 100644 index 00000000..10739152 --- /dev/null +++ b/MacawTests/svg/arcsgroup.reference @@ -0,0 +1 @@ + diff --git a/MacawTests/svg/circle.svg b/MacawTests/svg/circle.svg index 38f0ad26..31fd79df 100644 --- a/MacawTests/svg/circle.svg +++ b/MacawTests/svg/circle.svg @@ -1,6 +1 @@ - - - - - - + diff --git a/MacawTests/svg/clip.reference b/MacawTests/svg/clip.reference new file mode 100644 index 00000000..3b7aafc3 --- /dev/null +++ b/MacawTests/svg/clip.reference @@ -0,0 +1 @@ + diff --git a/MacawTests/svg/clipManual.reference b/MacawTests/svg/clipManual.reference new file mode 100644 index 00000000..a44dba71 --- /dev/null +++ b/MacawTests/svg/clipManual.reference @@ -0,0 +1 @@ + diff --git a/MacawTests/svg/ellipse.svg b/MacawTests/svg/ellipse.svg index 317757c5..a23801fc 100644 --- a/MacawTests/svg/ellipse.svg +++ b/MacawTests/svg/ellipse.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + diff --git a/MacawTests/svg/group.reference b/MacawTests/svg/group.reference new file mode 100644 index 00000000..dcec50f2 --- /dev/null +++ b/MacawTests/svg/group.reference @@ -0,0 +1 @@ + diff --git a/MacawTests/svg/line.svg b/MacawTests/svg/line.svg index 4fdb1973..453d0cc0 100644 --- a/MacawTests/svg/line.svg +++ b/MacawTests/svg/line.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + diff --git a/MacawTests/svg/polygon.svg b/MacawTests/svg/polygon.svg index 6b0f4954..8f6297f6 100644 --- a/MacawTests/svg/polygon.svg +++ b/MacawTests/svg/polygon.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + diff --git a/MacawTests/svg/polyline.svg b/MacawTests/svg/polyline.svg index 78508537..10707279 100644 --- a/MacawTests/svg/polyline.svg +++ b/MacawTests/svg/polyline.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + diff --git a/MacawTests/svg/rect.svg b/MacawTests/svg/rect.svg index 4e496d10..7dca4928 100644 --- a/MacawTests/svg/rect.svg +++ b/MacawTests/svg/rect.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + diff --git a/MacawTests/svg/roundRect.svg b/MacawTests/svg/roundRect.svg index 97110c79..c9966ea9 100644 --- a/MacawTests/svg/roundRect.svg +++ b/MacawTests/svg/roundRect.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + diff --git a/MacawTests/svg/style.reference b/MacawTests/svg/style.reference new file mode 100644 index 00000000..a4cdb223 --- /dev/null +++ b/MacawTests/svg/style.reference @@ -0,0 +1 @@ + diff --git a/MacawTests/svg/textBasicTransform.reference b/MacawTests/svg/textBasicTransform.reference new file mode 100644 index 00000000..6176036a --- /dev/null +++ b/MacawTests/svg/textBasicTransform.reference @@ -0,0 +1 @@ +Point diff --git a/MacawTests/svg/transform.reference b/MacawTests/svg/transform.reference new file mode 100644 index 00000000..2c65145d --- /dev/null +++ b/MacawTests/svg/transform.reference @@ -0,0 +1 @@ + diff --git a/MacawTests/svg/triangle.svg b/MacawTests/svg/triangle.svg index 9c4c749b..c67ae209 100644 --- a/MacawTests/svg/triangle.svg +++ b/MacawTests/svg/triangle.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + diff --git a/Source/model/geom2d/GeomUtils.swift b/Source/model/geom2d/GeomUtils.swift index 39097c40..d7424865 100644 --- a/Source/model/geom2d/GeomUtils.swift +++ b/Source/model/geom2d/GeomUtils.swift @@ -1,78 +1,6 @@ import Foundation open class GeomUtils { - - fileprivate class func rectToPath(_ rect: Rect) -> Path { - return MoveTo(x: rect.x, y: rect.y).lineTo(x: rect.x, y: rect.y + rect.h).lineTo(x: rect.x + rect.w, y: rect.y + rect.h).lineTo(x: rect.x + rect.w, y: rect.y).close().build() - } - - fileprivate class func circleToPath(_ circle: Circle) -> Path { - return MoveTo(x: circle.cx, y: circle.cy).m(-circle.r, 0).a(circle.r, circle.r, 0.0, true, false, circle.r * 2.0, 0.0).a(circle.r, circle.r, 0.0, true, false, -(circle.r * 2.0), 0.0).build() - } - - fileprivate class func arcToPath(_ arc: Arc) -> Path { - let rx = arc.ellipse.rx - let ry = arc.ellipse.ry - let cx = arc.ellipse.cx - let cy = arc.ellipse.cy - - var delta = arc.extent - if arc.shift == 0.0 && abs(arc.extent - Double.pi * 2.0) < 0.00001 { - delta = Double.pi * 2.0 - 0.001 - } - let theta1 = arc.shift - - let theta2 = theta1 + delta - - let x1 = cx + rx * cos(theta1) - let y1 = cy + ry * sin(theta1) - - let x2 = cx + rx * cos(theta2) - let y2 = cy + ry * sin(theta2) - - let largeArcFlag = abs(delta) > .pi ? true : false - let sweepFlag = delta > 0.0 ? true : false - - return PathBuilder(segment: PathSegment(type: .M, data: [x1, y1])).A(rx, ry, 0.0, largeArcFlag, sweepFlag, x2, y2).build() - } - - fileprivate class func pointToPath(_ point: Point) -> Path { - return MoveTo(x: point.x, y: point.y).lineTo(x: point.x, y: point.y).build() - } - - fileprivate class func pointsToPath(_ points: [Double], close: Bool = false) -> Path { - var pb = PathBuilder(segment: PathSegment(type: .M, data: [points[0], points[1]])) - if points.count > 2 { - let parts = stride(from: 2, to: points.count, by: 2).map { Array(points[$0 ..< $0 + 2]) } - for part in parts { - pb = pb.lineTo(x: part[0], y: part[1]) - } - } - if close { - pb = pb.close() - } - return pb.build() - } - - open class func locusToPath(_ locus: Locus) -> Path { - if let rect = locus as? Rect { - return rectToPath(rect) - } else if let circle = locus as? Circle { - return circleToPath(circle) - } else if let arc = locus as? Arc { - return arcToPath(arc) - } else if let point = locus as? Point { - return MoveTo(x: point.x, y: point.y).lineTo(x: point.x, y: point.y).build() - } else if let line = locus as? Line { - return MoveTo(x: line.x1, y: line.y1).lineTo(x: line.x2, y: line.y2).build() - } else if let polygon = locus as? Polygon { - return pointsToPath(polygon.points, close: true) - } else if let polyline = locus as? Polyline { - return pointsToPath(polyline.points) - } - fatalError("Unsupported locus: \(locus)") - } - open class func concat(t1: Transform, t2: Transform) -> Transform { let nm11 = t2.m11 * t1.m11 + t2.m12 * t1.m21 let nm21 = t2.m21 * t1.m11 + t2.m22 * t1.m21 diff --git a/Source/model/geom2d/Locus+ToPath.swift b/Source/model/geom2d/Locus+ToPath.swift new file mode 100644 index 00000000..86b21aa0 --- /dev/null +++ b/Source/model/geom2d/Locus+ToPath.swift @@ -0,0 +1,75 @@ +import Foundation + +extension Locus { + + fileprivate func rectToPath(_ rect: Rect) -> Path { + return MoveTo(x: rect.x, y: rect.y).lineTo(x: rect.x, y: rect.y + rect.h).lineTo(x: rect.x + rect.w, y: rect.y + rect.h).lineTo(x: rect.x + rect.w, y: rect.y).close().build() + } + + fileprivate func circleToPath(_ circle: Circle) -> Path { + return MoveTo(x: circle.cx, y: circle.cy).m(-circle.r, 0).a(circle.r, circle.r, 0.0, true, false, circle.r * 2.0, 0.0).a(circle.r, circle.r, 0.0, true, false, -(circle.r * 2.0), 0.0).build() + } + + fileprivate func arcToPath(_ arc: Arc) -> Path { + let rx = arc.ellipse.rx + let ry = arc.ellipse.ry + let cx = arc.ellipse.cx + let cy = arc.ellipse.cy + + var delta = arc.extent + if (arc.shift == 0.0 && abs(arc.extent - .pi * 2.0) < 0.00001) { + delta = .pi * 2.0 - 0.001 + } + let theta1 = arc.shift + + let theta2 = theta1 + delta + + let x1 = cx + rx * cos(theta1) + let y1 = cy + ry * sin(theta1) + + let x2 = cx + rx * cos(theta2) + let y2 = cy + ry * sin(theta2) + + let largeArcFlag = abs(delta) > .pi ? true : false + let sweepFlag = delta > 0.0 ? true : false + + return PathBuilder(segment: PathSegment(type: .M, data: [x1, y1])).A(rx, ry, 0.0, largeArcFlag, sweepFlag, x2, y2).build() + } + + fileprivate func pointToPath(_ point: Point) -> Path { + return MoveTo(x: point.x, y: point.y).lineTo(x: point.x, y: point.y).build() + } + + fileprivate func pointsToPath(_ points: [Double], close: Bool = false) -> Path { + var pb = PathBuilder(segment: PathSegment(type: .M, data: [points[0], points[1]])) + if (points.count > 2) { + let parts = stride(from: 2, to: points.count, by: 2).map { Array(points[$0 ..< $0 + 2]) } + for part in parts { + pb = pb.lineTo(x: part[0], y: part[1]) + } + } + if close { + pb = pb.close() + } + return pb.build() + } + + public func toPath() -> Path { + if let rect = self as? Rect { + return rectToPath(rect) + } else if let circle = self as? Circle { + return circleToPath(circle) + } else if let arc = self as? Arc { + return arcToPath(arc) + } else if let point = self as? Point { + return MoveTo(x: point.x, y: point.y).lineTo(x: point.x, y: point.y).build() + } else if let line = self as? Line { + return MoveTo(x: line.x1, y: line.y1).lineTo(x: line.x2, y: line.y2).build() + } else if let polygon = self as? Polygon { + return pointsToPath(polygon.points, close: true) + } else if let polyline = self as? Polyline { + return pointsToPath(polyline.points) + } + fatalError("Unsupported locus: \(self)") + } +} diff --git a/Source/svg/SVGParser.swift b/Source/svg/SVGParser.swift index 870bffad..a9840be3 100644 --- a/Source/svg/SVGParser.swift +++ b/Source/svg/SVGParser.swift @@ -853,9 +853,9 @@ open class SVGParser { clip.children.forEach { indexer in if let shape = parseNode(indexer) as? Shape { if let p = path { - path = Path(segments: p.segments + GeomUtils.locusToPath(shape.form).segments) + path = Path(segments: p.segments + shape.form.toPath() .segments) } else { - path = GeomUtils.locusToPath(shape.form) + path = shape.form.toPath() } } } diff --git a/Source/svg/SVGSerializer.swift b/Source/svg/SVGSerializer.swift index d4dba956..9a7180e1 100644 --- a/Source/svg/SVGSerializer.swift +++ b/Source/svg/SVGSerializer.swift @@ -75,7 +75,7 @@ open class SVGSerializer { } fileprivate func arcToSVG(_ arc: Arc) -> String { - if arc.shift == 0.0 && abs(arc.extent - Double.pi * 2.0) < SVGEpsilon { + if arc.shift == 0.0 && abs(arc.extent - .pi * 2.0) < SVGEpsilon { return tag(SVGEllipseOpenTag, ["cx": att(arc.ellipse.cx), "cy": att(arc.ellipse.cy), "rx": att(arc.ellipse.rx), "ry": att(arc.ellipse.ry)]) } else { let rx = arc.ellipse.rx