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

Fix #173: Implement elliptical Arc support in path SVG shape

This commit is contained in:
Yuri Strot 2017-12-28 17:55:45 +07:00
parent fb7663e159
commit 05c91d8250
3 changed files with 25 additions and 38 deletions

View File

@ -122,12 +122,12 @@ open class PathBuilder {
// GENERATED NOT
open func a(_ rx: Double, _ ry: Double, _ angle: Double, _ largeArc: Bool, _ sweep: Bool, _ x: Double, _ y: Double) -> PathBuilder {
return PathBuilder(segment: PathSegment(type: .a, data: [rx, ry, angle, boolsToNum(largeArc, sweep: sweep), x, y]), rest: self)
return PathBuilder(segment: PathSegment(type: .a, data: [rx, ry, angle, boolToNum(largeArc), boolToNum(sweep), x, y]), rest: self)
}
// GENERATED NOT
open func A(_ rx: Double, _ ry: Double, _ angle: Double, _ largeArc: Bool, _ sweep: Bool, _ x: Double, _ y: Double) -> PathBuilder {
return PathBuilder(segment: PathSegment(type: .A, data: [rx, ry, angle, boolsToNum(largeArc, sweep: sweep), x, y]), rest: self)
return PathBuilder(segment: PathSegment(type: .A, data: [rx, ry, angle, boolToNum(largeArc), boolToNum(sweep), x, y]), rest: self)
}
// GENERATED NOT
@ -155,9 +155,10 @@ open class PathBuilder {
}
return Path(segments: segments.reversed())
}
// GENERATED NOT
fileprivate func boolsToNum(_ largeArc: Bool, sweep: Bool) -> Double {
return (largeArc ? 1 : 0) + (sweep ? 1 : 0) * 2
fileprivate func boolToNum(_ value: Bool) -> Double {
return value ? 1 : 0
}
}

View File

@ -371,27 +371,18 @@ class RenderUtils {
let underroot = (rx * rx * ry * ry - rx * rx * y1_ * y1_ - ry * ry * x1_ * x1_)
/ (rx * rx * y1_ * y1_ + ry * ry * x1_ * x1_)
var bigRoot = (underroot > 0) ? sqrt(underroot) : 0
// TODO: Replace concrete number with 1e-2
bigRoot = (bigRoot <= 0.01) ? 0 : bigRoot
bigRoot = (bigRoot <= 1e-2) ? 0 : bigRoot
let coef: Double = (sweep != largeArc) ? 1 : -1
let cx_ = coef * bigRoot * rx * y1_ / ry
let cy_ = -1 * coef * bigRoot * ry * x1_ / rx
let cx = (cos(angle) * cx_ - sin(angle) * cy_ + (x1 + x) / 2)
let cy = (sin(angle) * cx_ + cos(angle) * cy_ + (y1 + y) / 2)
let t1 = -1 * atan2(y1 - cy, x1 - cx)
let t2 = atan2(y - cy, x - cx)
var delta = -(t1 + t2)
// recalculate delta depending on arc. Preserve rotation direction
if largeArc {
let sg = copysign(1.0, delta)
if abs(delta) < Double.pi {
delta = -1 * (sg * M_2_PI - delta)
}
} else {
let sg = copysign(1.0, delta)
if abs(delta) > Double.pi {
delta = -1 * (sg * M_2_PI - delta)
}
let cx = cos(angle) * cx_ - sin(angle) * cy_ + (x1 + x) / 2
let cy = sin(angle) * cx_ + cos(angle) * cy_ + (y1 + y) / 2
let t1 = calcAngle(ux: 1, uy: 0, vx: (x1_ - cx_) / rx, vy: (y1_ - cy_) / ry)
var delta = calcAngle(ux: (x1_ - cx_) / rx, uy: (y1_ - cy_) / ry, vx: (-x1_ - cx_) / rx, vy: (-y1_ - cy_) / ry).truncatingRemainder(dividingBy: .pi * 2)
if ((sweep && delta < 0) || (!sweep && delta > 0)) {
delta = -delta
}
E(cx - rx, y: cy - ry, w: 2 * rx, h: 2 * ry, startAngle: t1, arcAngle: delta)
setPoint(CGPoint(x: CGFloat(x), y: CGFloat(y)))
@ -508,11 +499,9 @@ class RenderUtils {
case .t:
t(data[0], y: data[1])
case .A:
let flags = numToBools(data[3])
A(data[0], ry: data[1], angle: data[2], largeArc: flags[0], sweep: flags[1], x: data[4], y: data[5])
A(data[0], ry: data[1], angle: data[2], largeArc: num2bool(data[3]), sweep: num2bool(data[4]), x: data[5], y: data[6])
case .a:
let flags = numToBools(data[3])
a(data[0], ry: data[1], angle: data[2], largeArc: flags[0], sweep: flags[1], x: data[4], y: data[5])
a(data[0], ry: data[1], angle: data[2], largeArc: num2bool(data[3]), sweep: num2bool(data[4]), x: data[5], y: data[6])
case .E:
E(data[0], y: data[1], w: data[2], h: data[3], startAngle: data[4], arcAngle: data[5])
case .e:
@ -523,10 +512,14 @@ class RenderUtils {
}
return bezierPath
}
class func calcAngle(ux: Double, uy: Double, vx: Double, vy: Double) -> Double {
let sign = copysign(1, ux * vy - uy * vx)
return sign * acos((ux * vx + uy * vy) / (sqrt(ux * ux + uy * uy) * sqrt(vx * vx + vy * vy)))
}
class func numToBools(_ num: Double) -> [Bool] {
let val: Int = Int(num)
return [(val & 1) > 0, (val & 2) > 0]
class func num2bool(_ double: Double) -> Bool {
return double > 0.5 ? true : false
}
fileprivate class func newCGRect(_ rect: Rect) -> CGRect {

View File

@ -115,14 +115,7 @@ open class SVGSerializer {
fileprivate func pathToSVG(_ path: Path) -> String {
var d = ""
for segment in path.segments {
if segment.type == .A || segment.type == .a {
let flags = RenderUtils.numToBools(segment.data[3])
let large: Double = flags[0] ? 1 : 0
let sweep: Double = flags[1] ? 1 : 0
d += "\(segment.type) \([segment.data[0], segment.data[1], segment.data[2], large, sweep, segment.data[4], segment.data[5]].flatMap { String(Int($0)) }.joined(separator: " "))"
} else {
d += "\(segment.type) \(segment.data.flatMap { String(Int($0)) }.joined(separator: " "))"
}
d += "\(segment.type) \(segment.data.flatMap { String(Int($0)) }.joined(separator: " "))"
}
return tag(SVGPathOpenTag, ["d": d])
}