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
@ -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 */,
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
10
MacawTests/Bounds/cubicAbsolute.svg
Normal 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 |
10
MacawTests/Bounds/cubicRelative.svg
Normal 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 |
@ -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 |
@ -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 |
@ -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 |
@ -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
|
||||
|
@ -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:
|
||||
|