mirror of
https://github.com/exyte/Macaw.git
synced 2024-08-16 00:20:23 +03:00
Remove lots of force unwrapping and instead guard for nil values at top of function
This commit is contained in:
parent
2b406e55f2
commit
33e8f4b467
7
Dependencies/SWXMLHash/SWXMLHash.swift
vendored
7
Dependencies/SWXMLHash/SWXMLHash.swift
vendored
@ -269,6 +269,11 @@ class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate {
|
||||
}
|
||||
|
||||
func startParsing(_ ops: [IndexOp]) {
|
||||
guard let data = data
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
// clear any prior runs of parse... expected that this won't be necessary,
|
||||
// but you never know
|
||||
parentStack.removeAll()
|
||||
@ -276,7 +281,7 @@ class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate {
|
||||
parentStack.push(root)
|
||||
|
||||
self.ops = ops
|
||||
let parser = Foundation.XMLParser(data: data!)
|
||||
let parser = Foundation.XMLParser(data: data)
|
||||
parser.shouldProcessNamespaces = options.shouldProcessNamespaces
|
||||
parser.delegate = self
|
||||
_ = parser.parse()
|
||||
|
@ -86,8 +86,8 @@ class ControlStatesTests: XCTestCase {
|
||||
|
||||
func testCombineAnimation() {
|
||||
let animation = [
|
||||
testNode.placeVar.animation(to: .identity),
|
||||
testNode.opacityVar.animation(to: 0.0)
|
||||
testNode.placeVar.animation(to: .identity)!,
|
||||
testNode.opacityVar.animation(to: 0.0)!
|
||||
].combine() as! CombineAnimation
|
||||
|
||||
animation.play()
|
||||
|
@ -212,8 +212,12 @@ class MacawSVGTests: XCTestCase {
|
||||
let nodeContent = String(data: getJSONData(node: node), encoding: String.Encoding.utf8)
|
||||
compareResults(nodeContent: nodeContent, referenceContent: referenceContent)
|
||||
|
||||
let nativeImage = getImage(from: referenceFile)
|
||||
|
||||
guard let nativeImage = getImage(from: referenceFile)
|
||||
else {
|
||||
XCTFail("Failed to create Image from file \(referenceFile)")
|
||||
return
|
||||
}
|
||||
|
||||
//To save new PNG image for test, uncomment this
|
||||
//saveImage(image: nativeImage, fileName: referenceFile)
|
||||
#if os(OSX)
|
||||
@ -826,7 +830,7 @@ class MacawSVGTests: XCTestCase {
|
||||
validateJSON("masking-mask-02-f-manual")
|
||||
}
|
||||
|
||||
func getImage(from svgName: String) -> MImage {
|
||||
func getImage(from svgName: String) -> MImage? {
|
||||
let bundle = Bundle(for: type(of: TestUtils()))
|
||||
do {
|
||||
let node = try SVGParser.parse(resource: svgName, fromBundle: bundle)
|
||||
|
@ -254,9 +254,9 @@ extension SVGLength {
|
||||
return
|
||||
}
|
||||
if string.hasSuffix("%") {
|
||||
self = SVGLength.percent(Double(string.dropLast())!)
|
||||
self = SVGLength.percent(Double(string.dropLast()) ?? 0.0)
|
||||
} else {
|
||||
self = SVGLength.pixels(Double(string)!)
|
||||
self = SVGLength.pixels(Double(string) ?? 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -479,9 +479,9 @@ extension Stroke: Serializable {
|
||||
width: parse(dictionary["width"]),
|
||||
cap: cap,
|
||||
join: join,
|
||||
miterLimit: miterLimit != nil ? miterLimit! : 4,
|
||||
miterLimit: miterLimit ?? 4,
|
||||
dashes: dashes,
|
||||
offset: offset != nil ? offset! : 0)
|
||||
offset: offset ?? 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ public class Animation {
|
||||
return self
|
||||
}
|
||||
|
||||
public func reverse() -> Animation {
|
||||
public func reverse() -> Animation? {
|
||||
return self
|
||||
}
|
||||
|
||||
|
@ -135,7 +135,7 @@ class BasicAnimation: Animation {
|
||||
return .running
|
||||
}
|
||||
|
||||
override open func reverse() -> Animation {
|
||||
override open func reverse() -> Animation? {
|
||||
return self
|
||||
}
|
||||
|
||||
|
@ -230,9 +230,9 @@ class AnimationProducer {
|
||||
return
|
||||
}
|
||||
|
||||
if animation.autoreverses {
|
||||
animation.autoreverses = false
|
||||
play([animation, animation.reverse()].sequence() as! BasicAnimation, context)
|
||||
if contentsAnimation.autoreverses {
|
||||
contentsAnimation.autoreverses = false
|
||||
play([contentsAnimation, contentsAnimation.reverse()].sequence() as! BasicAnimation, context)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -40,33 +40,51 @@ internal class ContentsAnimation: AnimationImpl<[Node]> {
|
||||
|
||||
public extension AnimatableVariable where T: ContentsInterpolation {
|
||||
|
||||
func animation(_ f: @escaping (Double) -> [Node]) -> Animation {
|
||||
let group = node! as! Group
|
||||
func animation(_ f: @escaping (Double) -> [Node]) -> Animation? {
|
||||
guard let group = node as? Group
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
return ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: 1.0, delay: 0.0, autostart: false)
|
||||
}
|
||||
|
||||
func animation(_ f: @escaping ((Double) -> [Node]), during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
let group = node! as! Group
|
||||
func animation(_ f: @escaping ((Double) -> [Node]), during: Double = 1.0, delay: Double = 0.0) -> Animation? {
|
||||
guard let group = node as? Group
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
return ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: false)
|
||||
}
|
||||
|
||||
func animation(during: Double = 1.0, delay: Double = 0.0, _ f: @escaping ((Double) -> [Node])) -> Animation {
|
||||
let group = node! as! Group
|
||||
func animation(during: Double = 1.0, delay: Double = 0.0, _ f: @escaping ((Double) -> [Node])) -> Animation? {
|
||||
guard let group = node as? Group
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
return ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: false)
|
||||
}
|
||||
|
||||
func animate(_ f: @escaping (Double) -> [Node]) {
|
||||
let group = node! as! Group
|
||||
guard let group = node as? Group
|
||||
else {
|
||||
return
|
||||
}
|
||||
_ = ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: 1.0, delay: 0.0, autostart: true)
|
||||
}
|
||||
|
||||
func animate(_ f: @escaping ((Double) -> [Node]), during: Double = 1.0, delay: Double = 0.0) {
|
||||
let group = node! as! Group
|
||||
guard let group = node as? Group
|
||||
else {
|
||||
return
|
||||
}
|
||||
_ = ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: true)
|
||||
}
|
||||
|
||||
func animate(during: Double = 1.0, delay: Double = 0.0, _ f: @escaping ((Double) -> [Node])) {
|
||||
let group = node! as! Group
|
||||
guard let group = node as? Group
|
||||
else {
|
||||
return
|
||||
}
|
||||
_ = ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: true)
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,11 @@ internal class OpacityAnimation: AnimationImpl<Double> {
|
||||
}
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
open override func reverse() -> Animation? {
|
||||
guard let node = node
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
let factory = { () -> (Double) -> Double in
|
||||
let original = self.timeFactory()
|
||||
return { (t: Double) -> Double in
|
||||
@ -37,7 +41,7 @@ internal class OpacityAnimation: AnimationImpl<Double> {
|
||||
}
|
||||
}
|
||||
|
||||
let reversedAnimation = OpacityAnimation(animatedNode: node!,
|
||||
let reversedAnimation = OpacityAnimation(animatedNode: node,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
@ -50,29 +54,49 @@ public typealias OpacityAnimationDescription = AnimationDescription<Double>
|
||||
|
||||
public extension AnimatableVariable where T: DoubleInterpolation {
|
||||
func animate(_ desc: OpacityAnimationDescription) {
|
||||
_ = OpacityAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: true)
|
||||
guard let node = node
|
||||
else {
|
||||
return
|
||||
}
|
||||
_ = OpacityAnimation(animatedNode: node, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: true)
|
||||
}
|
||||
|
||||
func animation(_ desc: OpacityAnimationDescription) -> Animation {
|
||||
return OpacityAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: false)
|
||||
func animation(_ desc: OpacityAnimationDescription) -> Animation? {
|
||||
guard let node = node
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
return OpacityAnimation(animatedNode: node, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: false)
|
||||
}
|
||||
|
||||
func animate(from: Double? = nil, to: Double, during: Double = 1.0, delay: Double = 0.0) {
|
||||
self.animate(((from ?? node!.opacity) >> to).t(during, delay: delay))
|
||||
guard let node = node
|
||||
else {
|
||||
return
|
||||
}
|
||||
self.animate(((from ?? node.opacity) >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
func animation(from: Double? = nil, to: Double, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
func animation(from: Double? = nil, to: Double, during: Double = 1.0, delay: Double = 0.0) -> Animation? {
|
||||
guard let node = node
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
if let safeFrom = from {
|
||||
return self.animation((safeFrom >> to).t(during, delay: delay))
|
||||
}
|
||||
let origin = node!.opacity
|
||||
let origin = node.opacity
|
||||
let factory = { () -> (Double) -> Double in { (t: Double) in origin.interpolate(to, progress: t) }
|
||||
}
|
||||
return OpacityAnimation(animatedNode: self.node!, factory: factory, animationDuration: during, delay: delay)
|
||||
return OpacityAnimation(animatedNode: node, factory: factory, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
func animation(_ f: @escaping ((Double) -> Double), during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
return OpacityAnimation(animatedNode: node!, valueFunc: f, animationDuration: during, delay: delay)
|
||||
func animation(_ f: @escaping ((Double) -> Double), during: Double = 1.0, delay: Double = 0.0) -> Animation? {
|
||||
guard let node = node
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
return OpacityAnimation(animatedNode: node, valueFunc: f, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -92,8 +92,7 @@ open class StrokeSideVariable {
|
||||
return self.animation((safeFrom >> to).t(during, delay: delay))
|
||||
}
|
||||
let origin = Double(0)
|
||||
let factory = { () -> (Double) -> Double in
|
||||
{ (t: Double) in origin.interpolate(to, progress: t) }
|
||||
let factory = { () -> (Double) -> Double in { (t: Double) in origin.interpolate(to, progress: t) }
|
||||
}
|
||||
return PathAnimation(animatedNode: node as! Shape, isEnd: isEnd, factory: factory, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ public extension AnimatableVariable where T == Fill? {
|
||||
finalShape.fill = to
|
||||
_ = ShapeAnimation(animatedNode: shape, finalValue: finalShape, animationDuration: during, delay: delay, autostart: true)
|
||||
}
|
||||
|
||||
|
||||
func animation(from: Fill? = nil, to: Fill, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
let shape = node as! Shape
|
||||
shape.fill = from ?? (shape.fill ?? Color.clear)
|
||||
|
@ -44,8 +44,11 @@ internal class TransformAnimation: AnimationImpl<Transform> {
|
||||
}
|
||||
}
|
||||
|
||||
open override func reverse() -> Animation {
|
||||
|
||||
open override func reverse() -> Animation? {
|
||||
guard let node = node
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
let factory = { () -> (Double) -> Transform in
|
||||
let original = self.timeFactory()
|
||||
return { (t: Double) -> Transform in
|
||||
@ -53,7 +56,7 @@ internal class TransformAnimation: AnimationImpl<Transform> {
|
||||
}
|
||||
}
|
||||
|
||||
let reversedAnimation = TransformAnimation(animatedNode: node!,
|
||||
let reversedAnimation = TransformAnimation(animatedNode: node,
|
||||
factory: factory, animationDuration: duration, fps: logicalFps)
|
||||
reversedAnimation.progress = progress
|
||||
reversedAnimation.completion = completion
|
||||
@ -66,45 +69,75 @@ public typealias TransformAnimationDescription = AnimationDescription<Transform>
|
||||
|
||||
public extension AnimatableVariable where T: TransformInterpolation {
|
||||
func animate(_ desc: TransformAnimationDescription) {
|
||||
_ = TransformAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: true)
|
||||
guard let node = node
|
||||
else {
|
||||
return
|
||||
}
|
||||
_ = TransformAnimation(animatedNode: node, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: true)
|
||||
}
|
||||
|
||||
func animation(_ desc: TransformAnimationDescription) -> Animation {
|
||||
return TransformAnimation(animatedNode: node!, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: false)
|
||||
func animation(_ desc: TransformAnimationDescription) -> Animation? {
|
||||
guard let node = node
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
return TransformAnimation(animatedNode: node, valueFunc: desc.valueFunc, animationDuration: desc.duration, delay: desc.delay, autostart: false)
|
||||
}
|
||||
|
||||
func animate(from: Transform? = nil, to: Transform, during: Double = 1.0, delay: Double = 0.0) {
|
||||
self.animate(((from ?? node!.place) >> to).t(during, delay: delay))
|
||||
guard let node = node
|
||||
else {
|
||||
return
|
||||
}
|
||||
self.animate(((from ?? node.place) >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
func animate(angle: Double, x: Double? = .none, y: Double? = .none, during: Double = 1.0, delay: Double = 0.0) {
|
||||
let animation = self.animation(angle: angle, x: x, y: y, during: during, delay: delay)
|
||||
guard let animation = self.animation(angle: angle, x: x, y: y, during: during, delay: delay)
|
||||
else {
|
||||
return
|
||||
}
|
||||
animation.play()
|
||||
}
|
||||
|
||||
func animate(along path: Path, during: Double = 1.0, delay: Double = 0.0) {
|
||||
let animation = self.animation(along: path, during: during, delay: delay)
|
||||
guard let animation = self.animation(along: path, during: during, delay: delay)
|
||||
else {
|
||||
return
|
||||
}
|
||||
animation.play()
|
||||
}
|
||||
|
||||
func animation(from: Transform? = nil, to: Transform, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
func animation(from: Transform? = nil, to: Transform, during: Double = 1.0, delay: Double = 0.0) -> Animation? {
|
||||
guard let node = node
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if let safeFrom = from {
|
||||
return self.animation((safeFrom >> to).t(during, delay: delay))
|
||||
}
|
||||
|
||||
let origin = node!.place
|
||||
let origin = node.place
|
||||
let factory = { () -> (Double) -> Transform in { (t: Double) in origin.interpolate(to, progress: t) }
|
||||
}
|
||||
return TransformAnimation(animatedNode: self.node!, factory: factory, animationDuration: during, delay: delay)
|
||||
return TransformAnimation(animatedNode: node, factory: factory, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
func animation(_ f: @escaping ((Double) -> Transform), during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
return TransformAnimation(animatedNode: node!, valueFunc: f, animationDuration: during, delay: delay)
|
||||
func animation(_ f: @escaping ((Double) -> Transform), during: Double = 1.0, delay: Double = 0.0) -> Animation? {
|
||||
guard let node = node
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
return TransformAnimation(animatedNode: node, valueFunc: f, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
func animation(angle: Double, x: Double? = .none, y: Double? = .none, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
let bounds = node!.bounds!
|
||||
let place = node!.place
|
||||
func animation(angle: Double, x: Double? = .none, y: Double? = .none, during: Double = 1.0, delay: Double = 0.0) -> Animation? {
|
||||
guard let node = node,
|
||||
let bounds = node.bounds
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
|
||||
let factory = { () -> (Double) -> Transform in { t in
|
||||
let asin = sin(angle * t); let acos = cos(angle * t)
|
||||
@ -119,18 +152,27 @@ public extension AnimatableVariable where T: TransformInterpolation {
|
||||
dy: y ?? bounds.y + bounds.h / 2.0
|
||||
)
|
||||
|
||||
return place.concat(with: move).concat(with: rotation).concat(with: move.invert()!)
|
||||
if let invert = move.invert() {
|
||||
return node.place.concat(with: move).concat(with: rotation).concat(with: invert)
|
||||
} else {
|
||||
return node.place
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return TransformAnimation(animatedNode: node!, factory: factory, animationDuration: during, delay: delay)
|
||||
return TransformAnimation(animatedNode: node, factory: factory, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
func animation(along path: Path, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||
func animation(along path: Path, during: Double = 1.0, delay: Double = 0.0) -> Animation? {
|
||||
guard let node = node
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
|
||||
let factory = { () -> (Double) -> Transform in { (t: Double) in self.node?.place ?? .identity }
|
||||
}
|
||||
return TransformAnimation(animatedNode: self.node!, factory: factory, along: path, animationDuration: during, delay: delay)
|
||||
return TransformAnimation(animatedNode: node, factory: factory, along: path, animationDuration: during, delay: delay)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -44,7 +44,9 @@ extension AnimationProducer {
|
||||
for i in minPathsNumber..<fromShapes.count {
|
||||
let shapeToHide = fromShapes[i]
|
||||
let animation = shapeToHide.opacityVar.animation(to: 0.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
if let animation = animation {
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +57,9 @@ extension AnimationProducer {
|
||||
fromNode.contents.append(shapeToShow)
|
||||
|
||||
let animation = shapeToShow.opacityVar.animation(to: 1.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
if let animation = animation {
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +79,9 @@ extension AnimationProducer {
|
||||
for i in minGroupsNumber..<fromGroups.count {
|
||||
let groupToHide = fromGroups[i]
|
||||
let animation = groupToHide.opacityVar.animation(to: 0.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
if let animation = animation {
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
for i in minGroupsNumber..<toGroups.count {
|
||||
@ -84,7 +90,9 @@ extension AnimationProducer {
|
||||
fromNode.contents.append(groupToShow)
|
||||
|
||||
let animation = groupToShow.opacityVar.animation(to: 1.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
if let animation = animation {
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
// Rest nodes
|
||||
@ -98,7 +106,9 @@ extension AnimationProducer {
|
||||
|
||||
fromNodes.forEach { node in
|
||||
let animation = node.opacityVar.animation(to: 0.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
if let animation = animation {
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
toNodes.forEach { node in
|
||||
@ -106,7 +116,9 @@ extension AnimationProducer {
|
||||
fromNode.contents.append(node)
|
||||
|
||||
let animation = node.opacityVar.animation(to: 1.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
if let animation = animation {
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
return animations
|
||||
|
@ -18,7 +18,10 @@ public extension MacawView {
|
||||
}
|
||||
|
||||
var frame = CGRect(origin: CGPoint.zero, size: size)
|
||||
let ctx = CGContext(path as CFURL, mediaBox: &frame, .none)!
|
||||
guard let ctx = CGContext(path as CFURL, mediaBox: &frame, .none)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.beginPDFPage(.none)
|
||||
ctx.translateBy(x: 0.0, y: size.height)
|
||||
|
@ -15,6 +15,10 @@ open class GeomUtils {
|
||||
|
||||
open class func anchorRotation(node: Node, place: Transform, anchor: Point, angle: Double) -> Transform {
|
||||
let move = Transform.move(dx: anchor.x, dy: anchor.y)
|
||||
guard let invert = move.invert()
|
||||
else {
|
||||
return Transform.identity
|
||||
}
|
||||
|
||||
let asin = sin(angle); let acos = cos(angle)
|
||||
|
||||
@ -25,7 +29,7 @@ open class GeomUtils {
|
||||
)
|
||||
|
||||
let t1 = move.concat(with: rotation)
|
||||
let t2 = t1.concat(with: move.invert()!)
|
||||
let t2 = t1.concat(with: invert)
|
||||
let result = place.concat(with: t2)
|
||||
|
||||
return result
|
||||
|
@ -15,7 +15,7 @@ func MGraphicsGetCurrentContext() -> CGContext? {
|
||||
return UIGraphicsGetCurrentContext()
|
||||
}
|
||||
|
||||
func MGraphicsGetImageFromCurrentImageContext() -> MImage! {
|
||||
func MGraphicsGetImageFromCurrentImageContext() -> MImage? {
|
||||
return UIGraphicsGetImageFromCurrentImageContext()
|
||||
}
|
||||
|
||||
|
@ -160,7 +160,11 @@ extension MBezierPath {
|
||||
self.appendArc(withCenter: withCenter, radius: radius, startAngle: startAngleRadian, endAngle: endAngleRadian, clockwise: !clockwise)
|
||||
}
|
||||
|
||||
func addPath(path: NSBezierPath!) {
|
||||
func addPath(path: NSBezierPath?) {
|
||||
guard let path = path
|
||||
else {
|
||||
return
|
||||
}
|
||||
self.append(path)
|
||||
}
|
||||
}
|
||||
|
@ -22,13 +22,15 @@ public class MDisplayLink: MDisplayLinkProtocol {
|
||||
|
||||
// MARK: - MDisplayLinkProtocol
|
||||
func startUpdates(_ onUpdate: @escaping () -> Void) {
|
||||
self.onUpdate = onUpdate
|
||||
|
||||
if CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) != kCVReturnSuccess {
|
||||
guard CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) == kCVReturnSuccess,
|
||||
let displayLink = displayLink
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
CVDisplayLinkSetOutputCallback(displayLink!, { _, _, _, _, _, userData -> CVReturn in
|
||||
self.onUpdate = onUpdate
|
||||
|
||||
CVDisplayLinkSetOutputCallback(displayLink, { _, _, _, _, _, userData -> CVReturn in
|
||||
|
||||
let `self` = unsafeBitCast(userData, to: MDisplayLink.self)
|
||||
`self`.onUpdate?()
|
||||
@ -36,9 +38,7 @@ public class MDisplayLink: MDisplayLinkProtocol {
|
||||
return kCVReturnSuccess
|
||||
}, Unmanaged.passUnretained(self).toOpaque())
|
||||
|
||||
if displayLink != nil {
|
||||
CVDisplayLinkStart(displayLink!)
|
||||
}
|
||||
CVDisplayLinkStart(displayLink)
|
||||
}
|
||||
|
||||
func invalidate() {
|
||||
|
@ -48,11 +48,12 @@ class ImageRenderer: NodeRenderer {
|
||||
}
|
||||
|
||||
if let mImage = mImage,
|
||||
let cgImage = mImage.cgImage,
|
||||
let rect = BoundsUtils.getRect(of: image, mImage: mImage) {
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: 0.0, y: -1.0 * rect.height)
|
||||
context.setAlpha(CGFloat(opacity))
|
||||
context.draw(mImage.cgImage!, in: rect)
|
||||
context.draw(cgImage, in: rect)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,8 +133,8 @@ class NodeRenderer {
|
||||
applyClip(in: context)
|
||||
|
||||
// draw masked image
|
||||
if let mask = node.mask, let bounds = mask.bounds {
|
||||
context.draw(getMaskedImage(bounds: bounds), in: bounds.toCG())
|
||||
if let mask = node.mask, let bounds = mask.bounds, let image = getMaskedImage(bounds: bounds) {
|
||||
context.draw(image, in: bounds.toCG())
|
||||
return
|
||||
}
|
||||
|
||||
@ -214,35 +214,47 @@ class NodeRenderer {
|
||||
inset = min(blur.r * 6 + 1, 150)
|
||||
}
|
||||
}
|
||||
|
||||
let shapeImage = CIImage(cgImage: renderToImage(bounds: bounds, inset: inset, coloringMode: coloringMode).cgImage!)
|
||||
guard let image = renderToImage(bounds: bounds, inset: inset, coloringMode: coloringMode)?.cgImage
|
||||
else {
|
||||
return
|
||||
}
|
||||
let shapeImage = CIImage(cgImage: image)
|
||||
|
||||
var filteredImage = shapeImage
|
||||
for effect in effects {
|
||||
if let blur = effect as? GaussianBlur {
|
||||
filteredImage = applyBlur(filteredImage, blur: blur)
|
||||
filteredImage = applyBlur(filteredImage, blur: blur) ?? filteredImage
|
||||
}
|
||||
if let matrix = effect as? ColorMatrixEffect {
|
||||
filteredImage = applyColorMatrix(filteredImage, colorMatrixEffect: matrix)
|
||||
filteredImage = applyColorMatrix(filteredImage, colorMatrixEffect: matrix) ?? filteredImage
|
||||
}
|
||||
}
|
||||
|
||||
let ciContext = CIContext(options: nil)
|
||||
let finalImage = ciContext.createCGImage(filteredImage, from: shapeImage.extent)!
|
||||
guard let finalImage = ciContext.createCGImage(filteredImage, from: shapeImage.extent)
|
||||
else {
|
||||
return
|
||||
}
|
||||
context.draw(finalImage, in: CGRect(x: bounds.x - inset / 2, y: bounds.y - inset / 2, width: bounds.w + inset, height: bounds.h + inset))
|
||||
}
|
||||
|
||||
fileprivate func applyBlur(_ image: CIImage, blur: GaussianBlur) -> CIImage {
|
||||
let filter = CIFilter(name: "CIGaussianBlur")!
|
||||
fileprivate func applyBlur(_ image: CIImage, blur: GaussianBlur) -> CIImage? {
|
||||
guard let filter = CIFilter(name: "CIGaussianBlur")
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
filter.setDefaults()
|
||||
filter.setValue(Int(blur.r), forKey: kCIInputRadiusKey)
|
||||
filter.setValue(image, forKey: kCIInputImageKey)
|
||||
return filter.outputImage!
|
||||
return filter.outputImage
|
||||
}
|
||||
|
||||
fileprivate func applyColorMatrix(_ image: CIImage, colorMatrixEffect: ColorMatrixEffect) -> CIImage {
|
||||
fileprivate func applyColorMatrix(_ image: CIImage, colorMatrixEffect: ColorMatrixEffect) -> CIImage? {
|
||||
guard let filter = CIFilter(name: "CIColorMatrix")
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
let matrix = colorMatrixEffect.matrix.values.map { CGFloat($0) }
|
||||
let filter = CIFilter(name: "CIColorMatrix")!
|
||||
filter.setDefaults()
|
||||
filter.setValue(CIVector(x: matrix[0], y: matrix[1], z: matrix[2], w: matrix[3]), forKey: "inputRVector")
|
||||
filter.setValue(CIVector(x: matrix[5], y: matrix[6], z: matrix[7], w: matrix[8]), forKey: "inputGVector")
|
||||
@ -250,13 +262,16 @@ class NodeRenderer {
|
||||
filter.setValue(CIVector(x: matrix[15], y: matrix[16], z: matrix[17], w: matrix[18]), forKey: "inputAVector")
|
||||
filter.setValue(CIVector(x: matrix[4], y: matrix[9], z: matrix[14], w: matrix[19]), forKey: "inputBiasVector")
|
||||
filter.setValue(image, forKey: kCIInputImageKey)
|
||||
return filter.outputImage!
|
||||
return filter.outputImage
|
||||
}
|
||||
|
||||
func renderToImage(bounds: Rect, inset: Double = 0, coloringMode: ColoringMode = .rgb) -> MImage {
|
||||
func renderToImage(bounds: Rect, inset: Double = 0, coloringMode: ColoringMode = .rgb) -> MImage? {
|
||||
guard let tempContext = MGraphicsGetCurrentContext()
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
let screenScale: CGFloat = MMainScreen()?.mScale ?? 1.0
|
||||
MGraphicsBeginImageContextWithOptions(CGSize(width: bounds.w + inset, height: bounds.h + inset), false, screenScale)
|
||||
let tempContext = MGraphicsGetCurrentContext()!
|
||||
|
||||
// flip y-axis and leave space for the blur
|
||||
tempContext.translateBy(x: CGFloat(inset / 2 - bounds.x), y: CGFloat(bounds.h + inset / 2 + bounds.y))
|
||||
@ -265,7 +280,7 @@ class NodeRenderer {
|
||||
|
||||
let img = MGraphicsGetImageFromCurrentImageContext()
|
||||
MGraphicsEndImageContext()
|
||||
return img!
|
||||
return img
|
||||
}
|
||||
|
||||
func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
|
||||
@ -334,33 +349,46 @@ class NodeRenderer {
|
||||
RenderUtils.toBezierPath(clip).addClip()
|
||||
}
|
||||
|
||||
private func getMaskedImage(bounds: Rect) -> CGImage {
|
||||
let mask = node.mask!
|
||||
let image = renderToImage(bounds: bounds)
|
||||
private func getMaskedImage(bounds: Rect) -> CGImage? {
|
||||
guard let mask = node.mask
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
let nodeRenderer = RenderUtils.createNodeRenderer(mask, view: .none)
|
||||
let maskImage = nodeRenderer.renderToImage(bounds: bounds, coloringMode: .greyscale)
|
||||
guard let image = renderToImage(bounds: bounds),
|
||||
let maskImage = nodeRenderer.renderToImage(bounds: bounds, coloringMode: .greyscale)
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
return apply(maskImage: maskImage, to: image)
|
||||
}
|
||||
|
||||
func apply(maskImage: MImage, to image: MImage) -> CGImage {
|
||||
let imageReference = image.cgImage!
|
||||
let maskReference = maskImage.cgImage!
|
||||
func apply(maskImage: MImage, to image: MImage) -> CGImage? {
|
||||
guard let imageReference = image.cgImage,
|
||||
let maskReference = maskImage.cgImage,
|
||||
let dataProvider = maskReference.dataProvider
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
|
||||
let decode = [CGFloat(1), CGFloat(0),
|
||||
CGFloat(0), CGFloat(1),
|
||||
CGFloat(0), CGFloat(1),
|
||||
CGFloat(0), CGFloat(1)]
|
||||
|
||||
let invertedMask = CGImage(maskWidth: maskReference.width,
|
||||
height: maskReference.height,
|
||||
bitsPerComponent: maskReference.bitsPerComponent,
|
||||
bitsPerPixel: maskReference.bitsPerPixel,
|
||||
bytesPerRow: maskReference.bytesPerRow,
|
||||
provider: maskReference.dataProvider!,
|
||||
decode: decode,
|
||||
shouldInterpolate: maskReference.shouldInterpolate)!
|
||||
guard let invertedMask = CGImage(maskWidth: maskReference.width,
|
||||
height: maskReference.height,
|
||||
bitsPerComponent: maskReference.bitsPerComponent,
|
||||
bitsPerPixel: maskReference.bitsPerPixel,
|
||||
bytesPerRow: maskReference.bytesPerRow,
|
||||
provider: dataProvider,
|
||||
decode: decode,
|
||||
shouldInterpolate: maskReference.shouldInterpolate)
|
||||
else {
|
||||
return .none
|
||||
}
|
||||
|
||||
return imageReference.masking(invertedMask)!
|
||||
return imageReference.masking(invertedMask)
|
||||
}
|
||||
|
||||
private func addObservers() {
|
||||
|
@ -298,28 +298,28 @@ class RenderUtils {
|
||||
func t(_ x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let next = CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y)
|
||||
var quadr: CGPoint?
|
||||
var quadr: CGPoint
|
||||
if let curQuadr = quadrPoint {
|
||||
quadr = CGPoint(x: 2 * cur.x - curQuadr.x, y: 2 * cur.y - curQuadr.y)
|
||||
} else {
|
||||
quadr = cur
|
||||
}
|
||||
bezierPath.addQuadCurve(to: next, controlPoint: quadr!)
|
||||
setQuadrPoint(next, quadr: quadr!)
|
||||
bezierPath.addQuadCurve(to: next, controlPoint: quadr)
|
||||
setQuadrPoint(next, quadr: quadr)
|
||||
}
|
||||
}
|
||||
|
||||
func T(_ x: Double, y: Double) {
|
||||
if let cur = currentPoint {
|
||||
let next = CGPoint(x: CGFloat(x), y: CGFloat(y))
|
||||
var quadr: CGPoint?
|
||||
var quadr: CGPoint
|
||||
if let curQuadr = quadrPoint {
|
||||
quadr = CGPoint(x: 2 * cur.x - curQuadr.x, y: 2 * cur.y - curQuadr.y)
|
||||
} else {
|
||||
quadr = cur
|
||||
}
|
||||
bezierPath.addQuadCurve(to: next, controlPoint: quadr!)
|
||||
setQuadrPoint(next, quadr: quadr!)
|
||||
bezierPath.addQuadCurve(to: next, controlPoint: quadr)
|
||||
setQuadrPoint(next, quadr: quadr)
|
||||
}
|
||||
}
|
||||
|
||||
@ -541,13 +541,17 @@ class RenderUtils {
|
||||
}
|
||||
|
||||
internal class func setStrokeAttributes(_ stroke: Stroke, ctx: CGContext?) {
|
||||
ctx!.setLineWidth(CGFloat(stroke.width))
|
||||
ctx!.setLineJoin(stroke.join.toCG())
|
||||
ctx!.setLineCap(stroke.cap.toCG())
|
||||
ctx!.setMiterLimit(CGFloat(stroke.miterLimit))
|
||||
guard let ctx = ctx
|
||||
else {
|
||||
return
|
||||
}
|
||||
ctx.setLineWidth(CGFloat(stroke.width))
|
||||
ctx.setLineJoin(stroke.join.toCG())
|
||||
ctx.setLineCap(stroke.cap.toCG())
|
||||
ctx.setMiterLimit(CGFloat(stroke.miterLimit))
|
||||
if !stroke.dashes.isEmpty {
|
||||
ctx?.setLineDash(phase: CGFloat(stroke.offset),
|
||||
lengths: stroke.dashes.map { CGFloat($0) })
|
||||
ctx.setLineDash(phase: CGFloat(stroke.offset),
|
||||
lengths: stroke.dashes.map { CGFloat($0) })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,16 +82,20 @@ class ShapeRenderer: NodeRenderer {
|
||||
}
|
||||
|
||||
fileprivate func drawPath(fill: Fill?, stroke: Stroke?, ctx: CGContext?, opacity: Double, fillRule: FillRule) {
|
||||
guard let ctx = ctx
|
||||
else {
|
||||
return
|
||||
}
|
||||
var shouldStrokePath = false
|
||||
if fill is Gradient || stroke?.fill is Gradient {
|
||||
shouldStrokePath = true
|
||||
}
|
||||
|
||||
if let fill = fill, let stroke = stroke {
|
||||
let path = ctx!.path
|
||||
let path = ctx.path
|
||||
setFill(fill, ctx: ctx, opacity: opacity)
|
||||
if stroke.fill is Gradient && !(fill is Gradient) {
|
||||
ctx!.drawPath(using: fillRule == .nonzero ? .fill : .eoFill)
|
||||
ctx.drawPath(using: fillRule == .nonzero ? .fill : .eoFill)
|
||||
}
|
||||
drawWithStroke(stroke, ctx: ctx, opacity: opacity, shouldStrokePath: shouldStrokePath, path: path, mode: fillRule == .nonzero ? .fillStroke : .eoFillStroke)
|
||||
return
|
||||
@ -99,7 +103,7 @@ class ShapeRenderer: NodeRenderer {
|
||||
|
||||
if let fill = fill {
|
||||
setFill(fill, ctx: ctx, opacity: opacity)
|
||||
ctx!.drawPath(using: fillRule == .nonzero ? .fill : .eoFill)
|
||||
ctx.drawPath(using: fillRule == .nonzero ? .fill : .eoFill)
|
||||
return
|
||||
}
|
||||
|
||||
@ -110,12 +114,14 @@ class ShapeRenderer: NodeRenderer {
|
||||
}
|
||||
|
||||
fileprivate func setFill(_ fill: Fill?, ctx: CGContext?, opacity: Double) {
|
||||
guard let fill = fill else {
|
||||
guard let fill = fill,
|
||||
let ctx = ctx
|
||||
else {
|
||||
return
|
||||
}
|
||||
if let fillColor = fill as? Color {
|
||||
let color = RenderUtils.applyOpacity(fillColor, opacity: opacity)
|
||||
ctx!.setFillColor(color.toCG())
|
||||
ctx.setFillColor(color.toCG())
|
||||
} else if let gradient = fill as? Gradient {
|
||||
drawGradient(gradient, ctx: ctx, opacity: opacity)
|
||||
} else if let pattern = fill as? Pattern {
|
||||
@ -126,8 +132,13 @@ class ShapeRenderer: NodeRenderer {
|
||||
}
|
||||
|
||||
fileprivate func drawWithStroke(_ stroke: Stroke, ctx: CGContext?, opacity: Double, shouldStrokePath: Bool = false, path: CGPath? = nil, mode: CGPathDrawingMode) {
|
||||
guard let ctx = ctx
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
if let path = path, shouldStrokePath {
|
||||
ctx!.addPath(path)
|
||||
ctx.addPath(path)
|
||||
}
|
||||
RenderUtils.setStrokeAttributes(stroke, ctx: ctx)
|
||||
|
||||
@ -138,29 +149,35 @@ class ShapeRenderer: NodeRenderer {
|
||||
colorStroke(stroke, ctx: ctx, opacity: opacity)
|
||||
}
|
||||
if shouldStrokePath {
|
||||
ctx!.strokePath()
|
||||
ctx.strokePath()
|
||||
} else {
|
||||
ctx!.drawPath(using: mode)
|
||||
ctx.drawPath(using: mode)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func colorStroke(_ stroke: Stroke, ctx: CGContext?, opacity: Double) {
|
||||
guard let strokeColor = stroke.fill as? Color else {
|
||||
guard let ctx = ctx,
|
||||
let strokeColor = stroke.fill as? Color else {
|
||||
return
|
||||
}
|
||||
let color = RenderUtils.applyOpacity(strokeColor, opacity: opacity)
|
||||
ctx!.setStrokeColor(color.toCG())
|
||||
ctx.setStrokeColor(color.toCG())
|
||||
}
|
||||
|
||||
fileprivate func gradientStroke(_ stroke: Stroke, ctx: CGContext?, opacity: Double) {
|
||||
guard let gradient = stroke.fill as? Gradient else {
|
||||
guard let ctx = ctx,
|
||||
let gradient = stroke.fill as? Gradient else {
|
||||
return
|
||||
}
|
||||
ctx!.replacePathWithStrokedPath()
|
||||
ctx.replacePathWithStrokedPath()
|
||||
drawGradient(gradient, ctx: ctx, opacity: opacity)
|
||||
}
|
||||
|
||||
fileprivate func drawPattern(_ pattern: Pattern, ctx: CGContext?, opacity: Double) {
|
||||
guard let ctx = ctx
|
||||
else {
|
||||
return
|
||||
}
|
||||
var patternNode = pattern.content
|
||||
if !pattern.userSpace, let node = BoundsUtils.createNodeFromRespectiveCoords(respectiveNode: pattern.content, absoluteLocus: shape.form) {
|
||||
patternNode = node
|
||||
@ -172,13 +189,21 @@ class ShapeRenderer: NodeRenderer {
|
||||
let boundsTranform = BoundsUtils.transformForLocusInRespectiveCoords(respectiveLocus: pattern.bounds, absoluteLocus: shape.form)
|
||||
patternBounds = pattern.bounds.applying(boundsTranform)
|
||||
}
|
||||
let tileImage = renderer.renderToImage(bounds: patternBounds, inset: 0)
|
||||
ctx?.clip()
|
||||
ctx?.draw(tileImage.cgImage!, in: patternBounds.toCG(), byTiling: true)
|
||||
guard let tileImage = renderer.renderToImage(bounds: patternBounds, inset: 0),
|
||||
let cgTileImage = tileImage.cgImage
|
||||
else {
|
||||
return
|
||||
}
|
||||
ctx.clip()
|
||||
ctx.draw(cgTileImage, in: patternBounds.toCG(), byTiling: true)
|
||||
}
|
||||
|
||||
fileprivate func drawGradient(_ gradient: Gradient, ctx: CGContext?, opacity: Double) {
|
||||
ctx!.saveGState()
|
||||
guard let ctx = ctx
|
||||
else {
|
||||
return
|
||||
}
|
||||
ctx.saveGState()
|
||||
var colors: [CGColor] = []
|
||||
var stops: [CGFloat] = []
|
||||
for stop in gradient.stops {
|
||||
@ -191,19 +216,22 @@ class ShapeRenderer: NodeRenderer {
|
||||
var start = CGPoint(x: CGFloat(gradient.x1), y: CGFloat(gradient.y1))
|
||||
var end = CGPoint(x: CGFloat(gradient.x2), y: CGFloat(gradient.y2))
|
||||
if !gradient.userSpace {
|
||||
let bounds = ctx!.boundingBoxOfPath
|
||||
let bounds = ctx.boundingBoxOfPath
|
||||
start = CGPoint(x: start.x * bounds.width + bounds.minX, y: start.y * bounds.height + bounds.minY)
|
||||
end = CGPoint(x: end.x * bounds.width + bounds.minX, y: end.y * bounds.height + bounds.minY)
|
||||
}
|
||||
ctx!.clip()
|
||||
let cgGradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: stops)
|
||||
ctx!.drawLinearGradient(cgGradient!, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation])
|
||||
ctx.clip()
|
||||
guard let cgGradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: stops)
|
||||
else {
|
||||
return
|
||||
}
|
||||
ctx.drawLinearGradient(cgGradient, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation])
|
||||
} else if let gradient = gradient as? RadialGradient {
|
||||
var innerCenter = CGPoint(x: CGFloat(gradient.fx), y: CGFloat(gradient.fy))
|
||||
var outerCenter = CGPoint(x: CGFloat(gradient.cx), y: CGFloat(gradient.cy))
|
||||
var radius = CGFloat(gradient.r)
|
||||
if !gradient.userSpace {
|
||||
var bounds = ctx!.boundingBoxOfPath
|
||||
var bounds = ctx.boundingBoxOfPath
|
||||
var scaleX: CGFloat = 1
|
||||
var scaleY: CGFloat = 1
|
||||
if bounds.width > bounds.height {
|
||||
@ -211,18 +239,21 @@ class ShapeRenderer: NodeRenderer {
|
||||
} else {
|
||||
scaleX = bounds.width / bounds.height
|
||||
}
|
||||
ctx!.scaleBy(x: scaleX, y: scaleY)
|
||||
bounds = ctx!.boundingBoxOfPath
|
||||
ctx.scaleBy(x: scaleX, y: scaleY)
|
||||
bounds = ctx.boundingBoxOfPath
|
||||
innerCenter = CGPoint(x: innerCenter.x * bounds.width + bounds.minX, y: innerCenter.y * bounds.height + bounds.minY)
|
||||
outerCenter = CGPoint(x: outerCenter.x * bounds.width + bounds.minX, y: outerCenter.y * bounds.height + bounds.minY)
|
||||
radius = min(radius * bounds.width, radius * bounds.height)
|
||||
|
||||
}
|
||||
ctx!.clip()
|
||||
let cgGradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: stops)
|
||||
ctx!.drawRadialGradient(cgGradient!, startCenter: innerCenter, startRadius: 0, endCenter: outerCenter, endRadius: radius, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation])
|
||||
ctx.clip()
|
||||
guard let cgGradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: stops)
|
||||
else {
|
||||
return
|
||||
}
|
||||
ctx.drawRadialGradient(cgGradient, startCenter: innerCenter, startRadius: 0, endCenter: outerCenter, endRadius: radius, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation])
|
||||
}
|
||||
ctx!.restoreGState()
|
||||
ctx.restoreGState()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -40,21 +40,18 @@ class CSSParser {
|
||||
for (index, header) in headers.enumerated() {
|
||||
for headerPart in header.split(separator: ",") where headerPart.count > 1 {
|
||||
let selector = parseSelector(text: String(headerPart))
|
||||
var currentStyles = getStyles(selector: selector)
|
||||
if currentStyles == nil {
|
||||
currentStyles = [String: String]()
|
||||
}
|
||||
var currentStyles = getStyles(selector: selector) ?? [String: String]()
|
||||
let style = String(bodies[index])
|
||||
let styleParts = style.components(separatedBy: ";")
|
||||
styleParts.forEach { styleAttribute in
|
||||
if !styleAttribute.isEmpty {
|
||||
let currentStyle = styleAttribute.components(separatedBy: ":")
|
||||
if currentStyle.count == 2 {
|
||||
currentStyles![currentStyle[0]] = currentStyle[1]
|
||||
currentStyles[currentStyle[0]] = currentStyle[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
setStyles(selector: selector, styles: currentStyles!)
|
||||
setStyles(selector: selector, styles: currentStyles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -470,8 +470,8 @@ open class SVGParser {
|
||||
if pattern.children.isEmpty {
|
||||
return parentPattern?.content
|
||||
} else if pattern.children.count == 1,
|
||||
let child = pattern.children.first,
|
||||
let shape = try parseNode(child) as? Shape {
|
||||
let child = pattern.children.first,
|
||||
let shape = try parseNode(child) as? Shape {
|
||||
return shape
|
||||
} else {
|
||||
var shapes = [Shape]()
|
||||
@ -1633,7 +1633,7 @@ open class SVGParser {
|
||||
if stopColor == SVGKeys.currentColor, let currentColor = groupStyle[SVGKeys.color] {
|
||||
stopColor = currentColor
|
||||
}
|
||||
color = createColor(stopColor.replacingOccurrences(of: " ", with: ""), opacity: opacity)!
|
||||
color = createColor(stopColor.replacingOccurrences(of: " ", with: ""), opacity: opacity) ?? color
|
||||
}
|
||||
|
||||
return Stop(offset: offset, color: color)
|
||||
@ -2248,7 +2248,7 @@ fileprivate extension Scanner {
|
||||
return scanUpTo(substring, into: &string) ? string as String? : nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A version of `scanString(_:)`, available for an earlier OS.
|
||||
func scannedString(_ searchString: String) -> String? {
|
||||
if #available(OSX 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) {
|
||||
|
@ -409,6 +409,6 @@ extension Double {
|
||||
formatter.minimumIntegerDigits = 1
|
||||
formatter.maximumFractionDigits = 6
|
||||
formatter.decimalSeparator = "."
|
||||
return abs(self.remainder(dividingBy: 1)) > 0.00001 ? formatter.string(from: NSNumber(value: self))! : String(Int(self.rounded()))
|
||||
return abs(self.remainder(dividingBy: 1)) > 0.00001 ? formatter.string(from: NSNumber(value: self)) ?? "" : String(Int(self.rounded()))
|
||||
}
|
||||
}
|
||||
|
@ -135,12 +135,15 @@ public extension CGAffineTransform {
|
||||
|
||||
public extension Node {
|
||||
|
||||
func toNativeImage(size: Size, layout: ContentLayout = .of()) -> MImage {
|
||||
func toNativeImage(size: Size, layout: ContentLayout = .of()) -> MImage? {
|
||||
let renderer = RenderUtils.createNodeRenderer(self, view: nil)
|
||||
let rect = size.rect()
|
||||
|
||||
MGraphicsBeginImageContextWithOptions(size.toCG(), false, 1)
|
||||
let ctx = MGraphicsGetCurrentContext()!
|
||||
guard let ctx = MGraphicsGetCurrentContext()
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
ctx.clear(rect.toCG())
|
||||
|
||||
let transform = LayoutHelper.calcTransform(self, layout, size)
|
||||
@ -149,7 +152,7 @@ public extension Node {
|
||||
|
||||
let img = MGraphicsGetImageFromCurrentImageContext()
|
||||
MGraphicsEndImageContext()
|
||||
return img!
|
||||
return img
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -283,7 +283,7 @@ internal class DrawingView: MView {
|
||||
var touchesOfNode = [Node: [MTouchEvent]]()
|
||||
var recognizersMap = [MGestureRecognizer: [Node]]()
|
||||
|
||||
var context: RenderContext!
|
||||
lazy var context: RenderContext = RenderContext(view: self)
|
||||
var renderer: NodeRenderer?
|
||||
|
||||
var toRender = true
|
||||
@ -376,7 +376,10 @@ internal class DrawingView: MView {
|
||||
touchesMap[touch] = [NodePath]()
|
||||
}
|
||||
|
||||
let inverted = node.place.invert()!
|
||||
guard let inverted = node.place.invert()
|
||||
else {
|
||||
return
|
||||
}
|
||||
let loc = location.applying(inverted.toCG())
|
||||
|
||||
var relativeToView = CGPoint.zero
|
||||
@ -430,7 +433,10 @@ internal class DrawingView: MView {
|
||||
continue
|
||||
}
|
||||
let location = CGPoint(x: currentTouch.x, y: currentTouch.y)
|
||||
let inverted = currentNode.place.invert()!
|
||||
guard let inverted = currentNode.place.invert()
|
||||
else {
|
||||
return
|
||||
}
|
||||
let loc = location.applying(inverted.toCG())
|
||||
|
||||
var relativeToView = CGPoint.zero
|
||||
@ -458,7 +464,10 @@ internal class DrawingView: MView {
|
||||
touchesMap[touch]?.forEach { nodePath in
|
||||
|
||||
let node = nodePath.node
|
||||
let inverted = node.place.invert()!
|
||||
guard let inverted = node.place.invert()
|
||||
else {
|
||||
return
|
||||
}
|
||||
let location = CGPoint(x: touch.x, y: touch.y)
|
||||
let loc = location.applying(inverted.toCG())
|
||||
|
||||
@ -502,11 +511,14 @@ internal class DrawingView: MView {
|
||||
|
||||
while let current = nodePath {
|
||||
let node = current.node
|
||||
let inverted = node.place.invert()!
|
||||
nodePath = nodePath?.parent
|
||||
guard let inverted = node.place.invert()
|
||||
else {
|
||||
break
|
||||
}
|
||||
let loc = location.applying(inverted.toCG())
|
||||
let event = TapEvent(node: node, location: loc.toMacaw())
|
||||
node.handleTap(event)
|
||||
nodePath = nodePath?.parent
|
||||
}
|
||||
}
|
||||
|
||||
@ -528,11 +540,14 @@ internal class DrawingView: MView {
|
||||
|
||||
while let next = nodePath.parent {
|
||||
let node = nodePath.node
|
||||
let inverted = node.place.invert()!
|
||||
nodePath = next
|
||||
guard let inverted = node.place.invert()
|
||||
else {
|
||||
break
|
||||
}
|
||||
let loc = location.applying(inverted.toCG())
|
||||
let event = TapEvent(node: node, location: loc.toMacaw())
|
||||
node.handleLongTap(event, touchBegan: recognizer.state == .began)
|
||||
nodePath = next
|
||||
}
|
||||
}
|
||||
|
||||
@ -727,7 +742,11 @@ class LayoutHelper {
|
||||
if let rect = prevRect {
|
||||
return rect
|
||||
}
|
||||
return canvas.layout(size: prevSize!).rect()
|
||||
if let prevSize = prevSize {
|
||||
return canvas.layout(size: prevSize).rect()
|
||||
} else {
|
||||
return node.bounds
|
||||
}
|
||||
} else {
|
||||
return node.bounds
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user