diff --git a/.travis.yml b/.travis.yml index ddaa8b2c..784378d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,12 @@ language: swift -osx_image: xcode10.2 +osx_image: xcode11.5 branches: only: - master script: - - set -o pipefail && xcodebuild test -project Macaw.xcodeproj -scheme 'Macaw iOS' -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO -destination 'platform=iOS Simulator,OS=12.2,name=iPhone X' | xcpretty; + - set -o pipefail && xcodebuild test -project Macaw.xcodeproj -scheme 'Macaw iOS' -sdk iphonesimulator13.5 ONLY_ACTIVE_ARCH=NO -destination 'platform=iOS Simulator,OS=13.5,name=iPhone 11 Pro' | xcpretty; - set -o pipefail && xcodebuild test -project Macaw.xcodeproj -scheme 'MacawOSX' ONLY_ACTIVE_ARCH=NO | xcpretty; diff --git a/Example/Example/Examples/AnimationsHierarchy/AnimationsHierarchyViewController.swift b/Example/Example/Examples/AnimationsHierarchy/AnimationsHierarchyViewController.swift index 394c4ebc..e1f554d3 100644 --- a/Example/Example/Examples/AnimationsHierarchy/AnimationsHierarchyViewController.swift +++ b/Example/Example/Examples/AnimationsHierarchy/AnimationsHierarchyViewController.swift @@ -13,6 +13,9 @@ class AnimationsHierarchyViewController: UIViewController { @IBOutlet weak var animView: MacawView! + var startCallbacks: [()->()] = [] + var stopCallbacks: [()->()] = [] + override func viewDidLoad() { super.viewDidLoad() @@ -20,6 +23,22 @@ class AnimationsHierarchyViewController: UIViewController { animView.zoom.enable() } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + startCallbacks.forEach { + $0() + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + stopCallbacks.forEach { + $0() + } + } + func createTree(height: Int) -> Node { let rect = Rect(w: 10, h: 10) @@ -52,7 +71,14 @@ class AnimationsHierarchyViewController: UIViewController { func createLeaf(childForm: Locus, xDelta: Double, yDelta: Double) -> Group { let inset = childForm.bounds().w / 2 let leaf = Shape(form: childForm, fill: Color.teal, place: .move(dx: -inset, dy: -inset)) - leaf.placeVar.animation(angle: 2 * .pi, during: 5).cycle().play() + + let animation = leaf.placeVar.animation(angle: 2 * .pi, during: 5).cycle() + startCallbacks.append({ + animation.play() + }) + stopCallbacks.append({ + animation.stop() + }) let leafGroup = [leaf].group(place: .move(dx: xDelta, dy: yDelta)) leaf.onTap { _ in diff --git a/Macaw.xcodeproj/project.pbxproj b/Macaw.xcodeproj/project.pbxproj index 10d2385e..59ee40a5 100644 --- a/Macaw.xcodeproj/project.pbxproj +++ b/Macaw.xcodeproj/project.pbxproj @@ -233,7 +233,6 @@ 57614B661F83D15600875933 /* PinchEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E10C1E3B393900D1CB28 /* PinchEvent.swift */; }; 57614B671F83D15600875933 /* ContentsInterpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57A27BCE1E44C4EC0057BD3A /* ContentsInterpolation.swift */; }; 57614B681F83D15600875933 /* GroupRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E13E1E3B393900D1CB28 /* GroupRenderer.swift */; }; - 57614B691F83D15600875933 /* SVGParserRegexHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1491E3B393900D1CB28 /* SVGParserRegexHelper.swift */; }; 57614B6B1F83D15600875933 /* NSTimer+Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14E1E3B393900D1CB28 /* NSTimer+Closure.swift */; }; 57614B6C1F83D15600875933 /* SWXMLHash+TypeConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572CEFC51E2CED4B008C7C83 /* SWXMLHash+TypeConversion.swift */; }; 57614B6D1F83D15600875933 /* AnimationSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0FF1E3B393900D1CB28 /* AnimationSequence.swift */; }; @@ -330,7 +329,6 @@ 57E5E1AA1E3B393900D1CB28 /* SVGConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1461E3B393900D1CB28 /* SVGConstants.swift */; }; 57E5E1AB1E3B393900D1CB28 /* SVGParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1471E3B393900D1CB28 /* SVGParser.swift */; }; 57E5E1AC1E3B393900D1CB28 /* SVGParserError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1481E3B393900D1CB28 /* SVGParserError.swift */; }; - 57E5E1AD1E3B393900D1CB28 /* SVGParserRegexHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1491E3B393900D1CB28 /* SVGParserRegexHelper.swift */; }; 57E5E1AE1E3B393900D1CB28 /* SVGView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14A1E3B393900D1CB28 /* SVGView.swift */; }; 57E5E1AF1E3B393900D1CB28 /* CAAnimationClosure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14C1E3B393900D1CB28 /* CAAnimationClosure.swift */; }; 57E5E1B01E3B393900D1CB28 /* CGFloat+Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14D1E3B393900D1CB28 /* CGFloat+Double.swift */; }; @@ -950,7 +948,6 @@ 57E5E1461E3B393900D1CB28 /* SVGConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGConstants.swift; sourceTree = ""; }; 57E5E1471E3B393900D1CB28 /* SVGParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGParser.swift; sourceTree = ""; }; 57E5E1481E3B393900D1CB28 /* SVGParserError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGParserError.swift; sourceTree = ""; }; - 57E5E1491E3B393900D1CB28 /* SVGParserRegexHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGParserRegexHelper.swift; sourceTree = ""; }; 57E5E14A1E3B393900D1CB28 /* SVGView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGView.swift; sourceTree = ""; }; 57E5E14C1E3B393900D1CB28 /* CAAnimationClosure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationClosure.swift; sourceTree = ""; }; 57E5E14D1E3B393900D1CB28 /* CGFloat+Double.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat+Double.swift"; sourceTree = ""; }; @@ -1731,7 +1728,6 @@ 5B1A8C7520A15F7300E5FFAE /* SVGNodeLayout.swift */, 57E5E1471E3B393900D1CB28 /* SVGParser.swift */, 57E5E1481E3B393900D1CB28 /* SVGParserError.swift */, - 57E5E1491E3B393900D1CB28 /* SVGParserRegexHelper.swift */, C4820B171F458D0E008CE0FF /* SVGSerializer.swift */, 57E5E14A1E3B393900D1CB28 /* SVGView.swift */, ); @@ -2811,7 +2807,6 @@ 57614B671F83D15600875933 /* ContentsInterpolation.swift in Sources */, 57614B681F83D15600875933 /* GroupRenderer.swift in Sources */, 5B6E192820AC58F900454E7E /* RadialGradient.swift in Sources */, - 57614B691F83D15600875933 /* SVGParserRegexHelper.swift in Sources */, 5B6E192A20AC58F900454E7E /* Align.swift in Sources */, 57614B6B1F83D15600875933 /* NSTimer+Closure.swift in Sources */, 5B6E193620AC58F900454E7E /* Stop.swift in Sources */, @@ -2954,7 +2949,6 @@ 57A27BCF1E44C4EC0057BD3A /* ContentsInterpolation.swift in Sources */, 57E5E1A31E3B393900D1CB28 /* GroupRenderer.swift in Sources */, 5B6E192720AC58F900454E7E /* RadialGradient.swift in Sources */, - 57E5E1AD1E3B393900D1CB28 /* SVGParserRegexHelper.swift in Sources */, 5B6E192920AC58F900454E7E /* Align.swift in Sources */, 57E5E1B11E3B393900D1CB28 /* NSTimer+Closure.swift in Sources */, 30FF4962215CE97300FF653C /* MCAMediaTimingFillMode_iOS.swift in Sources */, @@ -3004,6 +2998,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -3029,6 +3024,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -3226,8 +3222,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 7T95R85V93; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SDKROOT)", @@ -3249,8 +3245,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 7T95R85V93; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SDKROOT)", diff --git a/Macaw.xcodeproj/xcshareddata/xcschemes/MacawTests.xcscheme b/Macaw.xcodeproj/xcshareddata/xcschemes/MacawTests.xcscheme index 94cec15d..998fd245 100644 --- a/Macaw.xcodeproj/xcshareddata/xcschemes/MacawTests.xcscheme +++ b/Macaw.xcodeproj/xcshareddata/xcschemes/MacawTests.xcscheme @@ -5,6 +5,22 @@ + + + + + + - - - - + + + + diff --git a/MacawTests/MacawSVGTests.swift b/MacawTests/MacawSVGTests.swift index fb22ef30..78756407 100644 --- a/MacawTests/MacawSVGTests.swift +++ b/MacawTests/MacawSVGTests.swift @@ -20,13 +20,13 @@ class MacawSVGTests: XCTestCase { private let testFolderName = "MacawTestOutputData" private let shouldComparePNGImages = true private let multipleTestsWillRun = false - private let shouldSaveFaildedTestImage = false + private let shouldSaveFailedTestImage = false override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. super.setUp() - if shouldSaveFaildedTestImage { + if shouldSaveFailedTestImage { setupTestFolderDirectory() } } @@ -36,6 +36,21 @@ class MacawSVGTests: XCTestCase { super.tearDown() } + func compareResults(nodeContent: String?, referenceContent: String?) { + guard let nodeContent = nodeContent else { + XCTFail("nodeContent is empty") + return + } + guard let referenceContent = referenceContent else { + XCTFail("referenceContent is empty") + return + } + + if nodeContent != referenceContent { + XCTFail("nodeContent is not equal to referenceContent" + TestUtils.prettyFirstDifferenceBetweenStrings(s1: nodeContent, s2: referenceContent)) + } + } + func validate(node: Node, referenceFile: String) { let bundle = Bundle(for: type(of: TestUtils())) @@ -43,7 +58,7 @@ class MacawSVGTests: XCTestCase { if let path = bundle.path(forResource: referenceFile, ofType: "reference") { let clipReferenceContent = try String.init(contentsOfFile: path).trimmingCharacters(in: .newlines) let result = SVGSerializer.serialize(node: node) - XCTAssertEqual(result, clipReferenceContent) + compareResults(nodeContent: result, referenceContent: clipReferenceContent) } else { XCTFail("No file \(referenceFile)") } @@ -192,13 +207,10 @@ class MacawSVGTests: XCTestCase { let bundle = Bundle(for: type(of: TestUtils())) do { if let path = bundle.path(forResource: referenceFile, ofType: "reference") { + let referenceContent = try String(contentsOfFile: path) - let nodeContent = String(data: getJSONData(node: node), encoding: String.Encoding.utf8) - - if nodeContent != referenceContent { - XCTFail("nodeContent is not equal to referenceContent") - } + compareResults(nodeContent: nodeContent, referenceContent: referenceContent) let nativeImage = getImage(from: referenceFile) @@ -261,9 +273,9 @@ class MacawSVGTests: XCTestCase { if referenceContentData != nodeContentData { - var failInfo = "referenceContentData is not equal to nodeContentData" + var failInfo = "referenceImageData is not equal to nodeImageData" - if shouldSaveFaildedTestImage { + if shouldSaveFailedTestImage { let _ = saveImage(image: referenceImage, fileName: referenceFile + "_reference") let _ = saveImage(image: nodeImage, fileName: referenceFile + "_incorrect") @@ -314,7 +326,7 @@ class MacawSVGTests: XCTestCase { } func writeToFile(string: String, fileName: String) -> URL? { - guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else { + guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) as NSURL else { return .none } do { diff --git a/MacawTests/TestUtils.swift b/MacawTests/TestUtils.swift index 5fedc62f..71ca68b7 100644 --- a/MacawTests/TestUtils.swift +++ b/MacawTests/TestUtils.swift @@ -25,4 +25,133 @@ class TestUtils { return result } + class func prettyFirstDifferenceBetweenStrings(s1: String, s2: String) -> String { + return prettyFirstDifferenceBetweenNSStrings(s1: s1 as NSString, s2: s2 as NSString) as String + } + +} + +/// Find first differing character between two strings +/// +/// :param: s1 First String +/// :param: s2 Second String +/// +/// :returns: .DifferenceAtIndex(i) or .NoDifference +fileprivate func firstDifferenceBetweenStrings(s1: NSString, s2: NSString) -> FirstDifferenceResult { + let len1 = s1.length + let len2 = s2.length + + let lenMin = min(len1, len2) + + for i in 0.. NSString { + let firstDifferenceResult = firstDifferenceBetweenStrings(s1: s1, s2: s2) + return prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult: firstDifferenceResult, s1: s1, s2: s2) +} + + +/// Create a formatted String representation of a FirstDifferenceResult for two strings +/// +/// :param: firstDifferenceResult FirstDifferenceResult +/// :param: s1 First string used in generation of firstDifferenceResult +/// :param: s2 Second string used in generation of firstDifferenceResult +/// +/// :returns: a printable string, possibly containing significant whitespace and newlines +fileprivate func prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult: FirstDifferenceResult, s1: NSString, s2: NSString) -> NSString { + + func diffString(index: Int, s1: NSString, s2: NSString) -> NSString { + let markerArrow = "\u{2b06}" // "⬆" + let ellipsis = "\u{2026}" // "…" + /// Given a string and a range, return a string representing that substring. + /// + /// If the range starts at a position other than 0, an ellipsis + /// will be included at the beginning. + /// + /// If the range ends before the actual end of the string, + /// an ellipsis is added at the end. + func windowSubstring(s: NSString, range: NSRange) -> String { + let validRange = NSMakeRange(range.location, min(range.length, s.length - range.location)) + let substring = s.substring(with: validRange) + + let prefix = range.location > 0 ? ellipsis : "" + let suffix = (s.length - range.location > range.length) ? ellipsis : "" + + return "\(prefix)\(substring)\(suffix)" + } + + // Show this many characters before and after the first difference + let windowPrefixLength = 10 + let windowSuffixLength = 10 + let windowLength = windowPrefixLength + 1 + windowSuffixLength + + let windowIndex = max(index - windowPrefixLength, 0) + let windowRange = NSMakeRange(windowIndex, windowLength) + + let sub1 = windowSubstring(s: s1, range: windowRange) + let sub2 = windowSubstring(s: s2, range: windowRange) + + let markerPosition = min(windowSuffixLength, index) + (windowIndex > 0 ? 1 : 0) + + let markerPrefix = String(repeating: " ", count: markerPosition) + let markerLine = "\(markerPrefix)\(markerArrow)" + + return "Difference at index \(index):\n\(sub1)\n\(sub2)\n\(markerLine)" as NSString + } + + switch firstDifferenceResult { + case .NoDifference: return "No difference" + case .DifferenceAtIndex(let index): return diffString(index: index, s1: s1, s2: s2) + } +} + +/// Result type for firstDifferenceBetweenStrings() +public enum FirstDifferenceResult { + /// Strings are identical + case NoDifference + + /// Strings differ at the specified index. + /// + /// This could mean that characters at the specified index are different, + /// or that one string is longer than the other + case DifferenceAtIndex(Int) +} + +extension FirstDifferenceResult: CustomStringConvertible { + /// Textual representation of a FirstDifferenceResult + public var description: String { + switch self { + case .NoDifference: + return "NoDifference" + case .DifferenceAtIndex(let index): + return "DifferenceAtIndex(\(index))" + } + } + + /// Textual representation of a FirstDifferenceResult for debugging purposes + public var debugDescription: String { + return self.description + } } diff --git a/MacawTests/png/color-prop-01-b-manual.png b/MacawTests/png/color-prop-01-b-manual.png index 18a3bea5..ddc2e9c1 100644 Binary files a/MacawTests/png/color-prop-01-b-manual.png and b/MacawTests/png/color-prop-01-b-manual.png differ diff --git a/MacawTests/png/color-prop-03-t-manual.png b/MacawTests/png/color-prop-03-t-manual.png index 1a392e67..abd0daf5 100644 Binary files a/MacawTests/png/color-prop-03-t-manual.png and b/MacawTests/png/color-prop-03-t-manual.png differ diff --git a/MacawTests/png/color-prop-04-t-manual-osx.png b/MacawTests/png/color-prop-04-t-manual-osx.png index 78172e79..36820a9d 100644 Binary files a/MacawTests/png/color-prop-04-t-manual-osx.png and b/MacawTests/png/color-prop-04-t-manual-osx.png differ diff --git a/MacawTests/png/color-prop-05-t-manual.png b/MacawTests/png/color-prop-05-t-manual.png index 9776596f..01af3756 100644 Binary files a/MacawTests/png/color-prop-05-t-manual.png and b/MacawTests/png/color-prop-05-t-manual.png differ diff --git a/MacawTests/png/coords-coord-01-t-manual.png b/MacawTests/png/coords-coord-01-t-manual.png index 746438f1..a8d7ea9d 100644 Binary files a/MacawTests/png/coords-coord-01-t-manual.png and b/MacawTests/png/coords-coord-01-t-manual.png differ diff --git a/MacawTests/png/coords-coord-02-t-manual.png b/MacawTests/png/coords-coord-02-t-manual.png index 915d9eaa..00f7cad7 100644 Binary files a/MacawTests/png/coords-coord-02-t-manual.png and b/MacawTests/png/coords-coord-02-t-manual.png differ diff --git a/MacawTests/png/coords-trans-01-b-manual.png b/MacawTests/png/coords-trans-01-b-manual.png index 4b9a607a..a0ab8d97 100644 Binary files a/MacawTests/png/coords-trans-01-b-manual.png and b/MacawTests/png/coords-trans-01-b-manual.png differ diff --git a/MacawTests/png/coords-trans-02-t-manual.png b/MacawTests/png/coords-trans-02-t-manual.png index c97ab20f..974af5ff 100644 Binary files a/MacawTests/png/coords-trans-02-t-manual.png and b/MacawTests/png/coords-trans-02-t-manual.png differ diff --git a/MacawTests/png/coords-trans-03-t-manual.png b/MacawTests/png/coords-trans-03-t-manual.png index 3d271622..552630a3 100644 Binary files a/MacawTests/png/coords-trans-03-t-manual.png and b/MacawTests/png/coords-trans-03-t-manual.png differ diff --git a/MacawTests/png/coords-trans-04-t-manual.png b/MacawTests/png/coords-trans-04-t-manual.png index 6eff7be7..c3af41b2 100644 Binary files a/MacawTests/png/coords-trans-04-t-manual.png and b/MacawTests/png/coords-trans-04-t-manual.png differ diff --git a/MacawTests/png/coords-trans-05-t-manual.png b/MacawTests/png/coords-trans-05-t-manual.png index 03e06d9e..1a57aac2 100644 Binary files a/MacawTests/png/coords-trans-05-t-manual.png and b/MacawTests/png/coords-trans-05-t-manual.png differ diff --git a/MacawTests/png/coords-trans-06-t-manual.png b/MacawTests/png/coords-trans-06-t-manual.png index d3eb1a7f..1970f147 100644 Binary files a/MacawTests/png/coords-trans-06-t-manual.png and b/MacawTests/png/coords-trans-06-t-manual.png differ diff --git a/MacawTests/png/coords-trans-07-t-manual.png b/MacawTests/png/coords-trans-07-t-manual.png index e831309e..855b5a6b 100644 Binary files a/MacawTests/png/coords-trans-07-t-manual.png and b/MacawTests/png/coords-trans-07-t-manual.png differ diff --git a/MacawTests/png/coords-trans-08-t-manual.png b/MacawTests/png/coords-trans-08-t-manual.png index a923c516..dd205d85 100644 Binary files a/MacawTests/png/coords-trans-08-t-manual.png and b/MacawTests/png/coords-trans-08-t-manual.png differ diff --git a/MacawTests/png/coords-trans-09-t-manual.png b/MacawTests/png/coords-trans-09-t-manual.png index d06857e8..157deb53 100644 Binary files a/MacawTests/png/coords-trans-09-t-manual.png and b/MacawTests/png/coords-trans-09-t-manual.png differ diff --git a/MacawTests/png/coords-trans-10-f-manual.png b/MacawTests/png/coords-trans-10-f-manual.png index 182b4977..e1dcd8bf 100644 Binary files a/MacawTests/png/coords-trans-10-f-manual.png and b/MacawTests/png/coords-trans-10-f-manual.png differ diff --git a/MacawTests/png/coords-trans-11-f-manual.png b/MacawTests/png/coords-trans-11-f-manual.png index 6081e00f..15b53707 100644 Binary files a/MacawTests/png/coords-trans-11-f-manual.png and b/MacawTests/png/coords-trans-11-f-manual.png differ diff --git a/MacawTests/png/coords-trans-12-f-manual.png b/MacawTests/png/coords-trans-12-f-manual.png index 74ab8aac..b030a3b8 100644 Binary files a/MacawTests/png/coords-trans-12-f-manual.png and b/MacawTests/png/coords-trans-12-f-manual.png differ diff --git a/MacawTests/png/coords-trans-13-f-manual.png b/MacawTests/png/coords-trans-13-f-manual.png index 656f1534..f80c052a 100644 Binary files a/MacawTests/png/coords-trans-13-f-manual.png and b/MacawTests/png/coords-trans-13-f-manual.png differ diff --git a/MacawTests/png/coords-trans-14-f-manual.png b/MacawTests/png/coords-trans-14-f-manual.png index a8e1e62d..5a805890 100644 Binary files a/MacawTests/png/coords-trans-14-f-manual.png and b/MacawTests/png/coords-trans-14-f-manual.png differ diff --git a/MacawTests/png/coords-transformattr-01-f-manual.png b/MacawTests/png/coords-transformattr-01-f-manual.png index 8585ff7b..7abc321d 100644 Binary files a/MacawTests/png/coords-transformattr-01-f-manual.png and b/MacawTests/png/coords-transformattr-01-f-manual.png differ diff --git a/MacawTests/png/coords-transformattr-02-f-manual.png b/MacawTests/png/coords-transformattr-02-f-manual.png index f20af4b3..451d9307 100644 Binary files a/MacawTests/png/coords-transformattr-02-f-manual.png and b/MacawTests/png/coords-transformattr-02-f-manual.png differ diff --git a/MacawTests/png/coords-transformattr-03-f-manual.png b/MacawTests/png/coords-transformattr-03-f-manual.png index 6346bbd8..d2f89540 100644 Binary files a/MacawTests/png/coords-transformattr-03-f-manual.png and b/MacawTests/png/coords-transformattr-03-f-manual.png differ diff --git a/MacawTests/png/coords-transformattr-04-f-manual.png b/MacawTests/png/coords-transformattr-04-f-manual.png index 91f65991..2c5ba78e 100644 Binary files a/MacawTests/png/coords-transformattr-04-f-manual.png and b/MacawTests/png/coords-transformattr-04-f-manual.png differ diff --git a/MacawTests/png/coords-transformattr-05-f-manual.png b/MacawTests/png/coords-transformattr-05-f-manual.png index 516264cb..7c952c80 100644 Binary files a/MacawTests/png/coords-transformattr-05-f-manual.png and b/MacawTests/png/coords-transformattr-05-f-manual.png differ diff --git a/MacawTests/png/masking-filter-01-f-manual.png b/MacawTests/png/masking-filter-01-f-manual.png index 95d986a5..859ee50b 100644 Binary files a/MacawTests/png/masking-filter-01-f-manual.png and b/MacawTests/png/masking-filter-01-f-manual.png differ diff --git a/MacawTests/png/masking-intro-01-f-manual.png b/MacawTests/png/masking-intro-01-f-manual.png index 914d8c3c..902f1e95 100644 Binary files a/MacawTests/png/masking-intro-01-f-manual.png and b/MacawTests/png/masking-intro-01-f-manual.png differ diff --git a/MacawTests/png/masking-mask-02-f-manual.png b/MacawTests/png/masking-mask-02-f-manual.png index c32fb1f3..f280bfd6 100644 Binary files a/MacawTests/png/masking-mask-02-f-manual.png and b/MacawTests/png/masking-mask-02-f-manual.png differ diff --git a/MacawTests/png/masking-path-02-b-manual.png b/MacawTests/png/masking-path-02-b-manual.png index 6d9fc8cc..28fd9caf 100644 Binary files a/MacawTests/png/masking-path-02-b-manual.png and b/MacawTests/png/masking-path-02-b-manual.png differ diff --git a/MacawTests/png/masking-path-13-f-manual.png b/MacawTests/png/masking-path-13-f-manual.png index e8b84a82..cb284ea1 100644 Binary files a/MacawTests/png/masking-path-13-f-manual.png and b/MacawTests/png/masking-path-13-f-manual.png differ diff --git a/MacawTests/png/metadata-example-01-t-manual.png b/MacawTests/png/metadata-example-01-t-manual.png index c883e4bb..e00f35aa 100644 Binary files a/MacawTests/png/metadata-example-01-t-manual.png and b/MacawTests/png/metadata-example-01-t-manual.png differ diff --git a/MacawTests/png/painting-control-01-f-manual.png b/MacawTests/png/painting-control-01-f-manual.png index a6753fb6..26089211 100644 Binary files a/MacawTests/png/painting-control-01-f-manual.png and b/MacawTests/png/painting-control-01-f-manual.png differ diff --git a/MacawTests/png/painting-control-02-f-manual.png b/MacawTests/png/painting-control-02-f-manual.png index 88af569a..9a7a816f 100644 Binary files a/MacawTests/png/painting-control-02-f-manual.png and b/MacawTests/png/painting-control-02-f-manual.png differ diff --git a/MacawTests/png/painting-control-03-f-manual.png b/MacawTests/png/painting-control-03-f-manual.png index 40dcd0f6..e8774fd1 100644 Binary files a/MacawTests/png/painting-control-03-f-manual.png and b/MacawTests/png/painting-control-03-f-manual.png differ diff --git a/MacawTests/png/painting-control-06-f-manual.png b/MacawTests/png/painting-control-06-f-manual.png index 219170cc..547b1434 100644 Binary files a/MacawTests/png/painting-control-06-f-manual.png and b/MacawTests/png/painting-control-06-f-manual.png differ diff --git a/MacawTests/png/painting-fill-01-t-manual.png b/MacawTests/png/painting-fill-01-t-manual.png index 6fdee5aa..37d49dd8 100644 Binary files a/MacawTests/png/painting-fill-01-t-manual.png and b/MacawTests/png/painting-fill-01-t-manual.png differ diff --git a/MacawTests/png/painting-fill-02-t-manual.png b/MacawTests/png/painting-fill-02-t-manual.png index 33fbbd35..30ba74e5 100644 Binary files a/MacawTests/png/painting-fill-02-t-manual.png and b/MacawTests/png/painting-fill-02-t-manual.png differ diff --git a/MacawTests/png/painting-fill-03-t-manual.png b/MacawTests/png/painting-fill-03-t-manual.png index 7898d5ee..5436f66e 100644 Binary files a/MacawTests/png/painting-fill-03-t-manual.png and b/MacawTests/png/painting-fill-03-t-manual.png differ diff --git a/MacawTests/png/painting-fill-04-t-manual.png b/MacawTests/png/painting-fill-04-t-manual.png index e62a0fae..baa6f9c2 100644 Binary files a/MacawTests/png/painting-fill-04-t-manual.png and b/MacawTests/png/painting-fill-04-t-manual.png differ diff --git a/MacawTests/png/painting-fill-05-b-manual.png b/MacawTests/png/painting-fill-05-b-manual.png index a42c7f98..9a464b02 100644 Binary files a/MacawTests/png/painting-fill-05-b-manual.png and b/MacawTests/png/painting-fill-05-b-manual.png differ diff --git a/MacawTests/png/painting-stroke-01-t-manual.png b/MacawTests/png/painting-stroke-01-t-manual.png index 031cdec6..2785ed53 100644 Binary files a/MacawTests/png/painting-stroke-01-t-manual.png and b/MacawTests/png/painting-stroke-01-t-manual.png differ diff --git a/MacawTests/png/painting-stroke-02-t-manual.png b/MacawTests/png/painting-stroke-02-t-manual.png index 83c23998..9428b4ac 100644 Binary files a/MacawTests/png/painting-stroke-02-t-manual.png and b/MacawTests/png/painting-stroke-02-t-manual.png differ diff --git a/MacawTests/png/painting-stroke-03-t-manual.png b/MacawTests/png/painting-stroke-03-t-manual.png index 22e17166..e2c18f4a 100644 Binary files a/MacawTests/png/painting-stroke-03-t-manual.png and b/MacawTests/png/painting-stroke-03-t-manual.png differ diff --git a/MacawTests/png/painting-stroke-04-t-manual.png b/MacawTests/png/painting-stroke-04-t-manual.png index 94703bd7..631b548f 100644 Binary files a/MacawTests/png/painting-stroke-04-t-manual.png and b/MacawTests/png/painting-stroke-04-t-manual.png differ diff --git a/MacawTests/png/painting-stroke-05-t-manual.png b/MacawTests/png/painting-stroke-05-t-manual.png index fd9f37d5..4f233669 100644 Binary files a/MacawTests/png/painting-stroke-05-t-manual.png and b/MacawTests/png/painting-stroke-05-t-manual.png differ diff --git a/MacawTests/png/painting-stroke-06-t-manual.png b/MacawTests/png/painting-stroke-06-t-manual.png index 679fec11..99bab928 100644 Binary files a/MacawTests/png/painting-stroke-06-t-manual.png and b/MacawTests/png/painting-stroke-06-t-manual.png differ diff --git a/MacawTests/png/painting-stroke-07-t-manual.png b/MacawTests/png/painting-stroke-07-t-manual.png index 48b5faa6..fa9d194a 100644 Binary files a/MacawTests/png/painting-stroke-07-t-manual.png and b/MacawTests/png/painting-stroke-07-t-manual.png differ diff --git a/MacawTests/png/painting-stroke-08-t-manual.png b/MacawTests/png/painting-stroke-08-t-manual.png index 1fd52f57..bc901e4d 100644 Binary files a/MacawTests/png/painting-stroke-08-t-manual.png and b/MacawTests/png/painting-stroke-08-t-manual.png differ diff --git a/MacawTests/png/painting-stroke-09-t-manual.png b/MacawTests/png/painting-stroke-09-t-manual.png index 1d8b13c7..c09a4fbb 100644 Binary files a/MacawTests/png/painting-stroke-09-t-manual.png and b/MacawTests/png/painting-stroke-09-t-manual.png differ diff --git a/MacawTests/png/paths-data-01-t-manual.png b/MacawTests/png/paths-data-01-t-manual.png index aa24bd62..6e54bbd6 100644 Binary files a/MacawTests/png/paths-data-01-t-manual.png and b/MacawTests/png/paths-data-01-t-manual.png differ diff --git a/MacawTests/png/paths-data-02-t-manual.png b/MacawTests/png/paths-data-02-t-manual.png index 8063f7c1..5962766b 100644 Binary files a/MacawTests/png/paths-data-02-t-manual.png and b/MacawTests/png/paths-data-02-t-manual.png differ diff --git a/MacawTests/png/paths-data-03-f-manual.png b/MacawTests/png/paths-data-03-f-manual.png index df532e00..8a4c81c4 100644 Binary files a/MacawTests/png/paths-data-03-f-manual.png and b/MacawTests/png/paths-data-03-f-manual.png differ diff --git a/MacawTests/png/paths-data-04-t-manual.png b/MacawTests/png/paths-data-04-t-manual.png index c1b711ed..0a369f58 100644 Binary files a/MacawTests/png/paths-data-04-t-manual.png and b/MacawTests/png/paths-data-04-t-manual.png differ diff --git a/MacawTests/png/paths-data-05-t-manual.png b/MacawTests/png/paths-data-05-t-manual.png index aab64b38..90cdd129 100644 Binary files a/MacawTests/png/paths-data-05-t-manual.png and b/MacawTests/png/paths-data-05-t-manual.png differ diff --git a/MacawTests/png/paths-data-06-t-manual.png b/MacawTests/png/paths-data-06-t-manual.png index f4e064ca..338005e0 100644 Binary files a/MacawTests/png/paths-data-06-t-manual.png and b/MacawTests/png/paths-data-06-t-manual.png differ diff --git a/MacawTests/png/paths-data-07-t-manual.png b/MacawTests/png/paths-data-07-t-manual.png index 5fa93528..07829cd6 100644 Binary files a/MacawTests/png/paths-data-07-t-manual.png and b/MacawTests/png/paths-data-07-t-manual.png differ diff --git a/MacawTests/png/paths-data-08-t-manual.png b/MacawTests/png/paths-data-08-t-manual.png index fa565060..e11717d0 100644 Binary files a/MacawTests/png/paths-data-08-t-manual.png and b/MacawTests/png/paths-data-08-t-manual.png differ diff --git a/MacawTests/png/paths-data-09-t-manual.png b/MacawTests/png/paths-data-09-t-manual.png index c34db20e..345f5d98 100644 Binary files a/MacawTests/png/paths-data-09-t-manual.png and b/MacawTests/png/paths-data-09-t-manual.png differ diff --git a/MacawTests/png/paths-data-10-t-manual.png b/MacawTests/png/paths-data-10-t-manual.png index 98f493e7..192bbbb1 100644 Binary files a/MacawTests/png/paths-data-10-t-manual.png and b/MacawTests/png/paths-data-10-t-manual.png differ diff --git a/MacawTests/png/paths-data-12-t-manual.png b/MacawTests/png/paths-data-12-t-manual.png index 56f1576d..39a9396f 100644 Binary files a/MacawTests/png/paths-data-12-t-manual.png and b/MacawTests/png/paths-data-12-t-manual.png differ diff --git a/MacawTests/png/paths-data-13-t-manual.png b/MacawTests/png/paths-data-13-t-manual.png index 26e12c20..bc7f9844 100644 Binary files a/MacawTests/png/paths-data-13-t-manual.png and b/MacawTests/png/paths-data-13-t-manual.png differ diff --git a/MacawTests/png/paths-data-14-t-manual.png b/MacawTests/png/paths-data-14-t-manual.png index 8a09fd63..63c7c493 100644 Binary files a/MacawTests/png/paths-data-14-t-manual.png and b/MacawTests/png/paths-data-14-t-manual.png differ diff --git a/MacawTests/png/paths-data-15-t-manual.png b/MacawTests/png/paths-data-15-t-manual.png index fa320096..d6b4a4d1 100644 Binary files a/MacawTests/png/paths-data-15-t-manual.png and b/MacawTests/png/paths-data-15-t-manual.png differ diff --git a/MacawTests/png/paths-data-16-t-manual.png b/MacawTests/png/paths-data-16-t-manual.png index 2be8c805..77fd8527 100644 Binary files a/MacawTests/png/paths-data-16-t-manual.png and b/MacawTests/png/paths-data-16-t-manual.png differ diff --git a/MacawTests/png/paths-data-17-f-manual.png b/MacawTests/png/paths-data-17-f-manual.png index ef60ca0f..c6ef1bc8 100644 Binary files a/MacawTests/png/paths-data-17-f-manual.png and b/MacawTests/png/paths-data-17-f-manual.png differ diff --git a/MacawTests/png/paths-data-18-f-manual.png b/MacawTests/png/paths-data-18-f-manual.png index 7866f214..1fd8a9ea 100644 Binary files a/MacawTests/png/paths-data-18-f-manual.png and b/MacawTests/png/paths-data-18-f-manual.png differ diff --git a/MacawTests/png/paths-data-19-f-manual.png b/MacawTests/png/paths-data-19-f-manual.png index 3bfcb57f..a6e1e6e1 100644 Binary files a/MacawTests/png/paths-data-19-f-manual.png and b/MacawTests/png/paths-data-19-f-manual.png differ diff --git a/MacawTests/png/paths-data-20-f-manual.png b/MacawTests/png/paths-data-20-f-manual.png index 657af750..304ba1e8 100644 Binary files a/MacawTests/png/paths-data-20-f-manual.png and b/MacawTests/png/paths-data-20-f-manual.png differ diff --git a/MacawTests/png/pservers-grad-01-b-manual.png b/MacawTests/png/pservers-grad-01-b-manual.png index 921d16f5..e6d78ed2 100644 Binary files a/MacawTests/png/pservers-grad-01-b-manual.png and b/MacawTests/png/pservers-grad-01-b-manual.png differ diff --git a/MacawTests/png/pservers-grad-02-b-manual.png b/MacawTests/png/pservers-grad-02-b-manual.png index b544f0fe..174659ea 100644 Binary files a/MacawTests/png/pservers-grad-02-b-manual.png and b/MacawTests/png/pservers-grad-02-b-manual.png differ diff --git a/MacawTests/png/pservers-grad-03-b-manual.png b/MacawTests/png/pservers-grad-03-b-manual.png index 3acf5107..c4bc31ac 100644 Binary files a/MacawTests/png/pservers-grad-03-b-manual.png and b/MacawTests/png/pservers-grad-03-b-manual.png differ diff --git a/MacawTests/png/pservers-grad-07-b-manual.png b/MacawTests/png/pservers-grad-07-b-manual.png index c508c90b..262bbabe 100644 Binary files a/MacawTests/png/pservers-grad-07-b-manual.png and b/MacawTests/png/pservers-grad-07-b-manual.png differ diff --git a/MacawTests/png/pservers-grad-09-b-manual.png b/MacawTests/png/pservers-grad-09-b-manual.png index 78f0b8b8..6baf64a4 100644 Binary files a/MacawTests/png/pservers-grad-09-b-manual.png and b/MacawTests/png/pservers-grad-09-b-manual.png differ diff --git a/MacawTests/png/pservers-grad-12-b-manual.png b/MacawTests/png/pservers-grad-12-b-manual.png index 5d86eb69..39fa6efc 100644 Binary files a/MacawTests/png/pservers-grad-12-b-manual.png and b/MacawTests/png/pservers-grad-12-b-manual.png differ diff --git a/MacawTests/png/pservers-grad-13-b-manual.png b/MacawTests/png/pservers-grad-13-b-manual.png index 98c685c2..293bb99f 100644 Binary files a/MacawTests/png/pservers-grad-13-b-manual.png and b/MacawTests/png/pservers-grad-13-b-manual.png differ diff --git a/MacawTests/png/pservers-grad-15-b-manual.png b/MacawTests/png/pservers-grad-15-b-manual.png index b22cf0b0..280a922c 100644 Binary files a/MacawTests/png/pservers-grad-15-b-manual.png and b/MacawTests/png/pservers-grad-15-b-manual.png differ diff --git a/MacawTests/png/pservers-grad-22-b-manual.png b/MacawTests/png/pservers-grad-22-b-manual.png index 6f645ab4..e44dd6cf 100644 Binary files a/MacawTests/png/pservers-grad-22-b-manual.png and b/MacawTests/png/pservers-grad-22-b-manual.png differ diff --git a/MacawTests/png/pservers-grad-23-f-manual.png b/MacawTests/png/pservers-grad-23-f-manual.png index 7f08d5a1..29273bd9 100644 Binary files a/MacawTests/png/pservers-grad-23-f-manual.png and b/MacawTests/png/pservers-grad-23-f-manual.png differ diff --git a/MacawTests/png/pservers-grad-24-f-manual.png b/MacawTests/png/pservers-grad-24-f-manual.png index 4b3237c8..6d35f59c 100644 Binary files a/MacawTests/png/pservers-grad-24-f-manual.png and b/MacawTests/png/pservers-grad-24-f-manual.png differ diff --git a/MacawTests/png/pservers-grad-stops-01-f-manual.png b/MacawTests/png/pservers-grad-stops-01-f-manual.png index 398512e8..f3b698ad 100644 Binary files a/MacawTests/png/pservers-grad-stops-01-f-manual.png and b/MacawTests/png/pservers-grad-stops-01-f-manual.png differ diff --git a/MacawTests/png/render-elems-01-t-manual.png b/MacawTests/png/render-elems-01-t-manual.png index 2bde5dd8..0e92cfc9 100644 Binary files a/MacawTests/png/render-elems-01-t-manual.png and b/MacawTests/png/render-elems-01-t-manual.png differ diff --git a/MacawTests/png/render-elems-02-t-manual.png b/MacawTests/png/render-elems-02-t-manual.png index 6b376051..7e4671a3 100644 Binary files a/MacawTests/png/render-elems-02-t-manual.png and b/MacawTests/png/render-elems-02-t-manual.png differ diff --git a/MacawTests/png/render-elems-03-t-manual.png b/MacawTests/png/render-elems-03-t-manual.png index 102cc761..ecd43d29 100644 Binary files a/MacawTests/png/render-elems-03-t-manual.png and b/MacawTests/png/render-elems-03-t-manual.png differ diff --git a/MacawTests/png/shapes-circle-01-t-manual.png b/MacawTests/png/shapes-circle-01-t-manual.png index 27f044c1..b4b0c8ab 100644 Binary files a/MacawTests/png/shapes-circle-01-t-manual.png and b/MacawTests/png/shapes-circle-01-t-manual.png differ diff --git a/MacawTests/png/shapes-circle-02-t-manual.png b/MacawTests/png/shapes-circle-02-t-manual.png index e60fb418..96eceb72 100644 Binary files a/MacawTests/png/shapes-circle-02-t-manual.png and b/MacawTests/png/shapes-circle-02-t-manual.png differ diff --git a/MacawTests/png/shapes-ellipse-01-t-manual.png b/MacawTests/png/shapes-ellipse-01-t-manual.png index c7f18ea9..899e248b 100644 Binary files a/MacawTests/png/shapes-ellipse-01-t-manual.png and b/MacawTests/png/shapes-ellipse-01-t-manual.png differ diff --git a/MacawTests/png/shapes-ellipse-02-t-manual.png b/MacawTests/png/shapes-ellipse-02-t-manual.png index bd9e13ec..b22c349d 100644 Binary files a/MacawTests/png/shapes-ellipse-02-t-manual.png and b/MacawTests/png/shapes-ellipse-02-t-manual.png differ diff --git a/MacawTests/png/shapes-ellipse-03-f-manual.png b/MacawTests/png/shapes-ellipse-03-f-manual.png index ba7377c2..d74421b6 100644 Binary files a/MacawTests/png/shapes-ellipse-03-f-manual.png and b/MacawTests/png/shapes-ellipse-03-f-manual.png differ diff --git a/MacawTests/png/shapes-grammar-01-f-manual.png b/MacawTests/png/shapes-grammar-01-f-manual.png index 0e3e5ceb..fcb9abde 100644 Binary files a/MacawTests/png/shapes-grammar-01-f-manual.png and b/MacawTests/png/shapes-grammar-01-f-manual.png differ diff --git a/MacawTests/png/shapes-intro-01-t-manual.png b/MacawTests/png/shapes-intro-01-t-manual.png index efc010c0..98bae9dd 100644 Binary files a/MacawTests/png/shapes-intro-01-t-manual.png and b/MacawTests/png/shapes-intro-01-t-manual.png differ diff --git a/MacawTests/png/shapes-line-01-t-manual.png b/MacawTests/png/shapes-line-01-t-manual.png index e992c6a3..2b229cab 100644 Binary files a/MacawTests/png/shapes-line-01-t-manual.png and b/MacawTests/png/shapes-line-01-t-manual.png differ diff --git a/MacawTests/png/shapes-line-02-f-manual.png b/MacawTests/png/shapes-line-02-f-manual.png index 44328a0c..d6361ca5 100644 Binary files a/MacawTests/png/shapes-line-02-f-manual.png and b/MacawTests/png/shapes-line-02-f-manual.png differ diff --git a/MacawTests/png/shapes-polygon-01-t-manual.png b/MacawTests/png/shapes-polygon-01-t-manual.png index 175066cf..c2e8ca73 100644 Binary files a/MacawTests/png/shapes-polygon-01-t-manual.png and b/MacawTests/png/shapes-polygon-01-t-manual.png differ diff --git a/MacawTests/png/shapes-polygon-02-t-manual.png b/MacawTests/png/shapes-polygon-02-t-manual.png index 4b6f9a96..d5cdf0b4 100644 Binary files a/MacawTests/png/shapes-polygon-02-t-manual.png and b/MacawTests/png/shapes-polygon-02-t-manual.png differ diff --git a/MacawTests/png/shapes-polygon-03-t-manual.png b/MacawTests/png/shapes-polygon-03-t-manual.png index 7a74aeec..208b4a10 100644 Binary files a/MacawTests/png/shapes-polygon-03-t-manual.png and b/MacawTests/png/shapes-polygon-03-t-manual.png differ diff --git a/MacawTests/png/shapes-polyline-01-t-manual.png b/MacawTests/png/shapes-polyline-01-t-manual.png index 86d07718..b0266a7e 100644 Binary files a/MacawTests/png/shapes-polyline-01-t-manual.png and b/MacawTests/png/shapes-polyline-01-t-manual.png differ diff --git a/MacawTests/png/shapes-polyline-02-t-manual.png b/MacawTests/png/shapes-polyline-02-t-manual.png index 3f539981..9db8761d 100644 Binary files a/MacawTests/png/shapes-polyline-02-t-manual.png and b/MacawTests/png/shapes-polyline-02-t-manual.png differ diff --git a/MacawTests/png/shapes-rect-02-t-manual.png b/MacawTests/png/shapes-rect-02-t-manual.png index 4963f0ee..d414a6d4 100644 Binary files a/MacawTests/png/shapes-rect-02-t-manual.png and b/MacawTests/png/shapes-rect-02-t-manual.png differ diff --git a/MacawTests/png/shapes-rect-03-t-manual.png b/MacawTests/png/shapes-rect-03-t-manual.png index 9f82d28a..e6b32d81 100644 Binary files a/MacawTests/png/shapes-rect-03-t-manual.png and b/MacawTests/png/shapes-rect-03-t-manual.png differ diff --git a/MacawTests/png/shapes-rect-04-f-manual.png b/MacawTests/png/shapes-rect-04-f-manual.png index 7491a3cc..bf8ce5ab 100644 Binary files a/MacawTests/png/shapes-rect-04-f-manual.png and b/MacawTests/png/shapes-rect-04-f-manual.png differ diff --git a/MacawTests/png/shapes-rect-05-f-manual.png b/MacawTests/png/shapes-rect-05-f-manual.png index e02d9f0a..27a16b2d 100644 Binary files a/MacawTests/png/shapes-rect-05-f-manual.png and b/MacawTests/png/shapes-rect-05-f-manual.png differ diff --git a/MacawTests/png/shapes-rect-06-f-manual.png b/MacawTests/png/shapes-rect-06-f-manual.png index b37be75b..b1b932d1 100644 Binary files a/MacawTests/png/shapes-rect-06-f-manual.png and b/MacawTests/png/shapes-rect-06-f-manual.png differ diff --git a/MacawTests/png/shapes-rect-07-f-manual.png b/MacawTests/png/shapes-rect-07-f-manual.png index 21bb1767..181cdadf 100644 Binary files a/MacawTests/png/shapes-rect-07-f-manual.png and b/MacawTests/png/shapes-rect-07-f-manual.png differ diff --git a/MacawTests/png/struct-defs-01-t-manual.png b/MacawTests/png/struct-defs-01-t-manual.png index 128f6c58..0dfd2e7a 100644 Binary files a/MacawTests/png/struct-defs-01-t-manual.png and b/MacawTests/png/struct-defs-01-t-manual.png differ diff --git a/MacawTests/png/struct-frag-02-t-manual.png b/MacawTests/png/struct-frag-02-t-manual.png index 2f666a43..d7d13cc6 100644 Binary files a/MacawTests/png/struct-frag-02-t-manual.png and b/MacawTests/png/struct-frag-02-t-manual.png differ diff --git a/MacawTests/png/struct-frag-03-t-manual.png b/MacawTests/png/struct-frag-03-t-manual.png index 0016f673..1f77fead 100644 Binary files a/MacawTests/png/struct-frag-03-t-manual.png and b/MacawTests/png/struct-frag-03-t-manual.png differ diff --git a/MacawTests/png/struct-frag-04-t-manual.png b/MacawTests/png/struct-frag-04-t-manual.png index 37ba8104..9348a242 100644 Binary files a/MacawTests/png/struct-frag-04-t-manual.png and b/MacawTests/png/struct-frag-04-t-manual.png differ diff --git a/MacawTests/png/struct-frag-06-t-manual.png b/MacawTests/png/struct-frag-06-t-manual.png index b8129466..6518840e 100644 Binary files a/MacawTests/png/struct-frag-06-t-manual.png and b/MacawTests/png/struct-frag-06-t-manual.png differ diff --git a/MacawTests/png/struct-group-01-t-manual.png b/MacawTests/png/struct-group-01-t-manual.png index ecefe94c..262045dd 100644 Binary files a/MacawTests/png/struct-group-01-t-manual.png and b/MacawTests/png/struct-group-01-t-manual.png differ diff --git a/MacawTests/png/struct-use-01-t-manual.png b/MacawTests/png/struct-use-01-t-manual.png index e9bfffe9..e8136bcb 100644 Binary files a/MacawTests/png/struct-use-01-t-manual.png and b/MacawTests/png/struct-use-01-t-manual.png differ diff --git a/MacawTests/png/struct-use-03-t-manual.png b/MacawTests/png/struct-use-03-t-manual.png index 58aaa97e..18a3bb36 100644 Binary files a/MacawTests/png/struct-use-03-t-manual.png and b/MacawTests/png/struct-use-03-t-manual.png differ diff --git a/MacawTests/png/struct-use-12-f-manual.png b/MacawTests/png/struct-use-12-f-manual.png index 7f530970..7a813e42 100644 Binary files a/MacawTests/png/struct-use-12-f-manual.png and b/MacawTests/png/struct-use-12-f-manual.png differ diff --git a/MacawTests/png/text-align-01-b-manual.png b/MacawTests/png/text-align-01-b-manual.png index e16f13f7..a059137d 100644 Binary files a/MacawTests/png/text-align-01-b-manual.png and b/MacawTests/png/text-align-01-b-manual.png differ diff --git a/MacawTests/png/text-fonts-01-t-manual.png b/MacawTests/png/text-fonts-01-t-manual.png index d13ff5b9..6dcfa4d9 100644 Binary files a/MacawTests/png/text-fonts-01-t-manual.png and b/MacawTests/png/text-fonts-01-t-manual.png differ diff --git a/MacawTests/png/text-fonts-02-t-manual.png b/MacawTests/png/text-fonts-02-t-manual.png index 011dac33..2d8379b1 100644 Binary files a/MacawTests/png/text-fonts-02-t-manual.png and b/MacawTests/png/text-fonts-02-t-manual.png differ diff --git a/MacawTests/png/types-basic-01-f-manual.png b/MacawTests/png/types-basic-01-f-manual.png index 5682a2b3..8c56631c 100644 Binary files a/MacawTests/png/types-basic-01-f-manual.png and b/MacawTests/png/types-basic-01-f-manual.png differ diff --git a/MacawTests/svg/textBasicTransform.reference b/MacawTests/svg/textBasicTransform.reference index f8c09e0a..1faa17c8 100644 --- a/MacawTests/svg/textBasicTransform.reference +++ b/MacawTests/svg/textBasicTransform.reference @@ -1 +1 @@ -Point +Point \ No newline at end of file diff --git a/MacawTests/w3cSVGTests/coords-trans-05-t-manual.reference b/MacawTests/w3cSVGTests/coords-trans-05-t-manual.reference index b257fd9d..477448c5 100644 --- a/MacawTests/w3cSVGTests/coords-trans-05-t-manual.reference +++ b/MacawTests/w3cSVGTests/coords-trans-05-t-manual.reference @@ -40,7 +40,7 @@ } ], "node" : "Group", - "place" : "7.5, 0, 0, 5, 125.000002, 525" + "place" : "7.5, 0, 0, 5, 125.000003, 525" } ], "node" : "Group", diff --git a/MacawTests/w3cSVGTests/coords-trans-07-t-manual.reference b/MacawTests/w3cSVGTests/coords-trans-07-t-manual.reference index 76e5d517..5b7afca7 100644 --- a/MacawTests/w3cSVGTests/coords-trans-07-t-manual.reference +++ b/MacawTests/w3cSVGTests/coords-trans-07-t-manual.reference @@ -36,7 +36,7 @@ } ], "node" : "Group", - "place" : ".866025, .5, -.5, .866025, 123.205081, 186.60254" + "place" : "0.866025, 0.5, -0.5, 0.866025, 123.205081, 186.60254" }, { "align" : "min", @@ -86,7 +86,7 @@ } ], "node" : "Group", - "place" : ".866025, .5, -.5, .866025, 200, 100" + "place" : "0.866025, 0.5, -0.5, 0.866025, 200, 100" }, { "align" : "min", diff --git a/MacawTests/w3cSVGTests/coords-trans-09-t-manual.reference b/MacawTests/w3cSVGTests/coords-trans-09-t-manual.reference index f452cef4..d094312d 100644 --- a/MacawTests/w3cSVGTests/coords-trans-09-t-manual.reference +++ b/MacawTests/w3cSVGTests/coords-trans-09-t-manual.reference @@ -186,7 +186,7 @@ } ], "node" : "Group", - "place" : "1, 0, .5, 1, 30, 170" + "place" : "1, 0, 0.5, 1, 30, 170" }, { "align" : "min", @@ -236,7 +236,7 @@ } ], "node" : "Group", - "place" : "1, .5, 0, 1, 100, 200" + "place" : "1, 0.5, 0, 1, 100, 200" }, { "align" : "min", @@ -336,7 +336,7 @@ } ], "node" : "Group", - "place" : "1, .8, .8, 1, 300, 220" + "place" : "1, 0.8, 0.8, 1, 300, 220" }, { "align" : "min", diff --git a/MacawTests/w3cSVGTests/coords-trans-14-f-manual.reference b/MacawTests/w3cSVGTests/coords-trans-14-f-manual.reference index db9708f8..916da24c 100644 --- a/MacawTests/w3cSVGTests/coords-trans-14-f-manual.reference +++ b/MacawTests/w3cSVGTests/coords-trans-14-f-manual.reference @@ -564,7 +564,7 @@ } ], "node" : "Group", - "place" : ".704769, -.256515, .256515, .704769, 0, 0" + "place" : "0.704769, -0.256515, 0.256515, 0.704769, 0, 0" } ], "node" : "Group" diff --git a/MacawTests/w3cSVGTests/coords-transformattr-01-f-manual.reference b/MacawTests/w3cSVGTests/coords-transformattr-01-f-manual.reference index 7cea70d3..4560daa7 100644 --- a/MacawTests/w3cSVGTests/coords-transformattr-01-f-manual.reference +++ b/MacawTests/w3cSVGTests/coords-transformattr-01-f-manual.reference @@ -20,7 +20,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" }, { "contents" : [ @@ -52,7 +52,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" }, { "contents" : [ @@ -72,7 +72,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" }, { "contents" : [ @@ -104,7 +104,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" }, { "contents" : [ @@ -124,7 +124,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" }, { "contents" : [ @@ -156,7 +156,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" }, { "contents" : [ @@ -176,7 +176,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" }, { "contents" : [ @@ -208,7 +208,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" }, { "contents" : [ @@ -228,7 +228,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" }, { "contents" : [ @@ -260,7 +260,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" }, { "contents" : [ @@ -280,7 +280,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" }, { "contents" : [ @@ -312,7 +312,7 @@ } ], "node" : "Group", - "place" : ".565685, .565685, -.41411, .71726, 50, 50" + "place" : "0.565685, 0.565685, -0.41411, 0.71726, 50, 50" } ], "node" : "Group" diff --git a/MacawTests/w3cSVGTests/coords-transformattr-02-f-manual.reference b/MacawTests/w3cSVGTests/coords-transformattr-02-f-manual.reference index 3081d23b..a94821e5 100644 --- a/MacawTests/w3cSVGTests/coords-transformattr-02-f-manual.reference +++ b/MacawTests/w3cSVGTests/coords-transformattr-02-f-manual.reference @@ -130,7 +130,7 @@ } ], "node" : "Group", - "place" : ".707107, .707107, -.707107, .707107, 0, 0" + "place" : "0.707107, 0.707107, -0.707107, 0.707107, 0, 0" } ], "node" : "Group", @@ -209,7 +209,7 @@ } ], "node" : "Group", - "place" : ".707107, .707107, -.707107, .707107, 0, 0" + "place" : "0.707107, 0.707107, -0.707107, 0.707107, 0, 0" } ], "node" : "Group", diff --git a/MacawTests/w3cSVGTests/coords-transformattr-05-f-manual.reference b/MacawTests/w3cSVGTests/coords-transformattr-05-f-manual.reference index e11a73d1..ef8dfa66 100644 --- a/MacawTests/w3cSVGTests/coords-transformattr-05-f-manual.reference +++ b/MacawTests/w3cSVGTests/coords-transformattr-05-f-manual.reference @@ -106,7 +106,7 @@ } ], "node" : "Group", - "place" : ".965926, .258819, -.258819, .965926, 0, 0" + "place" : "0.965926, 0.258819, -0.258819, 0.965926, 0, 0" }, { "contents" : [ @@ -214,7 +214,7 @@ } ], "node" : "Group", - "place" : ".965926, .258819, -.258819, .965926, 0, 0" + "place" : "0.965926, 0.258819, -0.258819, 0.965926, 0, 0" } ], "node" : "Group", diff --git a/MacawTests/w3cSVGTests/masking-path-02-b-manual.reference b/MacawTests/w3cSVGTests/masking-path-02-b-manual.reference index 5e21d1e7..ad5b4a32 100644 --- a/MacawTests/w3cSVGTests/masking-path-02-b-manual.reference +++ b/MacawTests/w3cSVGTests/masking-path-02-b-manual.reference @@ -87,7 +87,7 @@ "y" : 0 }, "node" : "Shape", - "place" : ".707107, -.707107, .707107, .707107, 100, 200" + "place" : "0.707107, -0.707107, 0.707107, 0.707107, 100, 200" }, { "form" : { @@ -98,7 +98,7 @@ "y" : 60 }, "node" : "Shape", - "place" : ".707107, -.707107, .707107, .707107, 100, 200", + "place" : "0.707107, -0.707107, 0.707107, 0.707107, 100, 200", "stroke" : { "cap" : "butt", "dashes" : [ diff --git a/MacawTests/w3cSVGTests/metadata-example-01-t-manual.reference b/MacawTests/w3cSVGTests/metadata-example-01-t-manual.reference index acf016fe..bc33eed6 100644 --- a/MacawTests/w3cSVGTests/metadata-example-01-t-manual.reference +++ b/MacawTests/w3cSVGTests/metadata-example-01-t-manual.reference @@ -6424,7 +6424,7 @@ } ], "node" : "Group", - "place" : ".04455, -.0227, .0227, .04455, 153, 58" + "place" : "0.04455, -0.0227, 0.0227, 0.04455, 153, 58" } ], "node" : "Group", diff --git a/MacawTests/w3cSVGTests/pservers-grad-22-b-manual.reference b/MacawTests/w3cSVGTests/pservers-grad-22-b-manual.reference index 045538db..0ad861b4 100644 --- a/MacawTests/w3cSVGTests/pservers-grad-22-b-manual.reference +++ b/MacawTests/w3cSVGTests/pservers-grad-22-b-manual.reference @@ -18,7 +18,7 @@ } ], "node" : "Group", - "place" : ".5, 0, 0, 1, 0, 0" + "place" : "0.5, 0, 0, 1, 0, 0" } ], "node" : "Group", @@ -51,7 +51,7 @@ "y" : 0 }, "node" : "Shape", - "place" : ".5, 0, 0, 1, 0, 0" + "place" : "0.5, 0, 0, 1, 0, 0" } ], "node" : "Group", diff --git a/MacawTests/w3cSVGTests/shapes-ellipse-03-f-manual.reference b/MacawTests/w3cSVGTests/shapes-ellipse-03-f-manual.reference index 593a0eb8..6933b717 100644 --- a/MacawTests/w3cSVGTests/shapes-ellipse-03-f-manual.reference +++ b/MacawTests/w3cSVGTests/shapes-ellipse-03-f-manual.reference @@ -170,7 +170,7 @@ } ], "node" : "Group", - "place" : ".866025, -.5, .5, .866025, 350, 150" + "place" : "0.866025, -0.5, 0.5, 0.866025, 350, 150" } ], "node" : "Group" diff --git a/MacawTests/w3cSVGTests/shapes-line-02-f-manual.reference b/MacawTests/w3cSVGTests/shapes-line-02-f-manual.reference index b31c8195..32ea892b 100644 --- a/MacawTests/w3cSVGTests/shapes-line-02-f-manual.reference +++ b/MacawTests/w3cSVGTests/shapes-line-02-f-manual.reference @@ -41,7 +41,7 @@ "y2" : 100 }, "node" : "Shape", - "place" : ".965926, .258819, -.258819, .965926, 0, 0", + "place" : "0.965926, 0.258819, -0.258819, 0.965926, 0, 0", "stroke" : { "cap" : "butt", "dashes" : [ diff --git a/MacawTests/w3cSVGTests/shapes-rect-05-f-manual.reference b/MacawTests/w3cSVGTests/shapes-rect-05-f-manual.reference index 5384b245..5d780160 100644 --- a/MacawTests/w3cSVGTests/shapes-rect-05-f-manual.reference +++ b/MacawTests/w3cSVGTests/shapes-rect-05-f-manual.reference @@ -160,7 +160,7 @@ } ], "node" : "Group", - "place" : ".866025, -.5, .815207, .68404, 100, 100" + "place" : "0.866025, -0.5, 0.815207, 0.68404, 100, 100" } ], "node" : "Group" diff --git a/MacawTests/w3cSVGTests/struct-frag-06-t-manual.reference b/MacawTests/w3cSVGTests/struct-frag-06-t-manual.reference index 55fce126..4c8893fb 100644 --- a/MacawTests/w3cSVGTests/struct-frag-06-t-manual.reference +++ b/MacawTests/w3cSVGTests/struct-frag-06-t-manual.reference @@ -192,7 +192,7 @@ } ], "node" : "Group", - "place" : ".2, 0, 0, .2, 90, 235" + "place" : "0.2, 0, 0, 0.2, 90, 235" }, { "align" : "mid", @@ -296,7 +296,7 @@ } ], "node" : "Group", - "place" : ".2, 0, 0, .2, 190, 235" + "place" : "0.2, 0, 0, 0.2, 190, 235" } ], "node" : "Group" diff --git a/MacawTests/w3cSVGTests/struct-group-01-t-manual.reference b/MacawTests/w3cSVGTests/struct-group-01-t-manual.reference index 4814ac3e..461c2681 100644 --- a/MacawTests/w3cSVGTests/struct-group-01-t-manual.reference +++ b/MacawTests/w3cSVGTests/struct-group-01-t-manual.reference @@ -102,7 +102,7 @@ } ], "node" : "Group", - "place" : ".939693, -.34202, .34202, .939693, 0, 0" + "place" : "0.939693, -0.34202, 0.34202, 0.939693, 0, 0" } ], "node" : "Group" diff --git a/MacawTests/w3cSVGTests/struct-use-03-t-manual.reference b/MacawTests/w3cSVGTests/struct-use-03-t-manual.reference index edb66756..b7df32a3 100644 --- a/MacawTests/w3cSVGTests/struct-use-03-t-manual.reference +++ b/MacawTests/w3cSVGTests/struct-use-03-t-manual.reference @@ -64,7 +64,7 @@ } ], "node" : "Group", - "place" : ".707107, .707107, -.707107, .707107, 120, 99.289322" + "place" : "0.707107, 0.707107, -0.707107, 0.707107, 120, 99.289322" }, { "contents" : [ @@ -81,7 +81,7 @@ "y" : 0 }, "node" : "Shape", - "place" : ".707107, .707107, -.707107, .707107, 120, 99.289322", + "place" : "0.707107, 0.707107, -0.707107, 0.707107, 120, 99.289322", "stroke" : { "cap" : "butt", "dashes" : [ diff --git a/README.md b/README.md index f27a1ed6..701e28c3 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ We're working hard to provide full documentation. Currently you can take a look ## Requirements -* iOS 8.0+ +* iOS 9.0+ * Mac OS X 10.11+ * Xcode 7.3+ diff --git a/README_zh.md b/README_zh.md index b625267b..7024ce03 100644 --- a/README_zh.md +++ b/README_zh.md @@ -70,7 +70,7 @@ class MyView: MacawView { ## 系统要求 -* iOS 8.0+ +* iOS 9.0+ * Mac OS X 10.11+ * Xcode 7.3+ diff --git a/Source/animation/AnimationImpl.swift b/Source/animation/AnimationImpl.swift index 0b79032e..a6fca1ab 100644 --- a/Source/animation/AnimationImpl.swift +++ b/Source/animation/AnimationImpl.swift @@ -24,11 +24,8 @@ class BasicAnimation: Animation { weak var node: Node? { didSet { - node?.animations.append(self) - if let group = node as? Group { - for node in group.contents { - node.animations.append(self) - } + if !(self is CombineAnimation || self is AnimationSequence || self is EmptyAnimation) { + node?.animations.append(self) } } } @@ -104,6 +101,8 @@ class BasicAnimation: Animation { } removeFunc?() + node?.animations.removeAll { $0 === self } + nodeRenderer?.freeLayer() } override open func pause() { @@ -115,6 +114,8 @@ class BasicAnimation: Animation { } removeFunc?() + node?.animations.removeAll { $0 === self } + nodeRenderer?.freeLayer() } override func state() -> AnimationState { diff --git a/Source/animation/AnimationProducer.swift b/Source/animation/AnimationProducer.swift index e4fc0a3e..7ae54475 100644 --- a/Source/animation/AnimationProducer.swift +++ b/Source/animation/AnimationProducer.swift @@ -143,9 +143,9 @@ class AnimationProducer { } // MARK: - Sequence animation - func addAnimationSequence(_ animationSequnce: Animation, + func addAnimationSequence(_ animationSequence: Animation, _ context: AnimationContext) { - guard let sequence = animationSequnce as? AnimationSequence else { + guard let sequence = animationSequence as? AnimationSequence else { return } @@ -198,7 +198,7 @@ class AnimationProducer { } // MARK: - Stored animation - func addStoredAnimations(_ node: Node, _ view: MacawView) { + func addStoredAnimations(_ node: Node, _ view: DrawingView) { addStoredAnimations(node, AnimationContext()) } diff --git a/Source/animation/types/animation_generators/Cache/AnimationCache.swift b/Source/animation/types/animation_generators/Cache/AnimationCache.swift index d54b2c7a..0dbcb49b 100644 --- a/Source/animation/types/animation_generators/Cache/AnimationCache.swift +++ b/Source/animation/types/animation_generators/Cache/AnimationCache.swift @@ -8,7 +8,7 @@ import AppKit class AnimationUtils { - class func layerForNodeRenderer(_ renderer: NodeRenderer, _ context: AnimationContext, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer { + class func layerForNodeRenderer(_ renderer: NodeRenderer, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer { let node = renderer.node if let cachedLayer = renderer.layer { @@ -150,6 +150,9 @@ class AnimationUtils { } parent = parent?.parentRenderer } + if let viewPlace = renderer.view?.place { + uncachedParentsPlace = uncachedParentsPlace.concat(with: viewPlace) + } return uncachedParentsPlace } } diff --git a/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift b/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift index 7012323c..6815bcb0 100644 --- a/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift +++ b/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift @@ -115,8 +115,8 @@ extension AnimationProducer { // MARK: - Combine animation func addCombineAnimation(_ combineAnimation: Animation, _ context: AnimationContext) { guard let combine = combineAnimation as? CombineAnimation, - let renderer = combine.nodeRenderer, - let _ = renderer.view else { + let _ = combine.nodeRenderer, + let node = combine.node else { return } @@ -176,6 +176,7 @@ extension AnimationProducer { } combine.removeFunc = { + node.animations.removeAll { $0 === combine } animations.forEach { animation in animation.removeFunc?() } diff --git a/Source/animation/types/animation_generators/MorphingGenerator.swift b/Source/animation/types/animation_generators/MorphingGenerator.swift index 212cbd9d..b077c26b 100644 --- a/Source/animation/types/animation_generators/MorphingGenerator.swift +++ b/Source/animation/types/animation_generators/MorphingGenerator.swift @@ -30,7 +30,7 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte let toLocus = morphingAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0) let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration() - let layer = AnimationUtils.layerForNodeRenderer(renderer, context, animation: animation, shouldRenderContent: false) + let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false) // Creating proper animation let generatedAnimation = pathAnimation( @@ -94,6 +94,7 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte let animationId = animation.ID layer.add(generatedAnimation, forKey: animationId) animation.removeFunc = { [weak layer] in + shape.animations.removeAll { $0 === animation } layer?.removeAnimation(forKey: animationId) } diff --git a/Source/animation/types/animation_generators/OpacityGenerator.swift b/Source/animation/types/animation_generators/OpacityGenerator.swift index 71e9441c..dc58b087 100644 --- a/Source/animation/types/animation_generators/OpacityGenerator.swift +++ b/Source/animation/types/animation_generators/OpacityGenerator.swift @@ -47,6 +47,15 @@ func addOpacityAnimation(_ animation: BasicAnimation, _ context: AnimationContex node.opacityVar.value = opacityAnimation.getVFunc()(1.0) } + CATransaction.begin() + CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions) + renderer.layer?.animationLayer.opacity = Float(node.opacity) + CATransaction.commit() + + if !animation.paused { + animation.removeFunc?() + } + renderer.freeLayer() if !animation.cycled && @@ -58,10 +67,11 @@ func addOpacityAnimation(_ animation: BasicAnimation, _ context: AnimationContex completion() } - let layer = AnimationUtils.layerForNodeRenderer(renderer, context, animation: animation) + let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation) let animationId = animation.ID layer.add(generatedAnimation, forKey: animationId) animation.removeFunc = { [weak layer] in + node.animations.removeAll { $0 === animation } layer?.removeAnimation(forKey: animationId) } diff --git a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift index 2d3dd9dd..b64023ca 100644 --- a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift +++ b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift @@ -30,7 +30,7 @@ func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext, let toShape = shapeAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0) let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration() - let layer = AnimationUtils.layerForNodeRenderer(renderer, context, animation: animation, shouldRenderContent: false) + let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false) // Creating proper animation let generatedAnimation = generateShapeAnimation(context, diff --git a/Source/animation/types/animation_generators/TransformGenerator.swift b/Source/animation/types/animation_generators/TransformGenerator.swift index 98612d1c..5e45377d 100644 --- a/Source/animation/types/animation_generators/TransformGenerator.swift +++ b/Source/animation/types/animation_generators/TransformGenerator.swift @@ -22,7 +22,7 @@ func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationCont let transactionsDisabled = CATransaction.disableActions() CATransaction.setDisableActions(true) - let layer = AnimationUtils.layerForNodeRenderer(renderer, context, animation: animation, shouldRenderContent: true) + let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: true) // Creating proper animation let generatedAnimation = transformAnimationByFunc(transformAnimation, @@ -54,6 +54,15 @@ func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationCont node.placeVar.value = transformAnimation.getVFunc()(1.0) } + CATransaction.begin() + CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions) + renderer.layer?.animationLayer.transform = CATransform3DMakeAffineTransform(node.place.toCG()) + CATransaction.commit() + + if !animation.paused { + animation.removeFunc?() + } + renderer.freeLayer() if !animation.cycled && @@ -68,6 +77,7 @@ func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationCont let animationId = animation.ID layer.add(generatedAnimation, forKey: animationId) animation.removeFunc = { [weak layer] in + node.animations.removeAll { $0 === animation } layer?.removeAnimation(forKey: animationId) } diff --git a/Source/export/MacawView+PDF.swift b/Source/export/MacawView+PDF.swift index b723ec69..e54a1f17 100644 --- a/Source/export/MacawView+PDF.swift +++ b/Source/export/MacawView+PDF.swift @@ -27,8 +27,8 @@ public extension MacawView { y: -size.height / bounds.height ) - context.cgContext = ctx - renderer?.render(in: ctx, force: false, opacity: node.opacity) + drawingView.context.cgContext = ctx + drawingView.renderer?.render(in: ctx, force: false, opacity: node.opacity) ctx.endPDFPage() } diff --git a/Source/model/draw/ColorMatrix.swift b/Source/model/draw/ColorMatrix.swift index 0ee4ec31..9ac27a5a 100644 --- a/Source/model/draw/ColorMatrix.swift +++ b/Source/model/draw/ColorMatrix.swift @@ -57,7 +57,7 @@ open class ColorMatrix { let m3 = [-0.213, -0.715, 0.928, 0.143, 0.140, -0.283, -0.787, 0.715, 0.072] - let a = { i in + let a = { (i: Int) -> Double in m1[i] + c * m2[i] + s * m3[i] } self.init(values: [a(0), a(1), a(2), 0, 0, diff --git a/Source/model/geom2d/Transform.swift b/Source/model/geom2d/Transform.swift index 206d137a..b0e1f217 100644 --- a/Source/model/geom2d/Transform.swift +++ b/Source/model/geom2d/Transform.swift @@ -128,9 +128,9 @@ public final class Transform { } public func apply(to: Point) -> Point { - let x2 = m11 * to.x + m12 * to.x + dx - let y2 = m21 * to.y + m22 * to.y + dy - return Point(x: x2, y: y2) + let x = m11 * to.x + m12 * to.y + dx + let y = m21 * to.x + m22 * to.y + dy + return Point(x: x, y: y) } public func invert() -> Transform? { diff --git a/Source/model/scene/Group.swift b/Source/model/scene/Group.swift index 1358001d..a5e2f5ac 100644 --- a/Source/model/scene/Group.swift +++ b/Source/model/scene/Group.swift @@ -26,28 +26,28 @@ open class Group: Node { } // Searching - - override public func nodeBy(tag: String) -> Node? { - if let node = super.nodeBy(tag: tag) { + + override public func nodeBy(predicate: (Node) -> Bool) -> Node? { + if let node = super.nodeBy(predicate: predicate) { return node } for child in contents { - if let node = child.nodeBy(tag: tag) { + if let node = child.nodeBy(predicate: predicate) { return node } } return .none } - - override public func nodesBy(tag: String) -> [Node] { + + override public func nodesBy(predicate: (Node) -> Bool) -> [Node] { var result = [Node]() contents.forEach { child in - result.append(contentsOf: child.nodesBy(tag: tag)) + result.append(contentsOf: child.nodesBy(predicate: predicate)) } - if let node = super.nodeBy(tag: tag) { + if let node = super.nodeBy(predicate: predicate) { result.append(node) } diff --git a/Source/model/scene/Node.swift b/Source/model/scene/Node.swift index 0f321552..30bc9952 100644 --- a/Source/model/scene/Node.swift +++ b/Source/model/scene/Node.swift @@ -42,16 +42,24 @@ open class Node: Drawable { // MARK: - Searching public func nodeBy(tag: String) -> Node? { - if self.tag.contains(tag) { - return self - } - - return .none + return nodeBy(predicate: { $0.tag.contains(tag) }) } public func nodesBy(tag: String) -> [Node] { - return [nodeBy(tag: tag)].compactMap { $0 } + return nodesBy(predicate: { $0.tag.contains(tag) }) } + + public func nodeBy(predicate: (Node) -> Bool) -> Node? { + if predicate(self) { + return self + } + return .none + } + + public func nodesBy(predicate: (Node) -> Bool) -> [Node] { + return [nodeBy(predicate: predicate)].compactMap { $0 } + } + // MARK: - Events internal var animationObservers = [AnimationObserver]() @@ -111,11 +119,11 @@ open class Node: Drawable { @discardableResult public func onTap(tapCount: Int = 1, f: @escaping (TapEvent) -> Void) -> Disposable { let handler = ChangeHandler(f) - if var handlers = tapHandlers[tapCount] { - handlers.append(handler) - } else { - tapHandlers[tapCount] = [handler] - } + + var handlers = tapHandlers[tapCount] ?? [] + handlers.append(handler) + + tapHandlers[tapCount] = handlers return Disposable { [weak self, unowned handler] in guard let index = self?.tapHandlers[tapCount]?.firstIndex(of: handler) else { diff --git a/Source/platform/iOS/Common_iOS.swift b/Source/platform/iOS/Common_iOS.swift index 219342fe..3a25ac25 100644 --- a/Source/platform/iOS/Common_iOS.swift +++ b/Source/platform/iOS/Common_iOS.swift @@ -99,4 +99,10 @@ extension UIScreen { } } +extension UIBezierPath { + var mUsesEvenOddFillRule: Bool { + return self.usesEvenOddFillRule + } +} + #endif diff --git a/Source/platform/macOS/Common_macOS.swift b/Source/platform/macOS/Common_macOS.swift index b987e957..f667c3e8 100644 --- a/Source/platform/macOS/Common_macOS.swift +++ b/Source/platform/macOS/Common_macOS.swift @@ -184,4 +184,10 @@ extension NSWindow { } } +extension NSBezierPath { + var mUsesEvenOddFillRule: Bool { + return self.windingRule == .evenOdd + } +} + #endif diff --git a/Source/render/GroupRenderer.swift b/Source/render/GroupRenderer.swift index 76f02504..36f3d3ed 100644 --- a/Source/render/GroupRenderer.swift +++ b/Source/render/GroupRenderer.swift @@ -13,7 +13,7 @@ class GroupRenderer: NodeRenderer { return group } - init(group: Group, view: MacawView?, parentRenderer: GroupRenderer? = nil) { + init(group: Group, view: DrawingView?, parentRenderer: GroupRenderer? = nil) { self.group = group super.init(node: group, view: view, parentRenderer: parentRenderer) updateRenderers() diff --git a/Source/render/ImageRenderer.swift b/Source/render/ImageRenderer.swift index c9a571cc..693c96d8 100644 --- a/Source/render/ImageRenderer.swift +++ b/Source/render/ImageRenderer.swift @@ -17,7 +17,7 @@ class ImageRenderer: NodeRenderer { return image } - init(image: Image, view: MacawView?, parentRenderer: GroupRenderer? = nil) { + init(image: Image, view: DrawingView?, parentRenderer: GroupRenderer? = nil) { self.image = image super.init(node: image, view: view, parentRenderer: parentRenderer) } diff --git a/Source/render/NodeRenderer.swift b/Source/render/NodeRenderer.swift index 957f3445..854b9fc2 100644 --- a/Source/render/NodeRenderer.swift +++ b/Source/render/NodeRenderer.swift @@ -22,15 +22,15 @@ class CachedLayer { class NodeRenderer { - weak var view: MacawView? + weak var view: DrawingView? var sceneLayer: CALayer? { return view?.mLayer } var layer: CachedLayer? var zPosition: Int = 0 - let parentRenderer: GroupRenderer? - + private(set) weak var parentRenderer: GroupRenderer? + fileprivate let onNodeChange: () -> Void fileprivate let disposables = GroupDisposable() fileprivate var active = false @@ -71,7 +71,7 @@ class NodeRenderer { fatalError("Unsupported") } - init(node: Node, view: MacawView?, parentRenderer: GroupRenderer? = nil) { + init(node: Node, view: DrawingView?, parentRenderer: GroupRenderer? = nil) { self.view = view self.parentRenderer = parentRenderer diff --git a/Source/render/RenderContext.swift b/Source/render/RenderContext.swift index 92c85092..82759f0f 100644 --- a/Source/render/RenderContext.swift +++ b/Source/render/RenderContext.swift @@ -5,10 +5,10 @@ import UIKit #endif class RenderContext { - weak var view: MacawView? + weak var view: DrawingView? var cgContext: CGContext? - init(view: MacawView?) { + init(view: DrawingView?) { self.view = view self.cgContext = nil } diff --git a/Source/render/RenderUtils.swift b/Source/render/RenderUtils.swift index b2166717..9c8dec10 100644 --- a/Source/render/RenderUtils.swift +++ b/Source/render/RenderUtils.swift @@ -16,7 +16,7 @@ class RenderUtils { return p } - class func createNodeRenderer(_ node: Node, view: MacawView?, parentRenderer: GroupRenderer? = nil) -> NodeRenderer { + class func createNodeRenderer(_ node: Node, view: DrawingView?, parentRenderer: GroupRenderer? = nil) -> NodeRenderer { if let group = node as? Group { return GroupRenderer(group: group, view: view, parentRenderer: parentRenderer) } else if let shape = node as? Shape { diff --git a/Source/render/ShapeRenderer.swift b/Source/render/ShapeRenderer.swift index 69f5f417..48345d86 100644 --- a/Source/render/ShapeRenderer.swift +++ b/Source/render/ShapeRenderer.swift @@ -10,7 +10,7 @@ class ShapeRenderer: NodeRenderer { var shape: Shape - init(shape: Shape, view: MacawView?, parentRenderer: GroupRenderer? = nil) { + init(shape: Shape, view: DrawingView?, parentRenderer: GroupRenderer? = nil) { self.shape = shape super.init(node: shape, view: view, parentRenderer: parentRenderer) } diff --git a/Source/render/TextRenderer.swift b/Source/render/TextRenderer.swift index 292e0f3a..c34d373c 100644 --- a/Source/render/TextRenderer.swift +++ b/Source/render/TextRenderer.swift @@ -13,7 +13,7 @@ class TextRenderer: NodeRenderer { return text } - init(text: Text, view: MacawView?, parentRenderer: GroupRenderer? = nil) { + init(text: Text, view: DrawingView?, parentRenderer: GroupRenderer? = nil) { self.text = text super.init(node: text, view: view, parentRenderer: parentRenderer) } diff --git a/Source/svg/SVGParser.swift b/Source/svg/SVGParser.swift index f3e0a7c6..facdd4d9 100644 --- a/Source/svg/SVGParser.swift +++ b/Source/svg/SVGParser.swift @@ -534,79 +534,83 @@ open class SVGParser { fileprivate func parseTransformationAttribute(_ attributes: String, transform: Transform = Transform()) -> Transform { - guard let matcher = SVGParserRegexHelper.getTransformAttributeMatcher() else { - return transform - } + // Transform attribute regular grammar (whitespace characters are ignored): + // ([a-zA-Z]+)\(((-?\d+\.?\d*e?-?\d*,?)+)\) + // Group (1) is an attribute name. + // Group (2) is comma-separated numbers. - let attributes = attributes.replacingOccurrences(of: "\n", with: "") - var finalTransform = transform - let fullRange = NSRange(location: 0, length: attributes.count) + var transform = transform + let scanner = Scanner(string: attributes) - if let matchedAttribute = matcher.firstMatch(in: attributes, options: .reportCompletion, range: fullRange) { + stopParse: while !scanner.isAtEnd { + guard let attributeName = scanner.scannedCharacters(from: .transformationAttributeCharacters), + scanner.scanString("(", into: nil), + let valuesString = scanner.scannedUpToString(")"), + scanner.scanString(")", into: nil) else { + break stopParse + } - let attributeName = (attributes as NSString).substring(with: matchedAttribute.range(at: 1)) - let values = parseTransformValues((attributes as NSString).substring(with: matchedAttribute.range(at: 2))) + // Skip an optional comma after ")". + _ = scanner.scanString(",", into: nil) + + let values = parseTransformValues(valuesString) if values.isEmpty { return transform } + switch attributeName { case "translate": - if let x = Double(values[0]) { - var y: Double = 0 - if values.indices.contains(1) { - y = Double(values[1]) ?? 0 - } - finalTransform = transform.move(dx: x, dy: y) + let x = values[0] + var y: Double = 0 + if values.indices ~= 1 { + y = values[1] } + transform = transform.move(dx: x, dy: y) case "scale": - if let x = Double(values[0]) { - var y: Double = x - if values.indices.contains(1) { - y = Double(values[1]) ?? x - } - finalTransform = transform.scale(sx: x, sy: y) + let x = values[0] + var y: Double = x + if values.indices ~= 1 { + y = values[1] } + transform = transform.scale(sx: x, sy: y) case "rotate": - if let angle = Double(values[0]) { - if values.count == 1 { - finalTransform = transform.rotate(angle: degreesToRadians(angle)) - } else if values.count == 3 { - if let x = Double(values[1]), let y = Double(values[2]) { - finalTransform = transform.move(dx: x, dy: y).rotate(angle: degreesToRadians(angle)).move(dx: -x, dy: -y) - } - } + let angle = values[0] + if values.count == 1 { + transform = transform.rotate(angle: degreesToRadians(angle)) + } else if values.count == 3 { + let x = values[1] + let y = values[2] + transform = transform + .move(dx: x, dy: y) + .rotate(angle: degreesToRadians(angle)) + .move(dx: -x, dy: -y) } case "skewX": - if let x = Double(values[0]) { - let v = tan((x * Double.pi) / 180.0) - finalTransform = transform.shear(shx: v, shy: 0) - } + let x = values[0] + let v = tan((x * Double.pi) / 180.0) + transform = transform.shear(shx: v, shy: 0) case "skewY": - if let y = Double(values[0]) { - let y = tan((y * Double.pi) / 180.0) - finalTransform = transform.shear(shx: 0, shy: y) - } + let y = values[0] + let v = tan((y * Double.pi) / 180.0) + transform = transform.shear(shx: 0, shy: v) case "matrix": if values.count != 6 { return transform } - if let m11 = Double(values[0]), let m12 = Double(values[1]), - let m21 = Double(values[2]), let m22 = Double(values[3]), - let dx = Double(values[4]), let dy = Double(values[5]) { - - let transformMatrix = Transform(m11: m11, m12: m12, m21: m21, m22: m22, dx: dx, dy: dy) - finalTransform = transform.concat(with: transformMatrix) - } + let m11 = values[0] + let m12 = values[1] + let m21 = values[2] + let m22 = values[3] + let dx = values[4] + let dy = values[5] + let transformMatrix = Transform(m11: m11, m12: m12, m21: m21, m22: m22, dx: dx, dy: dy) + transform = transform.concat(with: transformMatrix) default: - break + break stopParse } - let rangeToRemove = NSRange(location: 0, - length: matchedAttribute.range.location + matchedAttribute.range.length) - let newAttributeString = (attributes as NSString).replacingCharacters(in: rangeToRemove, with: "") - return parseTransformationAttribute(newAttributeString, transform: finalTransform) - } else { - return transform } + + return transform } /// Parse an RGB @@ -639,20 +643,21 @@ open class SVGParser { b: Int(blue.rounded(.up))) } - fileprivate func parseTransformValues(_ values: String, collectedValues: [String] = []) -> [String] { - guard let matcher = SVGParserRegexHelper.getTransformMatcher() else { - return collectedValues + fileprivate func parseTransformValues(_ values: String) -> [Double] { + // Parse comma-separated list of numbers. + var collectedValues: [Double] = [] + let scanner = Scanner(string: values) + + while !scanner.isAtEnd { + if let value = scanner.scannedDouble() { + collectedValues.append(value) + } else { + break + } + _ = scanner.scanString(",", into: nil) } - var updatedValues: [String] = collectedValues - let fullRange = NSRange(location: 0, length: values.count) - if let matchedValue = matcher.firstMatch(in: values, options: .reportCompletion, range: fullRange) { - let value = (values as NSString).substring(with: matchedValue.range) - updatedValues.append(value) - let rangeToRemove = NSRange(location: 0, length: matchedValue.range.location + matchedValue.range.length) - let newValues = (values as NSString).replacingCharacters(in: rangeToRemove, with: "") - return parseTransformValues(newValues, collectedValues: updatedValues) - } - return updatedValues + + return collectedValues } fileprivate func getStyleAttributes(_ groupAttributes: [String: String], @@ -986,15 +991,14 @@ open class SVGParser { fileprivate func parsePoints(_ pointsString: String) -> [Double] { var resultPoints: [Double] = [] - let pointPairs = pointsString.replacingOccurrences(of: "-", with: " -").components(separatedBy: " ") - pointPairs.forEach { pointPair in - let points = pointPair.components(separatedBy: ",") - points.forEach { point in - if let resultPoint = Double(point) { - resultPoints.append(resultPoint) - } + let scanner = Scanner(string: pointsString) + while !scanner.isAtEnd { + var resultPoint: Double = 0 + if scanner.scanDouble(&resultPoint) { + resultPoints.append(resultPoint) } + _ = scanner.scanCharacters(from: [","], into: nil) } if resultPoints.count % 2 == 1 { @@ -1043,28 +1047,19 @@ open class SVGParser { fontWeight: fontWeight, pos: pos) } else { - guard let matcher = SVGParserRegexHelper.getTextElementMatcher() else { - return .none - } - let elementString = element.description - let fullRange = NSRange(location: 0, length: elementString.count) - if let match = matcher.firstMatch(in: elementString, options: .reportCompletion, range: fullRange) { - let tspans = (elementString as NSString).substring(with: match.range(at: 1)) - let rect = Rect(x: getDoubleValue(element, attribute: "x") ?? 0, - y: getDoubleValue(element, attribute: "y") ?? 0) - let collectedTspans = collectTspans(tspans, - textAnchor: textAnchor, - fill: fill, - stroke: stroke, - opacity: opacity, - fontName: fontName, - fontSize: fontSize, - fontWeight: fontWeight, - bounds: rect) - return Group(contents: collectedTspans, place: pos, tag: getTag(element)) - } + let rect = Rect(x: getDoubleValue(element, attribute: "x") ?? 0, + y: getDoubleValue(element, attribute: "y") ?? 0) + let collectedTspans = collectTspans(element.children, + textAnchor: textAnchor, + fill: fill, + stroke: stroke, + opacity: opacity, + fontName: fontName, + fontSize: fontSize, + fontWeight: fontWeight, + bounds: rect) + return Group(contents: collectedTspans, place: pos, tag: getTag(element)) } - return .none } fileprivate func anchorToAlign(_ textAnchor: String?) -> Align { @@ -1104,9 +1099,7 @@ open class SVGParser { // REFACTOR - fileprivate func collectTspans(_ tspan: String, - collectedTspans: [Node] = [], - withWhitespace: Bool = false, + fileprivate func collectTspans(_ contents: [XMLContent], textAnchor: String?, fill: Fill?, stroke: Stroke?, @@ -1115,99 +1108,78 @@ open class SVGParser { fontSize: Int?, fontWeight: String?, bounds: Rect) -> [Node] { - let fullString = tspan.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) as NSString - // exit recursion - if fullString.isEqual(to: "") { - return collectedTspans - } - var collection = collectedTspans - let tagRange = fullString.range(of: " element - let closingTagRange = fullString.range(of: "".lowercased()) - let tspanString = fullString.substring(to: closingTagRange.location + closingTagRange.length) - let tspanXml = SWXMLHash.parse(tspanString) - guard let indexer = tspanXml.children.first, - let text = parseTspan(indexer, - withWhitespace: withWhitespace, - textAnchor: textAnchor, - fill: fill, - stroke: stroke, - opacity: opacity, - fontName: fontName, - fontSize: fontSize, - fontWeight: fontWeight, - bounds: bounds, - previousCollectedTspan: collection.last) else { + var collection: [Node] = [] + var bounds = bounds + // Whether to add a space before the next non-whitespace-only text. + var addWhitespace = false + // Whether to preserve leading whitespaces before the next text + // by adding a single space prefix. + var preserveWhitespace = false - // skip this element if it can't be parsed - return collectTspans(fullString.substring(from: closingTagRange.location + closingTagRange.length), - collectedTspans: collectedTspans, - textAnchor: textAnchor, - fill: fill, - stroke: stroke, - opacity: opacity, - fontName: fontName, - fontSize: fontSize, - fontWeight: fontWeight, - bounds: bounds) + for element in contents { + let text: Text? + if let textElement = element as? TextElement { + // parse as regular text element + let textString = textElement.text + let hasLeadingWhitespace = textString.first?.isWhitespace == true + let hasTrailingWhitespace = textString.last?.isWhitespace == true + + var trimmedString = textString.trimmingCharacters(in: .whitespacesAndNewlines) + let isWhitespaceOnly = trimmedString.isEmpty + + if hasLeadingWhitespace && preserveWhitespace && !isWhitespaceOnly { + trimmedString = " " + trimmedString + } + + addWhitespace = preserveWhitespace && hasTrailingWhitespace + preserveWhitespace = false + + if trimmedString.isEmpty { + continue + } + + let place = Transform().move(dx: bounds.x + bounds.w, dy: bounds.y) + + text = Text(text: trimmedString, + font: getFont(fontName: fontName, fontWeight: fontWeight, fontSize: fontSize), + fill: fill, + stroke: stroke, + align: anchorToAlign(textAnchor), + baseline: .alphabetic, + place: place, + opacity: opacity) + } else if let tspanElement = element as? SWXMLHash.XMLElement, + tspanElement.name == "tspan" { + // parse as element + // ultimately skip it if it cannot be parsed + text = parseTspan(tspanElement, + withWhitespace: addWhitespace, + textAnchor: textAnchor, + fill: fill, + stroke: stroke, + opacity: opacity, + fontName: fontName, + fontSize: fontSize, + fontWeight: fontWeight, + bounds: bounds, + previousCollectedTspan: collection.last) + preserveWhitespace = true + addWhitespace = false + } else { + print("Skipped an unexpected element type: \(type(of: element)).") + text = nil } - collection.append(text) - let nextString = fullString.substring(from: closingTagRange.location + closingTagRange.length) as NSString - var withWhitespace = false - if nextString.rangeOfCharacter(from: CharacterSet.whitespacesAndNewlines).location == 0 { - withWhitespace = true + + if let text = text { + collection.append(text) + bounds = Rect(x: bounds.x, y: bounds.y, w: bounds.w + text.bounds.w, h: bounds.h) } - return collectTspans(fullString.substring(from: closingTagRange.location + closingTagRange.length), - collectedTspans: collection, - withWhitespace: withWhitespace, - textAnchor: textAnchor, - fill: fill, - stroke: stroke, - opacity: opacity, - fontName: fontName, - fontSize: fontSize, - fontWeight: fontWeight, - bounds: Rect(x: bounds.x, y: bounds.y, w: bounds.w + text.bounds.w, h: bounds.h)) } - // parse as regular text element - var textString: NSString - if tagRange.location >= fullString.length { - textString = fullString - } else { - textString = fullString.substring(to: tagRange.location) as NSString - } - var nextStringWhitespace = false - var trimmedString = textString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - if trimmedString.count != textString.length { - nextStringWhitespace = true - } - trimmedString = withWhitespace ? " \(trimmedString)" : trimmedString - let text = Text(text: trimmedString, - font: getFont(fontName: fontName, fontWeight: fontWeight, fontSize: fontSize), - fill: fill, - stroke: stroke, - align: anchorToAlign(textAnchor), - baseline: .alphabetic, - place: Transform().move(dx: bounds.x + bounds.w, dy: bounds.y), opacity: opacity) - collection.append(text) - if tagRange.location >= fullString.length { // leave recursion - return collection - } - return collectTspans(fullString.substring(from: tagRange.location), - collectedTspans: collection, - withWhitespace: nextStringWhitespace, - textAnchor: textAnchor, - fill: fill, - stroke: stroke, - opacity: opacity, - fontName: fontName, - fontSize: fontSize, - fontWeight: fontWeight, - bounds: Rect(x: bounds.x, y: bounds.y, w: bounds.w + text.bounds.w, h: bounds.h)) + + return collection } - fileprivate func parseTspan(_ tspan: XMLIndexer, + fileprivate func parseTspan(_ element: SWXMLHash.XMLElement, withWhitespace: Bool = false, textAnchor: String?, fill: Fill?, @@ -1219,10 +1191,6 @@ open class SVGParser { bounds: Rect, previousCollectedTspan: Node?) -> Text? { - guard let element = tspan.element else { - return .none - } - let string = element.text var shouldAddWhitespace = withWhitespace let pos = getTspanPosition(element, @@ -1650,40 +1618,37 @@ open class SVGParser { } fileprivate func dimensionFromString(_ string: String) -> SVGLength? { + if string.hasSuffix("%"), let value = Double(string.dropLast()) { + return SVGLength(percent: value) + } if let value = doubleFromString(string) { return SVGLength(pixels: value) } - if string.hasSuffix("%") { - return SVGLength(percent: Double(string.dropLast())!) - } return .none } fileprivate func doubleFromString(_ string: String) -> Double? { - if let doubleValue = Double(string) { - return doubleValue - } if string == "none" { return 0 } - guard let matcher = SVGParserRegexHelper.getUnitsIdenitifierMatcher() else { + + let scanner = Scanner(string: string) + let value = scanner.scannedDouble() + let unit = scanner.scannedCharacters(from: .unitCharacters) + + if !scanner.isAtEnd { + let junk = scanner.scannedUpToCharacters(from: []) ?? "" + print("Found trailing junk \"\(junk)\" in string \"\(string)\".") return .none } - let fullRange = NSRange(location: 0, length: string.count) - if let match = matcher.firstMatch(in: string, options: .reportCompletion, range: fullRange) { - let unitString = (string as NSString).substring(with: match.range(at: 1)) - let numberString = String(string.dropLast(unitString.count)) - let value = Double(numberString) ?? 0 - switch unitString { - case "px" : - return value - default: - print("SVG parsing error. Unit \(unitString) not supported") - return value - } + switch unit { + case nil, "px": + return value + default: + print("SVG parsing error. Unit \"\(unit ?? "")\" is not supported") + return value } - return .none } fileprivate func getDoubleValueFromPercentage(_ element: SWXMLHash.XMLElement, attribute: String) -> Double? { @@ -2190,3 +2155,55 @@ fileprivate enum SVGKeys { static let color = "color" static let currentColor = "currentColor" } + +fileprivate extension Scanner { + /// A version of `scanDouble()`, available for an earlier OS. + func scannedDouble() -> Double? { + if #available(OSX 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { + return scanDouble() + } else { + var double: Double = 0 + return scanDouble(&double) ? double : nil + } + } + + /// A version of `scanCharacters(from:)`, available for an earlier OS. + func scannedCharacters(from set: CharacterSet) -> String? { + if #available(OSX 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { + return scanCharacters(from: set) + } else { + var string: NSString? = nil + return scanCharacters(from: set, into: &string) ? string as String? : nil + } + } + + /// A version of `scanUpToCharacters(from:)`, available for an earlier OS. + func scannedUpToCharacters(from set: CharacterSet) -> String? { + if #available(OSX 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { + return scanUpToCharacters(from: set) + } else { + var string: NSString? = nil + return scanUpToCharacters(from: set, into: &string) ? string as String? : nil + } + } + + /// A version of `scanUpToString(_:)`, available for an earlier OS. + func scannedUpToString(_ substring: String) -> String? { + if #available(OSX 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { + return scanUpToString(substring) + } else { + var string: NSString? = nil + return scanUpTo(substring, into: &string) ? string as String? : nil + } + } +} + +fileprivate extension CharacterSet { + /// Latin alphabet characters. + static let latinAlphabet = CharacterSet(charactersIn: "a"..."z") + .union(CharacterSet(charactersIn: "A"..."Z")) + + static let unitCharacters = CharacterSet.latinAlphabet + + static let transformationAttributeCharacters = CharacterSet.latinAlphabet +} diff --git a/Source/svg/SVGParserRegexHelper.swift b/Source/svg/SVGParserRegexHelper.swift deleted file mode 100644 index d50e0cc8..00000000 --- a/Source/svg/SVGParserRegexHelper.swift +++ /dev/null @@ -1,72 +0,0 @@ -import Foundation - -class SVGParserRegexHelper { - - fileprivate static let transformAttributePattern = "([a-z]+)\\(((\\-?\\d+\\.?\\d*e?\\-?\\d*\\s*,?\\s*)+)\\)" - fileprivate static let transformPattern = "\\-?\\d+\\.?\\d*e?\\-?\\d*" - fileprivate static let textElementPattern = "((?s:.*))<\\/text>" - fileprivate static let maskIdenitifierPattern = "url\\(#((?s:.*))\\)" - fileprivate static let unitsIdenitifierPattern = "([a-zA-Z]+)$" - - fileprivate static var transformMatcher: NSRegularExpression? - fileprivate static var transformAttributeMatcher: NSRegularExpression? - fileprivate static var textElementMatcher: NSRegularExpression? - fileprivate static var maskIdenitifierMatcher: NSRegularExpression? - fileprivate static var unitsMatcher: NSRegularExpression? - - class func getTransformAttributeMatcher() -> NSRegularExpression? { - if self.transformAttributeMatcher == nil { - do { - self.transformAttributeMatcher = try NSRegularExpression(pattern: transformAttributePattern, options: .caseInsensitive) - } catch { - - } - } - return self.transformAttributeMatcher - } - - class func getTransformMatcher() -> NSRegularExpression? { - if self.transformMatcher == nil { - do { - self.transformMatcher = try NSRegularExpression(pattern: transformPattern, options: .caseInsensitive) - } catch { - - } - } - return self.transformMatcher - } - - class func getTextElementMatcher() -> NSRegularExpression? { - if self.textElementMatcher == nil { - do { - self.textElementMatcher = try NSRegularExpression(pattern: textElementPattern, options: .caseInsensitive) - } catch { - - } - } - return self.textElementMatcher - } - - class func getMaskIdenitifierMatcher() -> NSRegularExpression? { - if self.maskIdenitifierMatcher == nil { - do { - self.maskIdenitifierMatcher = try NSRegularExpression(pattern: maskIdenitifierPattern, options: .caseInsensitive) - } catch { - - } - } - return self.maskIdenitifierMatcher - } - - class func getUnitsIdenitifierMatcher() -> NSRegularExpression? { - if unitsMatcher == nil { - do { - unitsMatcher = try NSRegularExpression(pattern: unitsIdenitifierPattern, options: .caseInsensitive) - } catch { - - } - } - return unitsMatcher - } - -} diff --git a/Source/svg/SVGSerializer.swift b/Source/svg/SVGSerializer.swift index a5ef8822..096abbe1 100644 --- a/Source/svg/SVGSerializer.swift +++ b/Source/svg/SVGSerializer.swift @@ -407,7 +407,8 @@ extension Double { func serialize() -> String { let formatter = NumberFormatter() formatter.minimumIntegerDigits = 1 - formatter.maximumFractionDigits = 15 + formatter.maximumFractionDigits = 6 + formatter.decimalSeparator = "." 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 e3acc84b..6fe5042e 100644 --- a/Source/utils/CGMappings.swift +++ b/Source/utils/CGMappings.swift @@ -153,3 +153,61 @@ public extension Node { } } + +extension MBezierPath { + + public func toMacaw() -> Path { + let fillRule: FillRule = self.mUsesEvenOddFillRule ? .evenodd : .nonzero + return self.cgPath.toMacaw(fillRule: fillRule) + } + +} + +extension CGPath { + + public func toMacaw(fillRule: FillRule = .nonzero) -> Path { + + func createPathSegment(type: PathSegmentType, points: UnsafeMutablePointer, count: Int) -> PathSegment { + + var data = [Double]() + for index in 0.. Void) { + typealias Body = @convention(block) (CGPathElement) -> Void + func callback(info: UnsafeMutableRawPointer?, element: UnsafePointer) { + let body = unsafeBitCast(info, to: Body.self) + body(element.pointee) + } + let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self) + self.apply(info: unsafeBody, function: callback) + } +} diff --git a/Source/views/MacawView.swift b/Source/views/MacawView.swift index 2b208498..7ede880b 100644 --- a/Source/views/MacawView.swift +++ b/Source/views/MacawView.swift @@ -10,8 +10,202 @@ import AppKit /// MacawView is a main class used to embed Macaw scene into your Cocoa UI. /// You could create your own view extended from MacawView with predefined scene. /// + open class MacawView: MView, MGestureRecognizerDelegate { + internal var drawingView = DrawingView() + + public lazy var zoom = MacawZoom(view: self) + + open var node: Node { + get { return drawingView.node } + set { drawingView.node = newValue } + } + + open var contentLayout: ContentLayout { + get { return drawingView.contentLayout } + set { drawingView.contentLayout = newValue } + } + + open override var contentMode: MViewContentMode { + get { return drawingView.contentMode } + set { drawingView.contentMode = newValue } + } + + open var place: Transform { + get { return drawingView.place } + } + + open var placeVar: Variable { + get { return drawingView.placeVar } + } + + override open var frame: CGRect { + didSet { + super.frame = frame + drawingView.frame = frame + } + } + + override open var intrinsicContentSize: CGSize { + get { return drawingView.intrinsicContentSize } + } + + internal var renderer: NodeRenderer? { + get { return drawingView.renderer } + set { drawingView.renderer = newValue } + } + + #if os(OSX) + open override var layer: CALayer? { + didSet { + if self.layer == nil { + initializeView() + renderer = RenderUtils.createNodeRenderer(node, view: drawingView) + } + } + } + #endif + + @objc public convenience required init?(coder aDecoder: NSCoder) { + self.init(node: Group(), coder: aDecoder) + } + + @objc public init?(node: Node, coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + self.node = node + self.renderer = RenderUtils.createNodeRenderer(node, view: drawingView) + + zoom.initialize(onChange: { [weak self] transform in + self?.onZoomChange(t: transform) + }) + initializeView() + } + + public convenience init(node: Node, frame: CGRect) { + self.init(frame: frame) + + self.node = node + self.renderer = RenderUtils.createNodeRenderer(node, view: drawingView) + } + + public override init(frame: CGRect) { + super.init(frame: frame) + + zoom.initialize(onChange: { [weak self] transform in + self?.onZoomChange(t: transform) + }) + initializeView() + } + + public final func findNodeAt(location: CGPoint) -> Node? { + return drawingView.findNodeAt(location: location) + } + + private func onZoomChange(t: Transform) { + if let viewLayer = drawingView.mLayer { + viewLayer.transform = CATransform3DMakeAffineTransform(t.toCG()) + } + } + + func initializeView() { + + if !self.subviews.contains(drawingView) { + if self.backgroundColor == nil { + self.backgroundColor = .white + } + self.addSubview(drawingView) + drawingView.backgroundColor = .clear + drawingView.initializeView() + + drawingView.translatesAutoresizingMaskIntoConstraints = false + drawingView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true + drawingView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true + drawingView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true + drawingView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true + + #if os(iOS) + self.clipsToBounds = true + drawingView.isUserInteractionEnabled = false + #endif + } + + let tapRecognizer = MTapGestureRecognizer(target: drawingView, action: #selector(DrawingView.handleTap(recognizer:))) + let longTapRecognizer = MLongPressGestureRecognizer(target: drawingView, action: #selector(DrawingView.handleLongTap(recognizer:))) + let panRecognizer = MPanGestureRecognizer(target: drawingView, action: #selector(DrawingView.handlePan)) + let rotationRecognizer = MRotationGestureRecognizer(target: drawingView, action: #selector(DrawingView.handleRotation)) + let pinchRecognizer = MPinchGestureRecognizer(target: drawingView, action: #selector(DrawingView.handlePinch)) + + tapRecognizer.delegate = self + longTapRecognizer.delegate = self + panRecognizer.delegate = self + rotationRecognizer.delegate = self + pinchRecognizer.delegate = self + + tapRecognizer.cancelsTouchesInView = false + longTapRecognizer.cancelsTouchesInView = false + panRecognizer.cancelsTouchesInView = false + rotationRecognizer.cancelsTouchesInView = false + pinchRecognizer.cancelsTouchesInView = false + + self.removeGestureRecognizers() + self.addGestureRecognizer(tapRecognizer) + self.addGestureRecognizer(longTapRecognizer) + self.addGestureRecognizer(panRecognizer) + self.addGestureRecognizer(rotationRecognizer) + self.addGestureRecognizer(pinchRecognizer) + } + + open override func mTouchesBegan(_ touches: Set, with event: MEvent?) { + super.mTouchesBegan(touches, with: event) + zoom.touchesBegan(touches) + + drawingView.touchesBegan(touchPoints: convert(touches: touches)) + } + + open override func mTouchesMoved(_ touches: Set, with event: MEvent?) { + super.mTouchesMoved(touches, with: event) + zoom.touchesMoved(touches) + + drawingView.touchesMoved(touchPoints: convert(touches: touches)) + } + + open override func mTouchesEnded(_ touches: Set, with event: MEvent?) { + super.mTouchesEnded(touches, with: event) + zoom.touchesEnded(touches) + + drawingView.touchesEnded(touchPoints: convert(touches: touches)) + } + + override open func mTouchesCancelled(_ touches: Set, with event: MEvent?) { + super.mTouchesCancelled(touches, with: event) + zoom.touchesEnded(touches) + + drawingView.touchesEnded(touchPoints: convert(touches: touches)) + } + + private func convert(touches: Set) -> [MTouchEvent] { + return touches.map { touch -> MTouchEvent in + let location = touch.location(in: self).toMacaw() + let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque()) + return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id) + } + } + + // MARK: - MGestureRecognizerDelegate + + public func gestureRecognizer(_ gestureRecognizer: MGestureRecognizer, shouldReceive touch: MTouch) -> Bool { + return true + } + + public func gestureRecognizer(_ gestureRecognizer: MGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: MGestureRecognizer) -> Bool { + return true + } +} + +internal class DrawingView: MView { + /// Scene root node open var node: Node = Group() { didSet { @@ -42,18 +236,14 @@ open class MacawView: MView, MGestureRecognizerDelegate { } } - public let zoom = MacawZoom() - - public var place: Transform { + var place: Transform { return placeManager.placeVar.value } - public var placeVar: Variable { + var placeVar: Variable { return placeManager.placeVar } - private let placeManager = RootPlaceManager() - override open var frame: CGRect { didSet { super.frame = frame @@ -68,6 +258,14 @@ open class MacawView: MView, MGestureRecognizerDelegate { } } + override open var intrinsicContentSize: CGSize { + if let bounds = node.bounds { + return bounds.size().toCG() + } else { + return CGSize(width: MNoIntrinsicMetric(), height: MNoIntrinsicMetric()) + } + } + override open func didMoveToSuperview() { super.didMoveToSuperview() @@ -78,14 +276,7 @@ open class MacawView: MView, MGestureRecognizerDelegate { animationProducer.addStoredAnimations(node, self) } - override open var intrinsicContentSize: CGSize { - if let bounds = node.bounds { - return bounds.size().toCG() - } else { - return CGSize(width: MNoIntrinsicMetric(), height: MNoIntrinsicMetric()) - } - } - + private let placeManager = RootPlaceManager() private let layoutHelper = LayoutHelper() var touchesMap = [MTouchEvent: [NodePath]]() @@ -98,82 +289,17 @@ open class MacawView: MView, MGestureRecognizerDelegate { var toRender = true var frameSetFirstTime = false - #if os(OSX) - open override var layer: CALayer? { - didSet { - guard self.layer != nil else { - return - } - initializeView() - - self.renderer = RenderUtils.createNodeRenderer(node, view: self) - } + func initializeView() { + self.contentLayout = .none + self.context = RenderContext(view: self) } - #endif - @objc public init?(node: Node, coder aDecoder: NSCoder) { + @objc public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - zoom.initialize(view: self, onChange: onZoomChange) - - initializeView() - - self.node = node - self.renderer = RenderUtils.createNodeRenderer(node, view: self) - backgroundColor = .white - } - - public convenience init(node: Node, frame: CGRect) { - self.init(frame: frame) - - self.node = node - self.renderer = RenderUtils.createNodeRenderer(node, view: self) - backgroundColor = .white } public override init(frame: CGRect) { super.init(frame: frame) - zoom.initialize(view: self, onChange: onZoomChange) - - initializeView() - } - - @objc public convenience required init?(coder aDecoder: NSCoder) { - self.init(node: Group(), coder: aDecoder) - } - - private func onZoomChange(t: Transform) { - placeManager.setZoom(place: t) - self.setNeedsDisplay() - } - - func initializeView() { - self.contentLayout = .none - self.context = RenderContext(view: self) - - let tapRecognizer = MTapGestureRecognizer(target: self, action: #selector(MacawView.handleTap)) - let longTapRecognizer = MLongPressGestureRecognizer(target: self, action: #selector(MacawView.handleLongTap(recognizer:))) - let panRecognizer = MPanGestureRecognizer(target: self, action: #selector(MacawView.handlePan)) - let rotationRecognizer = MRotationGestureRecognizer(target: self, action: #selector(MacawView.handleRotation)) - let pinchRecognizer = MPinchGestureRecognizer(target: self, action: #selector(MacawView.handlePinch)) - - tapRecognizer.delegate = self - longTapRecognizer.delegate = self - panRecognizer.delegate = self - rotationRecognizer.delegate = self - pinchRecognizer.delegate = self - - tapRecognizer.cancelsTouchesInView = false - longTapRecognizer.cancelsTouchesInView = false - panRecognizer.cancelsTouchesInView = false - rotationRecognizer.cancelsTouchesInView = false - pinchRecognizer.cancelsTouchesInView = false - - self.removeGestureRecognizers() - self.addGestureRecognizer(tapRecognizer) - self.addGestureRecognizer(longTapRecognizer) - self.addGestureRecognizer(panRecognizer) - self.addGestureRecognizer(rotationRecognizer) - self.addGestureRecognizer(pinchRecognizer) } open override func layoutSubviews() { @@ -194,16 +320,16 @@ open class MacawView: MView, MGestureRecognizerDelegate { guard let renderer = renderer else { return } - renderer.calculateZPositionRecursively() // TODO: actually we should track all changes placeManager.setLayout(place: layoutHelper.getTransform(renderer, contentLayout, bounds.size.toMacaw())) - ctx.concatenate(self.place.toCG()) + + renderer.calculateZPositionRecursively() renderer.render(in: ctx, force: false, opacity: node.opacity) } - public final func findNodeAt(location: CGPoint) -> Node? { + final func findNodeAt(location: CGPoint) -> Node? { guard let ctx = context.cgContext else { return .none } @@ -222,17 +348,16 @@ open class MacawView: MView, MGestureRecognizerDelegate { defer { MGraphicsEndImageContext() } - guard let ctx = MGraphicsGetCurrentContext() else { + guard let ctx = MGraphicsGetCurrentContext(), let inverted = self.place.invert() else { return .none } - return doFindNode(location: location, ctx: ctx) + let loc = location.applying(inverted.toCG()) + return doFindNode(location: loc, ctx: ctx) } // MARK: - Touches - override func mTouchesBegan(_ touches: Set, with event: MEvent?) { - zoom.touchesBegan(touches) + func touchesBegan(touchPoints: [MTouchEvent]) { - let touchPoints = convert(touches: touches) if !self.node.shouldCheckForPressed() && !self.node.shouldCheckForMoved() && !self.node.shouldCheckForReleased () { @@ -280,8 +405,7 @@ open class MacawView: MView, MGestureRecognizerDelegate { } } - override func mTouchesMoved(_ touches: Set, with event: MEvent?) { - zoom.touchesMoved(touches) + func touchesMoved(touchPoints: [MTouchEvent]) { if !self.node.shouldCheckForMoved() { return } @@ -290,7 +414,6 @@ open class MacawView: MView, MGestureRecognizerDelegate { return } - let touchPoints = convert(touches: touches) touchesOfNode.keys.forEach { currentNode in guard let initialTouches = touchesOfNode[currentNode] else { return @@ -324,30 +447,12 @@ open class MacawView: MView, MGestureRecognizerDelegate { } } - override func mTouchesCancelled(_ touches: Set, with event: MEvent?) { - touchesEnded(touches: touches) - } - - override func mTouchesEnded(_ touches: Set, with event: MEvent?) { - touchesEnded(touches: touches) - } - - private func convert(touches: Set) -> [MTouchEvent] { - return touches.map { touch -> MTouchEvent in - let location = touch.location(in: self) - let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque()) - return MTouchEvent(x: Double(location.x), y: Double(location.y), id: id) - } - } - - private func touchesEnded(touches: Set) { - zoom.touchesEnded(touches) + func touchesEnded(touchPoints: [MTouchEvent]) { guard let _ = renderer else { return } let invertedViewPlace = self.place.invert() - let touchPoints = convert(touches: touches) for touch in touchPoints { touchesMap[touch]?.forEach { nodePath in @@ -566,16 +671,6 @@ open class MacawView: MView, MGestureRecognizerDelegate { recognizersMap.removeValue(forKey: recognizer) } } - - // MARK: - MGestureRecognizerDelegate - - public func gestureRecognizer(_ gestureRecognizer: MGestureRecognizer, shouldReceive touch: MTouch) -> Bool { - return true - } - - public func gestureRecognizer(_ gestureRecognizer: MGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: MGestureRecognizer) -> Bool { - return true - } } class LayoutHelper { diff --git a/Source/views/MacawZoom.swift b/Source/views/MacawZoom.swift index 1a9532cd..a8f1ca76 100644 --- a/Source/views/MacawZoom.swift +++ b/Source/views/MacawZoom.swift @@ -16,14 +16,18 @@ import AppKit open class MacawZoom { - private var view: MView! - private var onChange: ((Transform) -> Void)! + private unowned let view: MacawView + private var onChange: ((Transform) -> Void)? private var touches = [TouchData]() private var zoomData = ZoomData() private var trackMove = false private var trackScale = false private var trackRotate = false + + init(view: MacawView) { + self.view = view + } open func enable(move: Bool = true, scale: Bool = true, rotate: Bool = false) { trackMove = move @@ -47,11 +51,10 @@ open class MacawZoom { let s = scale ?? zoomData.scale let a = angle ?? zoomData.angle zoomData = ZoomData(offset: o, scale: s, angle: a) - onChange(zoomData.transform()) + onChange?(zoomData.transform()) } - func initialize(view: MView, onChange: @escaping ((Transform) -> Void)) { - self.view = view + func initialize(onChange: @escaping (Transform) -> Void) { self.onChange = onChange } @@ -63,7 +66,7 @@ open class MacawZoom { func touchesMoved(_ touches: Set) { let zoom = cleanTouches() ?? getNewZoom() - onChange(zoom.transform()) + onChange?(zoom.transform()) } func touchesEnded(_ touches: Set) { @@ -81,6 +84,9 @@ open class MacawZoom { } private func getNewZoom() -> ZoomData { + if !trackMove && !trackScale && !trackRotate { + return zoomData + } if touches.isEmpty || (touches.count == 1 && !trackMove) { return zoomData } @@ -93,15 +99,7 @@ open class MacawZoom { let e2 = touches[1].current(in: view) let scale = trackScale ? e1.distance(to: e2) / s1.distance(to: s2) : 1 let a = trackRotate ? (e1 - e2).angle() - (s1 - s2).angle() : 0 - var offset = Size.zero - if trackMove { - let sina = sin(a) - let cosa = cos(a) - let w = e1.x - scale * (s1.x * cosa - s1.y * sina) - let h = e1.y - scale * (s1.x * sina + s1.y * cosa) - offset = Size(w: w, h: h) - } - return ZoomData(offset: offset, scale: scale, angle: a).combine(with: zoomData) + return ZoomData(offset: .zero, scale: scale, angle: a).combine(with: zoomData) } } @@ -143,7 +141,7 @@ fileprivate class TouchData { let touch: MTouch let point: Point - convenience init(touch: MTouch, in view: MView) { + convenience init(touch: MTouch, in view: MacawView) { self.init(touch: touch, point: touch.location(in: view).toMacaw()) } @@ -152,7 +150,7 @@ fileprivate class TouchData { self.point = point } - func current(in view: MView) -> Point { + func current(in view: MacawView) -> Point { return touch.location(in: view).toMacaw() } diff --git a/Source/views/ShapeLayer.swift b/Source/views/ShapeLayer.swift index 2551bdc5..8ffab1f2 100644 --- a/Source/views/ShapeLayer.swift +++ b/Source/views/ShapeLayer.swift @@ -17,9 +17,6 @@ class ShapeLayer: CAShapeLayer { return } - let renderContext = RenderContext(view: .none) - renderContext.cgContext = ctx - renderer?.directRender(in: ctx, force: isForceRenderingEnabled) } }