diff --git a/Dependencies/SWXMLHash/SWXMLHash.swift b/Dependencies/SWXMLHash/SWXMLHash.swift index 1ad42d73..e120cc5a 100755 --- a/Dependencies/SWXMLHash/SWXMLHash.swift +++ b/Dependencies/SWXMLHash/SWXMLHash.swift @@ -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() diff --git a/MacawTests/Animation/ControlStatesTests.swift b/MacawTests/Animation/ControlStatesTests.swift index 8037dce6..ede8c4c2 100644 --- a/MacawTests/Animation/ControlStatesTests.swift +++ b/MacawTests/Animation/ControlStatesTests.swift @@ -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() diff --git a/MacawTests/MacawSVGTests.swift b/MacawTests/MacawSVGTests.swift index 78756407..52a358b0 100644 --- a/MacawTests/MacawSVGTests.swift +++ b/MacawTests/MacawSVGTests.swift @@ -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) diff --git a/MacawTests/SceneSerialization.swift b/MacawTests/SceneSerialization.swift index 1c914c58..d68b5351 100644 --- a/MacawTests/SceneSerialization.swift +++ b/MacawTests/SceneSerialization.swift @@ -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) } } diff --git a/Source/animation/Animation.swift b/Source/animation/Animation.swift index 9db2a92a..a5da0175 100644 --- a/Source/animation/Animation.swift +++ b/Source/animation/Animation.swift @@ -39,7 +39,7 @@ public class Animation { return self } - public func reverse() -> Animation { + public func reverse() -> Animation? { return self } diff --git a/Source/animation/AnimationImpl.swift b/Source/animation/AnimationImpl.swift index 2161999f..fe827804 100644 --- a/Source/animation/AnimationImpl.swift +++ b/Source/animation/AnimationImpl.swift @@ -135,7 +135,7 @@ class BasicAnimation: Animation { return .running } - override open func reverse() -> Animation { + override open func reverse() -> Animation? { return self } diff --git a/Source/animation/AnimationProducer.swift b/Source/animation/AnimationProducer.swift index 41b8534a..c04ddc16 100644 --- a/Source/animation/AnimationProducer.swift +++ b/Source/animation/AnimationProducer.swift @@ -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 } diff --git a/Source/animation/types/ContentsAnimation.swift b/Source/animation/types/ContentsAnimation.swift index 75bcbd5d..eaab5c37 100644 --- a/Source/animation/types/ContentsAnimation.swift +++ b/Source/animation/types/ContentsAnimation.swift @@ -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) } } diff --git a/Source/animation/types/OpacityAnimation.swift b/Source/animation/types/OpacityAnimation.swift index cef432bf..cddd45f2 100644 --- a/Source/animation/types/OpacityAnimation.swift +++ b/Source/animation/types/OpacityAnimation.swift @@ -29,7 +29,11 @@ internal class OpacityAnimation: AnimationImpl { } } - 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 { } } - 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 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) } } diff --git a/Source/animation/types/PathAnimation.swift b/Source/animation/types/PathAnimation.swift index 0420d7e0..06c974d8 100644 --- a/Source/animation/types/PathAnimation.swift +++ b/Source/animation/types/PathAnimation.swift @@ -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) } diff --git a/Source/animation/types/ShapeAnimation.swift b/Source/animation/types/ShapeAnimation.swift index 4a41c24c..a8b42bc8 100644 --- a/Source/animation/types/ShapeAnimation.swift +++ b/Source/animation/types/ShapeAnimation.swift @@ -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) diff --git a/Source/animation/types/TransformAnimation.swift b/Source/animation/types/TransformAnimation.swift index 67bc1f19..be1870e2 100644 --- a/Source/animation/types/TransformAnimation.swift +++ b/Source/animation/types/TransformAnimation.swift @@ -44,8 +44,11 @@ internal class TransformAnimation: AnimationImpl { } } - 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 { } } - 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 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) } } diff --git a/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift b/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift index 3fa9f767..913e7616 100644 --- a/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift +++ b/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift @@ -44,7 +44,9 @@ extension AnimationProducer { for i in minPathsNumber.. 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 diff --git a/Source/platform/iOS/Graphics_iOS.swift b/Source/platform/iOS/Graphics_iOS.swift index 52a04aa7..10b0ff51 100644 --- a/Source/platform/iOS/Graphics_iOS.swift +++ b/Source/platform/iOS/Graphics_iOS.swift @@ -15,7 +15,7 @@ func MGraphicsGetCurrentContext() -> CGContext? { return UIGraphicsGetCurrentContext() } -func MGraphicsGetImageFromCurrentImageContext() -> MImage! { +func MGraphicsGetImageFromCurrentImageContext() -> MImage? { return UIGraphicsGetImageFromCurrentImageContext() } diff --git a/Source/platform/macOS/MBezierPath+Extension_macOS.swift b/Source/platform/macOS/MBezierPath+Extension_macOS.swift index e38dbf29..cb4145c8 100644 --- a/Source/platform/macOS/MBezierPath+Extension_macOS.swift +++ b/Source/platform/macOS/MBezierPath+Extension_macOS.swift @@ -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) } } diff --git a/Source/platform/macOS/MDisplayLink_macOS.swift b/Source/platform/macOS/MDisplayLink_macOS.swift index dd0303f6..75dbc3c3 100644 --- a/Source/platform/macOS/MDisplayLink_macOS.swift +++ b/Source/platform/macOS/MDisplayLink_macOS.swift @@ -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() { diff --git a/Source/render/ImageRenderer.swift b/Source/render/ImageRenderer.swift index 0f1b7667..8f5aef30 100644 --- a/Source/render/ImageRenderer.swift +++ b/Source/render/ImageRenderer.swift @@ -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) } } diff --git a/Source/render/NodeRenderer.swift b/Source/render/NodeRenderer.swift index 268b7552..cf95c3d9 100644 --- a/Source/render/NodeRenderer.swift +++ b/Source/render/NodeRenderer.swift @@ -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() { diff --git a/Source/render/RenderUtils.swift b/Source/render/RenderUtils.swift index fe6b3d01..a08b8b3b 100644 --- a/Source/render/RenderUtils.swift +++ b/Source/render/RenderUtils.swift @@ -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) }) } } diff --git a/Source/render/ShapeRenderer.swift b/Source/render/ShapeRenderer.swift index 48345d86..9a093b2d 100644 --- a/Source/render/ShapeRenderer.swift +++ b/Source/render/ShapeRenderer.swift @@ -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() } } diff --git a/Source/svg/CSSParser.swift b/Source/svg/CSSParser.swift index e919abcc..b77611aa 100644 --- a/Source/svg/CSSParser.swift +++ b/Source/svg/CSSParser.swift @@ -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) } } } diff --git a/Source/svg/SVGParser.swift b/Source/svg/SVGParser.swift index 20c26c01..0876a314 100644 --- a/Source/svg/SVGParser.swift +++ b/Source/svg/SVGParser.swift @@ -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, *) { diff --git a/Source/svg/SVGSerializer.swift b/Source/svg/SVGSerializer.swift index 096abbe1..6e6a89fd 100644 --- a/Source/svg/SVGSerializer.swift +++ b/Source/svg/SVGSerializer.swift @@ -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())) } } diff --git a/Source/utils/CGMappings.swift b/Source/utils/CGMappings.swift index 6fe5042e..123f6595 100644 --- a/Source/utils/CGMappings.swift +++ b/Source/utils/CGMappings.swift @@ -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 } } diff --git a/Source/views/MacawView.swift b/Source/views/MacawView.swift index cb1ae1e1..ce85c17d 100644 --- a/Source/views/MacawView.swift +++ b/Source/views/MacawView.swift @@ -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 }