1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-11-09 20:03:05 +03:00

Fix path bounds for cubic curves

- Add 1 and 0 to solutions along to extrema they should be
  considered as point for bounding box
- Refactor test harness
- Add separate tests for absolute and relative cubic bounds
This commit is contained in:
Yuriy Kashnikov 2017-10-20 16:33:19 +07:00
parent 45979a16ff
commit b5bc3ef5e5
9 changed files with 68 additions and 47 deletions

View File

@ -267,6 +267,8 @@
C4153A8F1F8793DE001BA5EE /* small-logo.png in Resources */ = {isa = PBXBuildFile; fileRef = C4153A8E1F8793DD001BA5EE /* small-logo.png */; };
C43B064D1F9738EF00787A35 /* clip.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B064C1F9738EF00787A35 /* clip.svg */; };
C43B06631F99A33400787A35 /* pathbounds3.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B06621F99A33400787A35 /* pathbounds3.svg */; };
C43B06661F99EE7300787A35 /* cubicAbsolute.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B06641F99EE7200787A35 /* cubicAbsolute.svg */; };
C43B06671F99EE7300787A35 /* cubicRelative.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B06651F99EE7300787A35 /* cubicRelative.svg */; };
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 */; };
@ -453,6 +455,8 @@
C4153A8E1F8793DD001BA5EE /* small-logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "small-logo.png"; sourceTree = "<group>"; };
C43B064C1F9738EF00787A35 /* clip.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = clip.svg; sourceTree = "<group>"; };
C43B06621F99A33400787A35 /* pathbounds3.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = pathbounds3.svg; path = Bounds/pathbounds3.svg; sourceTree = "<group>"; };
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>"; };
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>"; };
@ -873,6 +877,8 @@
A7E675541EC4211E00BD9ECB /* Bounds */ = {
isa = PBXGroup;
children = (
C43B06641F99EE7200787A35 /* cubicAbsolute.svg */,
C43B06651F99EE7300787A35 /* cubicRelative.svg */,
C43B06621F99A33400787A35 /* pathbounds3.svg */,
C4BD40B91F8F58B0003034F0 /* pathbounds1.svg */,
C4BD40BA1F8F58B0003034F0 /* pathbounds2.svg */,
@ -1028,10 +1034,12 @@
C43B06631F99A33400787A35 /* pathbounds3.svg in Resources */,
C43B064D1F9738EF00787A35 /* clip.svg in Resources */,
57B7A4E11EE70DA5009D78D7 /* logo_base64.txt in Resources */,
C43B06671F99EE7300787A35 /* cubicRelative.svg in Resources */,
C410148E1F834D290022EE44 /* style.svg in Resources */,
C4BD40BB1F8F58B0003034F0 /* pathbounds1.svg in Resources */,
C4BD40BC1F8F58B0003034F0 /* pathbounds2.svg in Resources */,
57CAB1301D7832E000FD8E47 /* group.svg in Resources */,
C43B06661F99EE7300787A35 /* cubicAbsolute.svg in Resources */,
C46E83551F94B20E00208037 /* transform.svg in Resources */,
57CAB1351D7832E000FD8E47 /* roundRect.svg in Resources */,
57CAB12E1D7832E000FD8E47 /* circle.svg in Resources */,

View File

@ -13,57 +13,44 @@ class SVGBoundsTest: XCTestCase {
super.tearDown()
}
func testPathBounds1() {
func validate(name: String, referenceBounds: Rect) {
let passingThreshold = 0.2
let bundle = Bundle(for: type(of: TestUtils()))
do {
let node = try SVGParser.parse(bundle:bundle, path: "pathbounds1")
var testResult = false
if let bounds = node.bounds() {
testResult = (Double(round(100*bounds.x)/100) == 101.4)
testResult = testResult && (Double(round(100*bounds.y)/100) == 36.7)
testResult = testResult && (Double(round(100*bounds.w)/100) == 7.6)
testResult = testResult && (Double(round(100*bounds.h)/100) == 35.0)
}
XCTAssert(testResult)
} catch {
print(error)
}
}
func testPathBounds2() {
let bundle = Bundle(for: type(of: TestUtils()))
do {
let node = try SVGParser.parse(bundle:bundle, path: "pathbounds2")
let node = try SVGParser.parse(bundle:bundle, path: name)
var testResult = false
if let bounds = node.bounds() {
// print("\n<rect x=\"\(Double(round(100*bounds.x)/100))\" y=\"\(Double(round(100*bounds.y)/100))\" width=\"\(Double(round(100*bounds.w)/100))\" height=\"\(Double(round(100*bounds.h)/100))\" stroke=\"red\" stroke-width=\"1\" fill=\"none\"/>\n")
testResult = Double(round(100*bounds.x)/100) == 36.7
testResult = testResult && Double(round(100*bounds.y)/100) == 101.4
testResult = testResult && Double(round(100*bounds.w)/100) == 35.17
testResult = testResult && Double(round(100*bounds.h)/100) == 7.6
testResult = (Double(round(100*bounds.x)/100) - referenceBounds.x < passingThreshold)
testResult = testResult && (Double(round(100*bounds.y)/100) - referenceBounds.y < passingThreshold)
testResult = testResult && (Double(round(100*bounds.w)/100) - referenceBounds.w < passingThreshold)
testResult = testResult && (Double(round(100*bounds.h)/100) - referenceBounds.h < passingThreshold)
}
XCTAssert(testResult)
} catch {
print(error)
XCTFail()
}
}
func testPathBoundsCubic() {
let bundle = Bundle(for: type(of: TestUtils()))
do {
let node = try SVGParser.parse(bundle:bundle, path: "pathbounds3")
var testResult = false
if let bounds = node.bounds() {
testResult = Double(round(100*bounds.x)/100) == 0.0
testResult = testResult && Double(round(100*bounds.y)/100) == 0.0
testResult = testResult && Double(round(100*bounds.w)/100) == 50.0
testResult = testResult && Double(round(100*bounds.h)/100) == 50.0
}
XCTAssert(testResult)
} catch {
print(error)
}
func testPathBounds1() {
validate(name: "pathbounds1", referenceBounds: Rect(x: 101.4, y: 36.7, w: 7.6, h: 35.0))
}
func testPathBounds2() {
validate(name: "pathbounds2", referenceBounds: Rect(x: 36.7, y: 101.4, w: 35, h: 7.6))
}
func testPathBounds3() {
validate(name: "pathbounds3", referenceBounds: Rect(x: 0, y: 0, w: 50, h: 50))
}
func testPathBoundsCubicAbsolute() {
validate(name: "cubicAbsolute", referenceBounds: Rect(x: 33.66, y: 9.5, w: 16.84, h: 41))
}
func testPathBoundsCubicRelative() {
validate(name: "cubicRelative", referenceBounds: Rect(x: 49.5, y: 49.5, w: 51, h: 17.57))
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- correct bounds
<rect x="33.66" y="9.5" width="16.84" height="41.0" stroke="red" stroke-width="1" fill="none"/>
-->
<path d="M50 50 C 20 20, 40 20, 50 10" stroke="black" fill="transparent"/>
</svg>

After

Width:  |  Height:  |  Size: 367 B

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="200" height="200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- correct bounds
<rect x="49.5" y="49.5" width="51.0" height="17.57" stroke="red" stroke-width="1" fill="none"/>
-->
<path d="M50 50 c 20 20, 40 20, 50 10" stroke="black" fill="transparent"/>
</svg>

After

Width:  |  Height:  |  Size: 367 B

View File

@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="200" height="200">
<!-- correct bounds
<rect x="101.4" y="36.7" width="7.6" height="35.0" fill="none" stroke="red" />
-->
<path d="M101.9,40.5c0-1.8,1.5-3.3,3.3-3.3s3.3,1.5,3.3,3.3v27.4c0,1.8-1.5,3.3-3.3,3.3s-3.3-1.5-3.3-3.3V40.5z" fill="none" stroke="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 424 B

After

Width:  |  Height:  |  Size: 427 B

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0"
width="200" height="200">
<!--
<!-- correct bounds
<rect x="36.7" y="101.4" width="35.1" height="7.6" fill="none" stroke="red" />
-->
<path d="M68,101.9c1.8,0,3.3,1.5,3.3,3.3s-1.5,3.3-3.3,3.3H40.5c-1.8,0-3.3-1.5-3.3-3.3s1.5-3.3,3.3-3.3H68z" fill="none" stroke="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 408 B

After

Width:  |  Height:  |  Size: 425 B

View File

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- correct bounds
<rect x="0.0" y="0.0" width="50.0" height="50.0" stroke="red" stroke-width="1" fill="none"/>
-->
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-720.000000, -30.000000)">
<g transform="translate(720.000000, 30.000000)">
@ -7,7 +12,5 @@
</g>
</g>
</g>
<!--
<rect x="0.0" y="0.0" width="50.0" height="50.0" stroke="red" stroke-width="1" fill="none"/>
-->
</svg>

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 739 B

View File

@ -40,7 +40,7 @@ func boundsWithDerivative(p0: Point, p1: Point, p2: Point, p3: Point) -> Rect? {
let cy = 3 * p1.y - 3 * p0.y
let sy = solveEquation(a: ay, b: by, c: cy)
let solutions = [sx.s1, sx.s2, sy.s1, sy.s2].flatMap { $0 }
let solutions = [0, 1, sx.s1, sx.s2, sy.s1, sy.s2].flatMap { $0 }
var minX: Double? = .none
var minY: Double? = .none
var maxX: Double? = .none

View File

@ -48,11 +48,9 @@ func pathSegmenInfo(_ segment: PathSegment, currentPoint: Point?, currentBezierP
let point = Point(x: data[0], y: data[1])
return (Rect(x: point.x, y: point.y, w: 0.0, h: 0.0), point, .none)
case .c, .C:
var p: Point
if let c = currentPoint {
var p: Point = Point(x: 0, y: 0)
if let c = currentPoint, segment.isAbsolute() {
p = c
} else {
p = Point(x: 0, y: 0)
}
return (cubicBounds(data, currentPoint: p), Point(x: data[4], y: data[5]), Point(x: data[2], y: data[3]))
case .s, .S: