1
1
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:
Robert Smart 2020-12-14 20:07:17 +11:00
parent 2b406e55f2
commit 33e8f4b467
27 changed files with 369 additions and 171 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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)
}
}

View File

@ -39,7 +39,7 @@ public class Animation {
return self
}
public func reverse() -> Animation {
public func reverse() -> Animation? {
return self
}

View File

@ -135,7 +135,7 @@ class BasicAnimation: Animation {
return .running
}
override open func reverse() -> Animation {
override open func reverse() -> Animation? {
return self
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -15,7 +15,7 @@ func MGraphicsGetCurrentContext() -> CGContext? {
return UIGraphicsGetCurrentContext()
}
func MGraphicsGetImageFromCurrentImageContext() -> MImage! {
func MGraphicsGetImageFromCurrentImageContext() -> MImage? {
return UIGraphicsGetImageFromCurrentImageContext()
}

View File

@ -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)
}
}

View File

@ -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() {

View File

@ -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)
}
}

View File

@ -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() {

View File

@ -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) })
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}
}

View File

@ -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, *) {

View File

@ -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()))
}
}

View File

@ -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
}
}

View File

@ -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
}