1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-09-21 18:09:21 +03:00

Merge pull request #210 from exyte/feature/parse-svg-clip-path

Refactor locus to path implementation
This commit is contained in:
Yuriy Kashnikov 2017-10-23 20:54:56 +07:00 committed by GitHub
commit b4d271c8d0
21 changed files with 189 additions and 226 deletions

View File

@ -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 = "<group>"; };
C43B06651F99EE7300787A35 /* cubicRelative.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = cubicRelative.svg; path = Bounds/cubicRelative.svg; sourceTree = "<group>"; };
C43B06681F99FC2300787A35 /* pathbounds4.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = pathbounds4.svg; path = Bounds/pathbounds4.svg; sourceTree = "<group>"; };
C43B06501F9866E400787A35 /* Locus+ToPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locus+ToPath.swift"; sourceTree = "<group>"; };
C43B06521F989D9300787A35 /* transform.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = transform.reference; sourceTree = "<group>"; };
C43B06541F98A53600787A35 /* group.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = group.reference; sourceTree = "<group>"; };
C43B06561F98A7B700787A35 /* arcsgroup.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = arcsgroup.reference; sourceTree = "<group>"; };
C43B06581F98A84200787A35 /* style.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = style.reference; sourceTree = "<group>"; };
C43B065A1F98A9E000787A35 /* clipManual.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = clipManual.reference; sourceTree = "<group>"; };
C43B065E1F98AAA500787A35 /* clip.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = clip.reference; sourceTree = "<group>"; };
C43B06601F98ACFC00787A35 /* textBasicTransform.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = textBasicTransform.reference; sourceTree = "<group>"; };
C46E83541F94B20E00208037 /* transform.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = transform.svg; 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>"; };
@ -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 = "<group>";
@ -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 */,

View File

@ -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\" ><g>", with: "version=\"1.1\" >")
.replacingOccurrences(of: "defs><g>", with: "defs>")
.replacingOccurrences(of: "</g></svg>", with: "</svg>")
XCTAssertEqual(testContent, referenceContent)
}
} catch {
print(error)
XCTFail()
}
}
func testTextBasicTransform() {
let referenceContent = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><g transform=\"translate(100,100)\" ><text dominant-baseline=\"text-before-edge\" fill=\"black\" transform=\"matrix(0.707106781186548,-0.707106781186547,0.707106781186547,0.707106781186548,0.0,0.0)\" >Point</text></g></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><defs><clipPath id=\"clipPath1\"><path d=\"M 20 0m -20 0a 20 20 0 1 0 40 0a 20 20 0 1 0 -40 0M 0 10L 0 45L 100 45L 100 10z M 0 55L 0 90L 100 90L 100 55z \" /></clipPath></defs><g><circle r=\"50\" cy=\"50\" cx=\"50\" clip-path=\"url(#clipPath1)\" fill=\"black\"/></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><defs><clipPath id=\"clipPath1\"><rect height=\"90\" x=\"10\" y=\"10\" width=\"90\" /></clipPath><clipPath id=\"clipPath2\"><rect height=\"190\" x=\"110\" y=\"110\" width=\"190\" /></clipPath></defs><g><circle r=\"20\" cy=\"20\" cx=\"20\" clip-path=\"url(#clipPath1)\" fill=\"red\"/><circle r=\"20\" cy=\"120\" cx=\"120\" clip-path=\"url(#clipPath2)\" fill=\"green\"/></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><g><circle r=\"10\" cy=\"50\" cx=\"50\" fill=\"white\" stroke=\"#231F20\" stroke-width=\"1.5\"/><circle r=\"10\" cy=\"50\" cx=\"80\" fill=\"black\"/></g></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><g transform=\"translate(10,10)\" ><ellipse cy=\"20\" ry=\"20\" rx=\"20\" cx=\"20\" fill=\"none\" stroke=\"green\" stroke-width=\"1.0\"/></g><g transform=\"translate(10,140)\" ><path d=\"M20.0000015099579,39.9999999999999 A 20.0,20.0 0.0 0, 1 1.06581410364015e-14,20.0000006357301\" fill=\"none\" stroke=\"green\" stroke-width=\"1.0\"/></g><g transform=\"translate(110,140)\" ><path d=\"M2.27373675443232e-13,20.0000030199161 A 20.0,20.0 0.0 0, 1 37.888543296214,11.0557270424323\" fill=\"none\" stroke=\"green\" stroke-width=\"1.0\"/></g></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><g transform=\"matrix(2.0,1.0,1.0,1.0,0.0,0.0)\" ><rect height=\"5\" x=\"0\" y=\"0\" width=\"150\" fill=\"blue\"/><rect height=\"50\" x=\"0\" y=\"0\" width=\"5\" fill=\"red\"/><rect height=\"50\" x=\"150\" y=\"0\" width=\"5\" fill=\"black\"/><rect height=\"5\" x=\"0\" y=\"50\" width=\"150\" fill=\"black\"/><ellipse cy=\"25\" ry=\"15\" rx=\"40\" cx=\"75\" fill=\"purple\"/></g></g></svg>"
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 = "<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.0\"/></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><circle r=\"40\" cy=\"50\" cx=\"50\" fill=\"red\" stroke=\"black\" stroke-width=\"3.0\"/><circle r=\"40\" cy=\"50\" cx=\"140\" fill=\"#F0F0AA\" stroke=\"black\" stroke-width=\"3.0\"/></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><g><path d=\"M 150 0L 75 200L 225 200z \" fill=\"black\" stroke=\"black\" stroke-width=\"2.0\"/><line y1=\"0\" x2=\"200\" x1=\"0\" y2=\"200\" fill=\"black\" stroke=\"white\" stroke-width=\"2.0\"/></g></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><line y1=\"0\" x2=\"200\" x1=\"0\" y2=\"200\" fill=\"black\" stroke=\"red\" stroke-width=\"2.0\"/></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><polygon points=\"200.0,10.0,250.0,190.0,160.0,210.0\" fill=\"black\"/></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><polyline points=\"0.0,40.0,40.0,40.0,40.0,80.0,80.0,80.0,80.0,120.0,120.0,120.0,120.0,160.0\" fill=\"white\" stroke=\"red\" stroke-width=\"4.0\"/></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><rect height=\"150\" x=\"50\" y=\"0\" width=\"150\" fill=\"black\"/></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><rect height=\"150\" ry=\"20\" rx=\"20\" width=\"150\" fill=\"black\"/></g></svg>"
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" ><g><path d=\"M 150 0L 75 200L 225 200z \" fill=\"black\" stroke=\"black\" stroke-width=\"2.0\"/></g></svg>"
do {
let node = try SVGParser.parse(bundle:bundle, path: "triangle")
XCTAssertEqual(SVGSerializer.serialize(node: node), triangleReferenceContent)
} catch {
print(error)
}
validate("triangle")
}
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><g transform="translate(10,10)" ><ellipse cy="20" ry="20" rx="20" cx="20" fill="none" stroke="green" stroke-width="1.0"/></g><g transform="translate(10,140)" ><path d="M20.0000015099579,39.9999999999999 A 20.0,20.0 0.0 0, 1 1.06581410364015e-14,20.0000006357301" fill="none" stroke="green" stroke-width="1.0"/></g><g transform="translate(110,140)" ><path d="M2.27373675443232e-13,20.0000030199161 A 20.0,20.0 0.0 0, 1 37.888543296214,11.0557270424323" fill="none" stroke="green" stroke-width="1.0"/></g></g></svg>

After

Width:  |  Height:  |  Size: 622 B

View File

@ -1,6 +1 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="circle" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="210" width="400">
<circle cx="50" cy="50" r="40" stroke="#000000" stroke-width="3" fill="#f00" />
<circle cx="140" cy="50" r="40" stroke="#000000" stroke-width="3" fill="#f0f0aa" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><circle r="40" cy="50" cx="50" fill="red" stroke="black" stroke-width="3.0"/><circle r="40" cy="50" cx="140" fill="#F0F0AA" stroke="black" stroke-width="3.0"/></g></svg>

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 276 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><defs><clipPath id="clipPath1"><path d="M 20 0m -20 0a 20 20 0 1 0 40 0a 20 20 0 1 0 -40 0M 0 10L 0 45L 100 45L 100 10z M 0 55L 0 90L 100 90L 100 55z " /></clipPath></defs><circle r="50" cy="50" cx="50" clip-path="url(#clipPath1)" fill="black"/></svg>

After

Width:  |  Height:  |  Size: 355 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><defs><clipPath id="clipPath1"><rect height="90" x="10" y="10" width="90" /></clipPath><clipPath id="clipPath2"><rect height="190" x="110" y="110" width="190" /></clipPath></defs><g><circle r="20" cy="20" cx="20" clip-path="url(#clipPath1)" fill="red"/><circle r="20" cy="120" cx="120" clip-path="url(#clipPath2)" fill="green"/></g></svg>

After

Width:  |  Height:  |  Size: 446 B

View File

@ -1,5 +1 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="ellipse" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="210" width="400">
<ellipse cx="200" cy="80" rx="100" ry="50" style="fill:#ffff00;stroke:#800080;stroke-width:2" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><g><ellipse cy="80" ry="50" rx="100" cx="200" fill="yellow" stroke="purple" stroke-width="2.0"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 215 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><path d="M 150 0L 75 200L 225 200z " fill="black" stroke="black" stroke-width="2.0"/><line y1="0" x2="200" x1="0" y2="200" fill="black" stroke="white" stroke-width="2.0"/></g></svg>

After

Width:  |  Height:  |  Size: 288 B

View File

@ -1,5 +1 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="line" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="210" width="400">
<line x1="0" y1="0" x2="200" y2="200" style="stroke:#ff0000;stroke-width:2"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><line y1="0" x2="200" x1="0" y2="200" fill="black" stroke="red" stroke-width="2.0"/></g></svg>

Before

Width:  |  Height:  |  Size: 259 B

After

Width:  |  Height:  |  Size: 199 B

View File

@ -1,5 +1 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="polygon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="210" width="400">
<polygon points="200,10 250,190 160,210"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><polygon points="200.0,10.0,250.0,190.0,160.0,210.0" fill="black"/></g></svg>

Before

Width:  |  Height:  |  Size: 226 B

After

Width:  |  Height:  |  Size: 182 B

View File

@ -1,5 +1 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="polyline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="210" width="400">
<polyline points="0,40 40,40 40,80 80,80 80,120 120,120 120,160" style="fill:#ffffff;stroke:#ff0000;stroke-width:4" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><polyline points="0.0,40.0,40.0,40.0,40.0,80.0,80.0,80.0,80.0,120.0,120.0,120.0,120.0,160.0" fill="white" stroke="red" stroke-width="4.0"/></g></svg>

Before

Width:  |  Height:  |  Size: 305 B

After

Width:  |  Height:  |  Size: 254 B

View File

@ -1,5 +1 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="rountRect" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="210" width="400">
<rect x="50" width="150" height="150"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><rect height="150" x="50" y="0" width="150" fill="black"/></g></svg>

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 173 B

View File

@ -1,5 +1 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="rountRect" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="210" width="400">
<rect rx="20" ry="20" width="150" height="150"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><rect height="150" ry="20" rx="20" width="150" fill="black"/></g></svg>

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 176 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><circle r="10" cy="50" cx="50" fill="white" stroke="#231F20" stroke-width="1.5"/><circle r="10" cy="50" cx="80" fill="black"/></g></svg>

After

Width:  |  Height:  |  Size: 243 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><g transform="translate(100,100)" ><text dominant-baseline="text-before-edge" fill="black" transform="matrix(0.707106781186548,-0.707106781186547,0.707106781186547,0.707106781186548,0.0,0.0)" >Point</text></g></g></svg>

After

Width:  |  Height:  |  Size: 326 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g transform="matrix(2.0,1.0,1.0,1.0,0.0,0.0)" ><rect height="5" x="0" y="0" width="150" fill="blue"/><rect height="50" x="0" y="0" width="5" fill="red"/><rect height="50" x="150" y="0" width="5" fill="black"/><rect height="5" x="0" y="50" width="150" fill="black"/><ellipse cy="25" ry="15" rx="40" cx="75" fill="purple"/></g></svg>

After

Width:  |  Height:  |  Size: 442 B

View File

@ -1,5 +1 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="triangle" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="210" width="400">
<path d="M 150 0 L 75 200 L 225 200 Z" style="stroke:#000000;stroke-width:2"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><path d="M 150 0L 75 200L 225 200z " fill="black" stroke="black" stroke-width="2.0"/></g></svg>

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 200 B

View File

@ -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

View File

@ -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)")
}
}

View File

@ -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()
}
}
}

View File

@ -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