├── .gitignore ├── .swift-version ├── .swiftlint.yml ├── .travis.yml ├── Cartfile ├── Cartfile.resolved ├── Dependencies └── SWXMLHash │ ├── XMLHash.swift │ ├── XMLIndexer+XMLIndexerDeserializable.swift │ └── shim.swift ├── Deprecation.svg ├── Example-macOS ├── Example-macOS.xcodeproj │ └── project.pbxproj ├── Example-macOS.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Example-macOS │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Assets │ │ └── SVG │ │ │ └── tiger.svg │ ├── Base.lproj │ │ └── Main.storyboard │ ├── ExampleViewController.swift │ ├── Examples │ │ ├── Animations │ │ │ ├── AnimationsView.swift │ │ │ └── AnimationsViewController.swift │ │ ├── Easing │ │ │ ├── EasingView.swift │ │ │ └── EasingViewController.swift │ │ ├── Events │ │ │ └── EventsExampleController.swift │ │ ├── Morphing │ │ │ └── MorphingView.swift │ │ ├── SVG │ │ │ └── SVGExampleViewController.swift │ │ ├── Shapes │ │ │ └── ShapesExampleView.swift │ │ └── Transform │ │ │ └── TransformExampleView.swift │ ├── Info.plist │ └── ViewController.swift └── Podfile ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── Example.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Assets │ │ └── SVG │ │ │ └── tiger.svg │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Examples │ │ ├── Animations │ │ │ ├── AnimationsExampleController.swift │ │ │ └── AnimationsView.swift │ │ ├── AnimationsHierarchy │ │ │ └── AnimationsHierarchyViewController.swift │ │ ├── Easing │ │ │ ├── EasingExampleController.swift │ │ │ └── EasingView.swift │ │ ├── Events │ │ │ └── EventsExampleController.swift │ │ ├── Filters │ │ │ └── FiltersViewController.swift │ │ ├── Morphing │ │ │ └── MorphingView.swift │ │ ├── PathAnimation │ │ │ └── PathAnimationView.swift │ │ ├── Shapes │ │ │ └── ShapesExampleView.swift │ │ ├── Text │ │ │ └── TextsExampleView.swift │ │ └── TransformExampleView.swift │ ├── Info.plist │ └── MenuViewController.swift └── Podfile ├── LICENSE ├── Macaw.podspec ├── Macaw.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── Macaw iOS.xcscheme │ ├── MacawOSX.xcscheme │ └── MacawTests.xcscheme ├── MacawTests ├── Animation │ ├── AnimationUtilsTests.swift │ ├── CombineAnimationTests.swift │ ├── ControlStatesTests.swift │ ├── DelayedAnimationTests.swift │ └── SequenceAnimationTests.swift ├── Bounds │ ├── ImageBoundsTests.swift │ ├── NodeBoundsTests.swift │ └── logo_base64.txt ├── Info.plist ├── MacawSVGTests.swift ├── MacawTests.bundle │ ├── circle.reference │ └── circle.svg ├── MacawTests.swift ├── SVGParserTest.swift ├── SceneSerialization.swift ├── TestUtils.swift ├── png │ ├── color-prop-01-b-manual.png │ ├── color-prop-03-t-manual.png │ ├── color-prop-04-t-manual-osx.png │ ├── color-prop-05-t-manual.png │ ├── coords-coord-01-t-manual.png │ ├── coords-coord-02-t-manual.png │ ├── coords-trans-01-b-manual.png │ ├── coords-trans-02-t-manual.png │ ├── coords-trans-03-t-manual.png │ ├── coords-trans-04-t-manual.png │ ├── coords-trans-05-t-manual.png │ ├── coords-trans-06-t-manual.png │ ├── coords-trans-07-t-manual.png │ ├── coords-trans-08-t-manual.png │ ├── coords-trans-09-t-manual.png │ ├── coords-trans-10-f-manual.png │ ├── coords-trans-11-f-manual.png │ ├── coords-trans-12-f-manual.png │ ├── coords-trans-13-f-manual.png │ ├── coords-trans-14-f-manual.png │ ├── coords-transformattr-01-f-manual.png │ ├── coords-transformattr-02-f-manual.png │ ├── coords-transformattr-03-f-manual.png │ ├── coords-transformattr-04-f-manual.png │ ├── coords-transformattr-05-f-manual.png │ ├── masking-filter-01-f-manual.png │ ├── masking-intro-01-f-manual.png │ ├── masking-mask-02-f-manual.png │ ├── masking-path-02-b-manual.png │ ├── masking-path-13-f-manual.png │ ├── metadata-example-01-t-manual.png │ ├── painting-control-01-f-manual.png │ ├── painting-control-02-f-manual.png │ ├── painting-control-03-f-manual.png │ ├── painting-control-06-f-manual.png │ ├── painting-fill-01-t-manual.png │ ├── painting-fill-02-t-manual.png │ ├── painting-fill-03-t-manual.png │ ├── painting-fill-04-t-manual.png │ ├── painting-fill-05-b-manual.png │ ├── painting-stroke-01-t-manual.png │ ├── painting-stroke-02-t-manual.png │ ├── painting-stroke-03-t-manual.png │ ├── painting-stroke-04-t-manual.png │ ├── painting-stroke-05-t-manual.png │ ├── painting-stroke-06-t-manual.png │ ├── painting-stroke-07-t-manual.png │ ├── painting-stroke-08-t-manual.png │ ├── painting-stroke-09-t-manual.png │ ├── paths-data-01-t-manual.png │ ├── paths-data-02-t-manual.png │ ├── paths-data-03-f-manual.png │ ├── paths-data-04-t-manual.png │ ├── paths-data-05-t-manual.png │ ├── paths-data-06-t-manual.png │ ├── paths-data-07-t-manual.png │ ├── paths-data-08-t-manual.png │ ├── paths-data-09-t-manual.png │ ├── paths-data-10-t-manual.png │ ├── paths-data-12-t-manual.png │ ├── paths-data-13-t-manual.png │ ├── paths-data-14-t-manual.png │ ├── paths-data-15-t-manual.png │ ├── paths-data-16-t-manual.png │ ├── paths-data-17-f-manual.png │ ├── paths-data-18-f-manual.png │ ├── paths-data-19-f-manual.png │ ├── paths-data-20-f-manual.png │ ├── pservers-grad-01-b-manual.png │ ├── pservers-grad-02-b-manual.png │ ├── pservers-grad-03-b-manual.png │ ├── pservers-grad-07-b-manual.png │ ├── pservers-grad-09-b-manual.png │ ├── pservers-grad-12-b-manual.png │ ├── pservers-grad-13-b-manual.png │ ├── pservers-grad-15-b-manual.png │ ├── pservers-grad-22-b-manual.png │ ├── pservers-grad-23-f-manual.png │ ├── pservers-grad-24-f-manual.png │ ├── pservers-grad-stops-01-f-manual.png │ ├── render-elems-01-t-manual.png │ ├── render-elems-02-t-manual.png │ ├── render-elems-03-t-manual.png │ ├── shapes-circle-01-t-manual.png │ ├── shapes-circle-02-t-manual.png │ ├── shapes-ellipse-01-t-manual.png │ ├── shapes-ellipse-02-t-manual.png │ ├── shapes-ellipse-03-f-manual.png │ ├── shapes-grammar-01-f-manual.png │ ├── shapes-intro-01-t-manual.png │ ├── shapes-line-01-t-manual.png │ ├── shapes-line-02-f-manual.png │ ├── shapes-polygon-01-t-manual.png │ ├── shapes-polygon-02-t-manual.png │ ├── shapes-polygon-03-t-manual.png │ ├── shapes-polyline-01-t-manual.png │ ├── shapes-polyline-02-t-manual.png │ ├── shapes-rect-02-t-manual.png │ ├── shapes-rect-03-t-manual.png │ ├── shapes-rect-04-f-manual.png │ ├── shapes-rect-05-f-manual.png │ ├── shapes-rect-06-f-manual.png │ ├── shapes-rect-07-f-manual.png │ ├── struct-defs-01-t-manual.png │ ├── struct-frag-01-t-manual.png │ ├── struct-frag-02-t-manual.png │ ├── struct-frag-03-t-manual.png │ ├── struct-frag-04-t-manual.png │ ├── struct-frag-06-t-manual.png │ ├── struct-group-01-t-manual.png │ ├── struct-use-01-t-manual.png │ ├── struct-use-03-t-manual.png │ ├── struct-use-12-f-manual.png │ ├── text-align-01-b-manual.png │ ├── text-fonts-01-t-manual.png │ ├── text-fonts-02-t-manual.png │ └── types-basic-01-f-manual.png ├── svg │ ├── arcsgroup.reference │ ├── circle.reference │ ├── circle.svg │ ├── clearColor.reference │ ├── clip.reference │ ├── clip.svg │ ├── clipManual.reference │ ├── ellipse.reference │ ├── ellipse.svg │ ├── group.reference │ ├── group.svg │ ├── line.reference │ ├── line.svg │ ├── polygon.reference │ ├── polygon.svg │ ├── polyline.reference │ ├── polyline.svg │ ├── rect.reference │ ├── rect.svg │ ├── roundRect.reference │ ├── roundRect.svg │ ├── rounded.reference │ ├── rounded.svg │ ├── small-logo.png │ ├── style.reference │ ├── style.svg │ ├── svglist.txt │ ├── textBasicTransform.reference │ ├── transform.reference │ ├── transform.svg │ ├── triangle.reference │ ├── triangle.svg │ ├── viewBox.reference │ └── viewBox.svg ├── w3c-test-suite.md └── w3cSVGTests │ ├── color-prop-01-b-manual.reference │ ├── color-prop-01-b-manual.svg │ ├── color-prop-02-f-manual.reference │ ├── color-prop-02-f-manual.svg │ ├── color-prop-03-t-manual.reference │ ├── color-prop-03-t-manual.svg │ ├── color-prop-04-t-manual-osx.reference │ ├── color-prop-04-t-manual-osx.svg │ ├── color-prop-04-t-manual.reference │ ├── color-prop-04-t-manual.svg │ ├── color-prop-05-t-manual.reference │ ├── color-prop-05-t-manual.svg │ ├── coords-coord-01-t-manual.reference │ ├── coords-coord-01-t-manual.svg │ ├── coords-coord-02-t-manual.reference │ ├── coords-coord-02-t-manual.svg │ ├── coords-trans-01-b-manual.reference │ ├── coords-trans-01-b-manual.svg │ ├── coords-trans-02-t-manual.reference │ ├── coords-trans-02-t-manual.svg │ ├── coords-trans-03-t-manual.reference │ ├── coords-trans-03-t-manual.svg │ ├── coords-trans-04-t-manual.reference │ ├── coords-trans-04-t-manual.svg │ ├── coords-trans-05-t-manual.reference │ ├── coords-trans-05-t-manual.svg │ ├── coords-trans-06-t-manual.reference │ ├── coords-trans-06-t-manual.svg │ ├── coords-trans-07-t-manual.reference │ ├── coords-trans-07-t-manual.svg │ ├── coords-trans-08-t-manual.reference │ ├── coords-trans-08-t-manual.svg │ ├── coords-trans-09-t-manual.reference │ ├── coords-trans-09-t-manual.svg │ ├── coords-trans-10-f-manual.reference │ ├── coords-trans-10-f-manual.svg │ ├── coords-trans-11-f-manual.reference │ ├── coords-trans-11-f-manual.svg │ ├── coords-trans-12-f-manual.reference │ ├── coords-trans-12-f-manual.svg │ ├── coords-trans-13-f-manual.reference │ ├── coords-trans-13-f-manual.svg │ ├── coords-trans-14-f-manual.reference │ ├── coords-trans-14-f-manual.svg │ ├── coords-transformattr-01-f-manual.reference │ ├── coords-transformattr-01-f-manual.svg │ ├── coords-transformattr-02-f-manual.reference │ ├── coords-transformattr-02-f-manual.svg │ ├── coords-transformattr-03-f-manual.reference │ ├── coords-transformattr-03-f-manual.svg │ ├── coords-transformattr-04-f-manual.reference │ ├── coords-transformattr-04-f-manual.svg │ ├── coords-transformattr-05-f-manual.reference │ ├── coords-transformattr-05-f-manual.svg │ ├── masking-filter-01-f-manual.reference │ ├── masking-filter-01-f-manual.svg │ ├── masking-intro-01-f-manual.reference │ ├── masking-intro-01-f-manual.svg │ ├── masking-mask-02-f-manual.reference │ ├── masking-mask-02-f-manual.svg │ ├── masking-path-02-b-manual.reference │ ├── masking-path-02-b-manual.svg │ ├── masking-path-13-f-manual.reference │ ├── masking-path-13-f-manual.svg │ ├── metadata-example-01-t-manual.reference │ ├── metadata-example-01-t-manual.svg │ ├── painting-control-01-f-manual.reference │ ├── painting-control-01-f-manual.svg │ ├── painting-control-02-f-manual.reference │ ├── painting-control-02-f-manual.svg │ ├── painting-control-03-f-manual.reference │ ├── painting-control-03-f-manual.svg │ ├── painting-control-06-f-manual.reference │ ├── painting-control-06-f-manual.svg │ ├── painting-fill-01-t-manual.reference │ ├── painting-fill-01-t-manual.svg │ ├── painting-fill-02-t-manual.reference │ ├── painting-fill-02-t-manual.svg │ ├── painting-fill-03-t-manual.reference │ ├── painting-fill-03-t-manual.svg │ ├── painting-fill-04-t-manual.reference │ ├── painting-fill-04-t-manual.svg │ ├── painting-fill-05-b-manual.reference │ ├── painting-fill-05-b-manual.svg │ ├── painting-stroke-01-t-manual.reference │ ├── painting-stroke-01-t-manual.svg │ ├── painting-stroke-02-t-manual.reference │ ├── painting-stroke-02-t-manual.svg │ ├── painting-stroke-03-t-manual.reference │ ├── painting-stroke-03-t-manual.svg │ ├── painting-stroke-04-t-manual.reference │ ├── painting-stroke-04-t-manual.svg │ ├── painting-stroke-05-t-manual.reference │ ├── painting-stroke-05-t-manual.svg │ ├── painting-stroke-06-t-manual.reference │ ├── painting-stroke-06-t-manual.svg │ ├── painting-stroke-07-t-manual.reference │ ├── painting-stroke-07-t-manual.svg │ ├── painting-stroke-08-t-manual.reference │ ├── painting-stroke-08-t-manual.svg │ ├── painting-stroke-09-t-manual.reference │ ├── painting-stroke-09-t-manual.svg │ ├── paths-data-01-t-manual.reference │ ├── paths-data-01-t-manual.svg │ ├── paths-data-02-t-manual.reference │ ├── paths-data-02-t-manual.svg │ ├── paths-data-03-f-manual.reference │ ├── paths-data-03-f-manual.svg │ ├── paths-data-04-t-manual.reference │ ├── paths-data-04-t-manual.svg │ ├── paths-data-05-t-manual.reference │ ├── paths-data-05-t-manual.svg │ ├── paths-data-06-t-manual.reference │ ├── paths-data-06-t-manual.svg │ ├── paths-data-07-t-manual.reference │ ├── paths-data-07-t-manual.svg │ ├── paths-data-08-t-manual.reference │ ├── paths-data-08-t-manual.svg │ ├── paths-data-09-t-manual.reference │ ├── paths-data-09-t-manual.svg │ ├── paths-data-10-t-manual.reference │ ├── paths-data-10-t-manual.svg │ ├── paths-data-12-t-manual.reference │ ├── paths-data-12-t-manual.svg │ ├── paths-data-13-t-manual.reference │ ├── paths-data-13-t-manual.svg │ ├── paths-data-14-t-manual.reference │ ├── paths-data-14-t-manual.svg │ ├── paths-data-15-t-manual.reference │ ├── paths-data-15-t-manual.svg │ ├── paths-data-16-t-manual.reference │ ├── paths-data-16-t-manual.svg │ ├── paths-data-17-f-manual.reference │ ├── paths-data-17-f-manual.svg │ ├── paths-data-18-f-manual.reference │ ├── paths-data-18-f-manual.svg │ ├── paths-data-19-f-manual.reference │ ├── paths-data-19-f-manual.svg │ ├── paths-data-20-f-manual.reference │ ├── paths-data-20-f-manual.svg │ ├── pservers-grad-01-b-manual.reference │ ├── pservers-grad-01-b-manual.svg │ ├── pservers-grad-02-b-manual.reference │ ├── pservers-grad-02-b-manual.svg │ ├── pservers-grad-03-b-manual.reference │ ├── pservers-grad-03-b-manual.svg │ ├── pservers-grad-07-b-manual.reference │ ├── pservers-grad-07-b-manual.svg │ ├── pservers-grad-09-b-manual.reference │ ├── pservers-grad-09-b-manual.svg │ ├── pservers-grad-12-b-manual.reference │ ├── pservers-grad-12-b-manual.svg │ ├── pservers-grad-13-b-manual.reference │ ├── pservers-grad-13-b-manual.svg │ ├── pservers-grad-15-b-manual.reference │ ├── pservers-grad-15-b-manual.svg │ ├── pservers-grad-22-b-manual.reference │ ├── pservers-grad-22-b-manual.svg │ ├── pservers-grad-23-f-manual.reference │ ├── pservers-grad-23-f-manual.svg │ ├── pservers-grad-24-f-manual.reference │ ├── pservers-grad-24-f-manual.svg │ ├── pservers-grad-stops-01-f-manual.reference │ ├── pservers-grad-stops-01-f-manual.svg │ ├── render-elems-01-t-manual.reference │ ├── render-elems-01-t-manual.svg │ ├── render-elems-02-t-manual.reference │ ├── render-elems-02-t-manual.svg │ ├── render-elems-03-t-manual.reference │ ├── render-elems-03-t-manual.svg │ ├── shapes-circle-01-t-manual.reference │ ├── shapes-circle-01-t-manual.svg │ ├── shapes-circle-02-t-manual.reference │ ├── shapes-circle-02-t-manual.svg │ ├── shapes-ellipse-01-t-manual.reference │ ├── shapes-ellipse-01-t-manual.svg │ ├── shapes-ellipse-02-t-manual.reference │ ├── shapes-ellipse-02-t-manual.svg │ ├── shapes-ellipse-03-f-manual.reference │ ├── shapes-ellipse-03-f-manual.svg │ ├── shapes-grammar-01-f-manual.reference │ ├── shapes-grammar-01-f-manual.svg │ ├── shapes-intro-01-t-manual.reference │ ├── shapes-intro-01-t-manual.svg │ ├── shapes-line-01-t-manual.reference │ ├── shapes-line-01-t-manual.svg │ ├── shapes-line-02-f-manual.reference │ ├── shapes-line-02-f-manual.svg │ ├── shapes-polygon-01-t-manual.reference │ ├── shapes-polygon-01-t-manual.svg │ ├── shapes-polygon-02-t-manual.reference │ ├── shapes-polygon-02-t-manual.svg │ ├── shapes-polygon-03-t-manual.reference │ ├── shapes-polygon-03-t-manual.svg │ ├── shapes-polyline-01-t-manual.reference │ ├── shapes-polyline-01-t-manual.svg │ ├── shapes-polyline-02-t-manual.reference │ ├── shapes-polyline-02-t-manual.svg │ ├── shapes-rect-02-t-manual.reference │ ├── shapes-rect-02-t-manual.svg │ ├── shapes-rect-03-t-manual.reference │ ├── shapes-rect-03-t-manual.svg │ ├── shapes-rect-04-f-manual.reference │ ├── shapes-rect-04-f-manual.svg │ ├── shapes-rect-05-f-manual.reference │ ├── shapes-rect-05-f-manual.svg │ ├── shapes-rect-06-f-manual.reference │ ├── shapes-rect-06-f-manual.svg │ ├── shapes-rect-07-f-manual.reference │ ├── shapes-rect-07-f-manual.svg │ ├── struct-defs-01-t-manual.reference │ ├── struct-defs-01-t-manual.svg │ ├── struct-frag-01-t-manual.reference │ ├── struct-frag-01-t-manual.svg │ ├── struct-frag-02-t-manual.reference │ ├── struct-frag-02-t-manual.svg │ ├── struct-frag-03-t-manual.reference │ ├── struct-frag-03-t-manual.svg │ ├── struct-frag-04-t-manual.reference │ ├── struct-frag-04-t-manual.svg │ ├── struct-frag-06-t-manual.reference │ ├── struct-frag-06-t-manual.svg │ ├── struct-group-01-t-manual.reference │ ├── struct-group-01-t-manual.svg │ ├── struct-use-01-t-manual.reference │ ├── struct-use-01-t-manual.svg │ ├── struct-use-03-t-manual.reference │ ├── struct-use-03-t-manual.svg │ ├── struct-use-12-f-manual.reference │ ├── struct-use-12-f-manual.svg │ ├── text-align-01-b-manual.reference │ ├── text-align-01-b-manual.svg │ ├── text-fonts-01-t-manual.reference │ ├── text-fonts-01-t-manual.svg │ ├── text-fonts-02-t-manual.reference │ ├── text-fonts-02-t-manual.svg │ ├── types-basic-01-f-manual.reference │ └── types-basic-01-f-manual.svg ├── Package.swift ├── README.md ├── README_zh.md ├── Source ├── Info.plist ├── MCAMediaTimingFillMode_iOS.swift ├── MCAMediaTimingFillMode_macOS.swift ├── MCAMediaTimingFunctionName_iOS.swift ├── MCAMediaTimingFunctionName_macOS.swift ├── MCAShapeLayerLineCap_iOS.swift ├── MCAShapeLayerLineCap_macOS.swift ├── MCAShapeLayerLineJoin_iOS.swift ├── MCAShapeLayerLineJoin_macOS.swift ├── animation │ ├── AnimatableVariable.swift │ ├── Animation.swift │ ├── AnimationImpl.swift │ ├── AnimationProducer.swift │ ├── AnimationUtils.swift │ ├── Easing.swift │ ├── layer_animation │ │ ├── Extensions │ │ │ ├── AnimOperators.swift │ │ │ ├── ContentsInterpolation.swift │ │ │ ├── DoubleInterpolation.swift │ │ │ ├── FillInterpolation.swift │ │ │ ├── Interpolable.swift │ │ │ ├── LocusInterpolation.swift │ │ │ ├── ShapeInterpolation.swift │ │ │ ├── StrokeInterpolation.swift │ │ │ └── TransformInterpolation.swift │ │ ├── FuncBounds.swift │ │ ├── PathBounds.swift │ │ └── PathFunctions.swift │ └── types │ │ ├── AnimationSequence.swift │ │ ├── CombineAnimation.swift │ │ ├── ContentsAnimation.swift │ │ ├── MorphingAnimation.swift │ │ ├── OpacityAnimation.swift │ │ ├── PathAnimation.swift │ │ ├── ShapeAnimation.swift │ │ ├── TransformAnimation.swift │ │ └── animation_generators │ │ ├── Cache │ │ ├── AnimationCache.swift │ │ ├── NodeHashable.swift │ │ └── TransformHashable.swift │ │ ├── CombinationAnimationGenerator.swift │ │ ├── MorphingGenerator.swift │ │ ├── OpacityGenerator.swift │ │ ├── PathAnimationGenerator.swift │ │ ├── ShapeAnimationGenerator.swift │ │ ├── TimingFunction.swift │ │ └── TransformGenerator.swift ├── bindings │ ├── Disposable.swift │ ├── GroupDisposable.swift │ └── Variable.swift ├── events │ ├── Event.swift │ ├── PanEvent.swift │ ├── PinchEvent.swift │ ├── RotateEvent.swift │ ├── TapEvent.swift │ └── TouchEvent.swift ├── export │ └── MacawView+PDF.swift ├── layout │ └── ContentLayout.swift ├── model │ ├── draw │ │ ├── Align.swift │ │ ├── AlphaEffect.swift │ │ ├── AspectRatio.swift │ │ ├── Baseline.swift │ │ ├── BlendEffect.swift │ │ ├── Color.swift │ │ ├── ColorMatrix.swift │ │ ├── ColorMatrixEffect.swift │ │ ├── Drawable.swift │ │ ├── Effect.swift │ │ ├── Fill.swift │ │ ├── Font.swift │ │ ├── GaussianBlur.swift │ │ ├── Gradient.swift │ │ ├── LineCap.swift │ │ ├── LineJoin.swift │ │ ├── LinearGradient.swift │ │ ├── OffsetEffect.swift │ │ ├── Pattern.swift │ │ ├── RadialGradient.swift │ │ ├── Stop.swift │ │ └── Stroke.swift │ ├── geom2d │ │ ├── Arc.swift │ │ ├── Circle.swift │ │ ├── Ellipse.swift │ │ ├── GeomUtils.swift │ │ ├── Insets.swift │ │ ├── Line.swift │ │ ├── Locus.swift │ │ ├── MoveTo.swift │ │ ├── Path.swift │ │ ├── PathBuilder.swift │ │ ├── PathSegment.swift │ │ ├── PathSegmentType.swift │ │ ├── Point.swift │ │ ├── Polygon.swift │ │ ├── Polyline.swift │ │ ├── Rect.swift │ │ ├── RoundRect.swift │ │ ├── Size.swift │ │ ├── Transform.swift │ │ └── TransformedLocus.swift │ └── scene │ │ ├── Group.swift │ │ ├── Image.swift │ │ ├── Node.swift │ │ ├── SceneUtils.swift │ │ ├── Shape.swift │ │ └── Text.swift ├── platform │ ├── MDisplayLink.swift │ ├── iOS │ │ ├── Common_iOS.swift │ │ ├── Graphics_iOS.swift │ │ ├── MDisplayLink_iOS.swift │ │ └── MView_iOS.swift │ └── macOS │ │ ├── Common_macOS.swift │ │ ├── Graphics_macOS.swift │ │ ├── MBezierPath+Extension_macOS.swift │ │ ├── MDisplayLink_macOS.swift │ │ └── MView_macOS.swift ├── render │ ├── GroupRenderer.swift │ ├── ImageRenderer.swift │ ├── NodeRenderer.swift │ ├── RenderContext.swift │ ├── RenderUtils.swift │ ├── ShapeRenderer.swift │ └── TextRenderer.swift ├── svg │ ├── CSSParser.swift │ ├── SVGCanvas.swift │ ├── SVGConstants.swift │ ├── SVGNodeLayout.swift │ ├── SVGParser.swift │ ├── SVGParserError.swift │ ├── SVGSerializer.swift │ └── SVGView.swift ├── thirdparty │ ├── CAAnimationClosure.swift │ ├── CGFloat+Double.swift │ └── NSTimer+Closure.swift ├── utils │ ├── BoundsUtils.swift │ ├── CGMappings.swift │ ├── CommonError.swift │ ├── DescriptionExtensions.swift │ └── UIImage2Image.swift └── views │ ├── MacawView.swift │ ├── MacawZoom.swift │ ├── ShapeLayer.swift │ └── Touchable.swift ├── demo.gif ├── header.png ├── logo.png └── macaw-logo.svg /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | .build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | Pods/ 21 | contents.xcworkspacedata 22 | .DS_Store 23 | .Macaw.podspec.swp 24 | .Package.swift.swp 25 | Podfile.lock 26 | Package.resolved 27 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.3 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode11.5 3 | 4 | branches: 5 | only: 6 | - master 7 | 8 | script: 9 | - 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; 10 | - set -o pipefail && xcodebuild test -project Macaw.xcodeproj -scheme 'MacawOSX' ONLY_ACTIVE_ARCH=NO | xcpretty; 11 | 12 | 13 | notifications: 14 | slack: exyte:kdo9FNtTOFMuMqAyyvZPDAD7 15 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "drmohundro/SWXMLHash" ~> 4.7.5 -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "drmohundro/SWXMLHash" "4.7.6" 2 | -------------------------------------------------------------------------------- /Dependencies/SWXMLHash/shim.swift: -------------------------------------------------------------------------------- 1 | // 2 | // shim.swift 3 | // SWXMLHash 4 | // 5 | // Copyright (c) 2018 David Mohundro 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #if (!swift(>=4.1) && swift(>=4.0)) || !swift(>=3.3) 27 | 28 | extension Sequence { 29 | func compactMap( 30 | _ transform: (Self.Element 31 | ) throws -> ElementOfResult?) rethrows -> [ElementOfResult] { 32 | try flatMap(transform) 33 | } 34 | } 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /Example-macOS/Example-macOS.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example-macOS/Example-macOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example-macOS 4 | // 5 | // Created by Daniil Manin on 8/11/17. 6 | // 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | } 19 | 20 | func applicationWillTerminate(_ aNotification: Notification) { 21 | // Insert code here to tear down your application 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /Example-macOS/Example-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Example-macOS/Example-macOS/ExampleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleViewController.swift 3 | // Example-macOS 4 | // 5 | // Created by Daniil Manin on 9/28/18. 6 | // 7 | 8 | import Cocoa 9 | 10 | class ExampleViewController: NSViewController { 11 | 12 | override func awakeFromNib() { 13 | super.awakeFromNib() 14 | self.view.wantsLayer = true 15 | self.view.layer?.backgroundColor = .white 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example-macOS/Example-macOS/Examples/Animations/AnimationsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationsViewController.swift 3 | // Example-macOS 4 | // 5 | // Created by Daniil Manin on 8/15/17. 6 | // 7 | // 8 | 9 | import Cocoa 10 | 11 | class AnimationsViewController: NSViewController { 12 | 13 | @IBOutlet weak var animationsView: AnimationsView! 14 | @IBOutlet weak var actionButton: NSButton! 15 | 16 | override func viewWillAppear() { 17 | super.viewWillAppear() 18 | 19 | animationsView?.onComplete = { 20 | self.actionButton?.isEnabled = true 21 | } 22 | animationsView?.prepareAnimation() 23 | 24 | let attributes: [NSAttributedString.Key: Any] = [.foregroundColor: NSColor.blue.withAlphaComponent(0.5)] 25 | actionButton.attributedTitle = NSMutableAttributedString(string: actionButton.title, attributes: attributes) 26 | } 27 | 28 | override func viewDidDisappear() { 29 | super.viewDidDisappear() 30 | self.actionButton?.isHidden = false 31 | } 32 | 33 | @IBAction func startAnimationsAction(_ sender: Any) { 34 | actionButton?.isHidden = true 35 | animationsView?.startAnimation() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Example-macOS/Example-macOS/Examples/Easing/EasingView.swift: -------------------------------------------------------------------------------- 1 | import Macaw 2 | 3 | class EasingView: MacawView { 4 | var mAnimations = [Animation]() 5 | var circlesNodes = [Group]() 6 | var animation: Animation! 7 | 8 | required init?(coder aDecoder: NSCoder) { 9 | let centerX = 250.0 10 | 11 | let fromStroke = Stroke(fill: Color.black, width: 3) 12 | let all = [Easing.ease, Easing.easeIn, Easing.easeOut, Easing.easeInOut, Easing.elasticInOut] 13 | let titles = ["Easing", "Easy In", "Easy Out", "Easy In Out", "Elastic In Out"] 14 | 15 | for (index, easing) in all.enumerated() { 16 | let y = Double(80 + index * 80) 17 | let titleText = Text(text: titles[index], align: .mid, place: .move(dx: centerX, dy: y - 45)) 18 | 19 | let fromCircle = Circle(cx: centerX - 100, cy: y, r: 20).stroke(with: fromStroke) 20 | let toPlace = fromCircle.place.move(dx: 200, dy: 0) 21 | 22 | let toAnimation = fromCircle.placeVar.animation(to: toPlace).easing(easing) 23 | 24 | mAnimations.append([toAnimation.autoreversed()].sequence()) 25 | circlesNodes.append(Group(contents: [titleText])) 26 | circlesNodes.append(Group(contents: [fromCircle])) 27 | } 28 | 29 | animation = mAnimations.combine().cycle() 30 | super.init(node: circlesNodes.group(), coder: aDecoder) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Example-macOS/Example-macOS/Examples/Easing/EasingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasingViewController.swift 3 | // Example-macOS 4 | // 5 | // Created by Daniil Manin on 8/15/17. 6 | // 7 | // 8 | 9 | import Cocoa 10 | 11 | class EasingViewController: NSViewController { 12 | @IBOutlet weak var easingView: EasingView! 13 | 14 | override func viewDidAppear() { 15 | super.viewDidAppear() 16 | easingView.animation.play() 17 | } 18 | 19 | override func viewWillDisappear() { 20 | super.viewDidDisappear() 21 | easingView.animation.stop() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Example-macOS/Example-macOS/Examples/Morphing/MorphingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MorphingView.swift 3 | // Example 4 | // 5 | // Created by Victor Sukochev on 25/01/2017. 6 | // Copyright © 2017 Exyte. All rights reserved. 7 | // 8 | 9 | import Macaw 10 | 11 | class MorphingView: MacawView { 12 | 13 | required init?(coder aDecoder: NSCoder) { 14 | super.init(node: MorphingView.newScene(), coder: aDecoder) 15 | } 16 | 17 | class func newScene() -> Node { 18 | let stroke = Stroke(width: 15.0, cap: .round) 19 | 20 | let contents1 = [ 21 | Shape(form: Line(x1: 50.0, y1: 50.0, x2: 75.0, y2: 25.0), stroke: stroke), 22 | Shape(form: Line(x1: 50.0, y1: 50.0, x2: 125.0, y2: 50.0), stroke: stroke), 23 | Shape(form: Line(x1: 50.0, y1: 50.0, x2: 75.0, y2: 75.0), stroke: stroke), 24 | ] 25 | 26 | let contents2 = [ 27 | Shape(form: Line(x1: 50.0, y1: 10.0, x2: 165.0, y2: 10.0), stroke: stroke), 28 | Shape(form: Line(x1: 50.0, y1: 50.0, x2: 165.0, y2: 50.0), stroke: stroke), 29 | Shape(form: Line(x1: 50.0, y1: 90.0, x2: 165.0, y2: 90.0), stroke: stroke), 30 | ] 31 | 32 | var switched = false 33 | let group = contents1.group() 34 | group.onTap { e in 35 | group.contentsVar.animate(to: switched ? contents1 : contents2) 36 | switched = !switched 37 | } 38 | return group 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Example-macOS/Example-macOS/Examples/SVG/SVGExampleViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Macaw 3 | 4 | class SVGExampleView: MacawView { 5 | 6 | required init?(coder aDecoder: NSCoder) { 7 | super.init(node: SVGParser.parse(path: "tiger"), coder: aDecoder) 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /Example-macOS/Example-macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainStoryboardFile 26 | Main 27 | NSPrincipalClass 28 | NSApplication 29 | NSAppleEventsUsageDescription 30 | Example 31 | 32 | 33 | -------------------------------------------------------------------------------- /Example-macOS/Example-macOS/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Example-macOS 4 | // 5 | // Created by Daniil Manin on 8/11/17. 6 | // 7 | // 8 | 9 | import Cocoa 10 | import Macaw 11 | 12 | class ViewController: NSViewController { 13 | 14 | @IBOutlet weak var mainView: EasingView! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Example-macOS/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.12' 2 | use_frameworks! 3 | 4 | target 'Example-macOS' do 5 | pod 'Macaw', :path => '../' 6 | end 7 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Created by Igor Zapletnev on 12/8/15. 6 | // Copyright © 2015 Exyte. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Macaw 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Example/Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Example/Examples/Animations/AnimationsExampleController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class AnimationsExampleController: UIViewController { 4 | 5 | @IBOutlet weak var animView: AnimationsView? 6 | @IBOutlet weak var actionButton: UIButton? 7 | 8 | override func viewWillAppear(_ animated: Bool) { 9 | super.viewWillAppear(animated) 10 | 11 | actionButton?.layer.cornerRadius = 5 12 | 13 | animView?.onComplete = { 14 | self.actionButton?.isEnabled = true 15 | } 16 | animView?.prepareAnimation() 17 | actionButton?.addTarget(self, action: #selector(startAnimationAction), for: .touchUpInside) 18 | } 19 | 20 | @objc func startAnimationAction() { 21 | animView?.startAnimation() 22 | actionButton?.isEnabled = false 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Example/Example/Examples/Easing/EasingExampleController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class EasingExampleController: UIViewController { 4 | 5 | @IBOutlet weak var easingView: EasingView! 6 | 7 | override func viewWillAppear(_ animated: Bool) { 8 | super.viewWillAppear(animated) 9 | easingView.animation.play() 10 | } 11 | 12 | override func viewWillDisappear(_ animated: Bool) { 13 | super.viewWillDisappear(animated) 14 | easingView.animation.stop() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/Example/Examples/Easing/EasingView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Macaw 3 | 4 | class EasingView: MacawView { 5 | 6 | var animations = [Animation]() 7 | var circlesNodes = [Group]() 8 | var animation: Animation! 9 | 10 | required init?(coder aDecoder: NSCoder) { 11 | let screenSize: CGRect = UIScreen.main.bounds 12 | let centerX = Double(screenSize.width / 2) 13 | 14 | let fromStroke = Stroke(fill: Color.black, width: 3) 15 | let toStroke = Stroke(fill: Color.black, width: 1, dashes: [3, 3]) 16 | 17 | let all = [Easing.ease, Easing.linear, Easing.easeIn, Easing.easeOut, Easing.easeInOut, Easing.elasticInOut] 18 | 19 | for (i, easing) in all.enumerated() { 20 | let y = Double(150 + i * 75) 21 | let title = String(describing: type(of: easing)) 22 | let titleText = Text(text: title, align: .mid, place: .move(dx: centerX, dy: y - 45)) 23 | 24 | let fromCircle = Circle(cx: centerX - 100, cy: y, r: 25).stroke(with: fromStroke) 25 | let toCircle = Circle(cx: centerX + 100, cy: y, r: 25).stroke(with: toStroke) 26 | let toPlace = fromCircle.place.move(dx: 200, dy: 0) 27 | 28 | let toAnimation = fromCircle.placeVar.animation(to: toPlace).easing(easing) 29 | 30 | animations.append([toAnimation.autoreversed()].sequence()) 31 | circlesNodes.append(Group(contents: [titleText, fromCircle, toCircle])) 32 | } 33 | 34 | animation = animations.combine().cycle() 35 | super.init(node: circlesNodes.group(), coder: aDecoder) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Example/Example/Examples/Filters/FiltersViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FiltersViewController.swift 3 | // Example 4 | // 5 | // Created by Alisa Mylnikova on 04/06/2018. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | 9 | import Macaw 10 | 11 | class FiltersViewController: UIViewController { 12 | 13 | @IBOutlet weak var macawView: MacawView! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | let yellowRect = Shape(form: Rect(x: 180, y: 30, w: 90, h: 90), fill: Color.yellow, stroke: Stroke(fill: Color.green, width: 3), effect: Effect.dropShadow()) 18 | 19 | let stop1 = Stop(offset: 0, color: Color.blue.with(a: 0.3)) 20 | let stop2 = Stop(offset: 1, color: Color.purple) 21 | let gradient = LinearGradient(stops: [stop1, stop2]) 22 | let rect = Shape(form: Rect(x: 150, y: 200, w: 90, h: 90), fill: gradient, stroke: Stroke(fill: Color.green, width: 3)) 23 | rect.effect = OffsetEffect(dx: 20, dy: 20).mapColor( 24 | with: ColorMatrix(values: [0.33, 0, 0, 0, 0.33, 25 | 0.5, 0.5, 0, 0, 0, 26 | 0.33, 0.33, 0.33, 0, 1, 27 | 1, 1, 1, 1, 0])).blur(r: 4).blend() 28 | 29 | let circle = Shape(form: Circle(cx: 30, cy: 70, r: 50), fill: Color.navy, place: Transform(m11: 1, m12: 0, m21: 0, m22: 1, dx: 50, dy: 50)) 30 | circle.effect = .dropShadow(dx: 10, dy: 10, r: 5, color: .teal) 31 | macawView.node = Group(contents: [yellowRect, rect, circle]) 32 | 33 | } 34 | 35 | override func didReceiveMemoryWarning() { 36 | super.didReceiveMemoryWarning() 37 | // Dispose of any resources that can be recreated. 38 | } 39 | 40 | 41 | /* 42 | // MARK: - Navigation 43 | 44 | // In a storyboard-based application, you will often want to do a little preparation before navigation 45 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 46 | // Get the new view controller using segue.destinationViewController. 47 | // Pass the selected object to the new view controller. 48 | } 49 | */ 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Example/Example/Examples/Morphing/MorphingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MorphingView.swift 3 | // Example 4 | // 5 | // Created by Victor Sukochev on 25/01/2017. 6 | // Copyright © 2017 Exyte. All rights reserved. 7 | // 8 | 9 | import Macaw 10 | 11 | class MorphingView: MacawView { 12 | 13 | required init?(coder aDecoder: NSCoder) { 14 | super.init(node: MorphingView.newScene(), coder: aDecoder) 15 | } 16 | 17 | class func newScene() -> Node { 18 | let stroke = Stroke(width: 15.0, cap: .round) 19 | 20 | let contents1 = [ 21 | Shape(form: Line(x1: 150.0, y1: 150.0, x2: 175.0, y2: 125.0), stroke: stroke), 22 | Shape(form: Line(x1: 150.0, y1: 150.0, x2: 225.0, y2: 150.0), stroke: stroke), 23 | Shape(form: Line( x1: 150.0, y1: 150.0, x2: 175.0, y2: 175.0), stroke: stroke), 24 | ] 25 | 26 | let contents2 = [ 27 | Shape(form: Line(x1: 130.0, y1: 110.0, x2: 245.0, y2: 110.0), stroke: stroke), 28 | Shape(form: Line(x1: 130.0, y1: 150.0, x2: 245.0, y2: 150.0), stroke: stroke), 29 | Shape(form: Line(x1: 130.0, y1: 190.0, x2: 245.0, y2: 190.0), stroke: stroke), 30 | ] 31 | 32 | var switched = false 33 | let group = contents1.group() 34 | group.onTap { e in 35 | group.contentsVar.animate(to: switched ? contents1 : contents2) 36 | switched = !switched 37 | } 38 | return group 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Example/Example/Examples/Text/TextsExampleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextsExampleView.swift 3 | // Example 4 | // 5 | // Created by Andrew Romanov on 28/02/2019. 6 | // Copyright © 2019 Exyte. All rights reserved. 7 | // 8 | 9 | import Macaw 10 | 11 | class TextsExampleView: MacawView { 12 | 13 | required init?(coder aDecoder: NSCoder) { 14 | let text1 = TextsExampleView.newText("Font", .move(dx: 100, dy: 40)) 15 | text1.font = Font(name: "Helvetica", size: 20, weight: "normal") 16 | 17 | let text2 = TextsExampleView.newText("Stroke", .move(dx: 100, dy: 200)) 18 | text2.font = Font(name: "Helvetica", size: 40, weight: "normal") 19 | text2.fill = Color(val: 0xFF0000); 20 | text2.stroke = Stroke(fill: Color(val: 0x00FF00), width: 20.0); 21 | 22 | let text4 = TextsExampleView.newText("Stroke", .move(dx: 100, dy: 200)) 23 | text4.font = Font(name: "Helvetica", size: 40, weight: "normal") 24 | text4.fill = Color(val: 0xFF0000); 25 | 26 | let text3 = TextsExampleView.newText("Kern inc", .move(dx: 100, dy: 250)) 27 | text3.kerning = 3.0 28 | 29 | let text5 = TextsExampleView.newText("Kern dec", .move(dx: 100, dy: 300)) 30 | text5.kerning = -1.0 31 | 32 | let group = Group( 33 | contents: [ 34 | text1, text2, text3, text4, text5 35 | ] 36 | ) 37 | 38 | super.init(node: group, coder: aDecoder) 39 | } 40 | 41 | fileprivate static func newText(_ text: String, _ place: Transform, baseline: Baseline = .bottom) -> Text { 42 | return Text(text: text, fill: Color.black, align: .mid, baseline: baseline, place: place) 43 | } 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/Example/Examples/TransformExampleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransformExampleView.swift 3 | // Example 4 | // 5 | // Created by Yuri Strot on 9/10/16. 6 | // Copyright © 2016 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import UIKit 12 | import Macaw 13 | 14 | class TransformExampleView: MacawView { 15 | 16 | fileprivate static let transforms = [Transform.scale(sx: 2, sy: 2), 17 | Transform.move(dx: 100, dy: 30), 18 | Transform.rotate(angle: Double.pi / 4.0, x: 150, y: 80), 19 | Transform.rotate(angle: Double.pi / 4.0)] 20 | 21 | fileprivate static let titles = ["Transform.scale(sx: 2, sy: 2)", 22 | "Transform.move(dx: 100, dy: 30)", 23 | "Transform.rotate(angle: M_PI_4, x: 150, y: 80)", 24 | "Transform.rotate(angle: M_PI_4)"] 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | super.init(node: TransformExampleView.newScene(), coder: aDecoder) 28 | } 29 | 30 | fileprivate static func newScene() -> Node { 31 | let shape = Shape(form: Rect(x: 0, y: 0, w: 50, h: 50), fill: Color.blue) 32 | let textes = Group(place: .move(dx: 50, dy: 275)) 33 | for (i, item) in titles.enumerated() { 34 | let place = Transform.move(dx: 0, dy: Double(i * 25)) 35 | textes.contents.append(Text(text: item, baseline: .bottom, place: place, opacity: 0)) 36 | } 37 | var combines: [Transform] = [Transform.identity] 38 | for transform in transforms { 39 | combines.append(transform.concat(with: combines.last!)) 40 | } 41 | var state = 0 42 | 43 | shape.onTouchPressed { _ in 44 | if (state < textes.contents.count) { 45 | textes.contents[state].opacityVar.animate(from: 0.0, to: 1.0, during: 0.6) 46 | } else { 47 | for item in textes.contents { 48 | item.opacityVar.animate(from: 1.0, to: 0.0, during: 0.6) 49 | } 50 | } 51 | state = (state + 1) % 5; 52 | shape.placeVar.animate(to: combines[state], during: 0.6) 53 | } 54 | 55 | return Group(contents: [newAxes(), shape, textes], place: .move(dx: 10, dy: 10)) 56 | } 57 | 58 | fileprivate static func newAxes() -> Node { 59 | var items: [Node] = [] 60 | let gray = Color(val: 0xF0F0F0) 61 | for i in 1...20 { 62 | let shift = Double(i) * 50.0 63 | items.append(Line(x1: -50, y1: shift, x2: 1000, y2: shift).stroke(fill: gray)) 64 | items.append(Line(x1: shift, y1: -50, x2: shift, y2: 1000).stroke(fill: gray)) 65 | } 66 | items.append(Line(x1: -10, y1: 0, x2: 1000, y2: 0).stroke()) 67 | items.append(Line(x1: 0, y1: -10, x2: 0, y2: 1000).stroke()) 68 | return Group(contents: items) 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationPortraitUpsideDown 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/Example/MenuViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | open class MenuViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { 4 | 5 | @IBOutlet var tableView: UITableView! 6 | 7 | fileprivate var viewControllers = [ 8 | "FirstPageViewController", 9 | "TransformExampleController", 10 | "AnimationsExampleController", 11 | "SVGViewController", 12 | "EasingExampleController", 13 | "MorphingExampleController", 14 | "EventsExampleController", 15 | "FiltersViewController", 16 | "TextsViewController", 17 | "AnimationsHierarchyViewController", 18 | "PathAnimationViewController" 19 | ].map { 20 | UIStoryboard(name: "Main", bundle: .none).instantiateViewController(withIdentifier: $0) 21 | } 22 | 23 | open override func viewWillAppear(_ animated: Bool) { 24 | super.viewWillAppear(animated) 25 | 26 | tableView?.reloadData() 27 | } 28 | 29 | open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 30 | return viewControllers.count 31 | } 32 | 33 | open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 34 | let cell = tableView.dequeueReusableCell(withIdentifier: "menu_cell")! 35 | cell.textLabel?.text = viewControllers[indexPath.row].title 36 | return cell 37 | } 38 | 39 | open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 40 | self.navigationController?.pushViewController(viewControllers[indexPath.row], animated: true) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | use_frameworks! 3 | 4 | target 'Example' do 5 | pod 'Macaw', :path => '../' 6 | end 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 exyte 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Macaw.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint Macaw.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = "Macaw" 11 | s.version = "0.9.10" 12 | s.summary = "Powerful and easy-to-use vector graphics library with SVG support written in Swift." 13 | 14 | s.homepage = 'https://github.com/exyte/Macaw.git' 15 | s.license = 'MIT' 16 | s.author = { 'Exyte' => 'info@exyte.com' } 17 | s.source = { :git => 'https://github.com/exyte/Macaw.git', :tag => s.version.to_s } 18 | s.social_media_url = 'http://exyte.com' 19 | 20 | s.ios.deployment_target = "9.0" 21 | s.osx.deployment_target = "10.12" 22 | s.requires_arc = true 23 | s.swift_version = "5.3" 24 | 25 | s.source_files = [ 26 | 'Source/**/*.swift' 27 | ] 28 | 29 | s.dependency 'SWXMLHash' 30 | end 31 | -------------------------------------------------------------------------------- /Macaw.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MacawTests/Animation/AnimationUtilsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationUtilsTests.swift 3 | // Macaw 4 | // 5 | // Created by Victor Sukochev on 28/04/2017. 6 | // Copyright © 2017 Exyte. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | #if os(OSX) 12 | @testable import MacawOSX 13 | #elseif os(iOS) 14 | @testable import Macaw 15 | #endif 16 | 17 | class AnimationUtilsTests: XCTestCase { 18 | 19 | func testIndex() { 20 | let shape = Circle(cx: 0, cy: 0, r: 10) 21 | 22 | let rootGroup = Group() 23 | let a = Shape(form: shape) 24 | rootGroup.contents.append(a) 25 | 26 | let bGroup = Group() 27 | let c = Shape(form: shape) 28 | let d = Shape(form: shape) 29 | bGroup.contents.append(c) 30 | bGroup.contents.append(d) 31 | rootGroup.contents.append(bGroup) 32 | 33 | let e = Shape(form: shape) 34 | let f = Shape(form: shape) 35 | rootGroup.contents.append(e) 36 | rootGroup.contents.append(f) 37 | 38 | let view = MacawView() 39 | view.node = rootGroup 40 | view.draw(CGRect(x: 0, y: 0, width: 100, height: 100)) 41 | let rootRenderer = view.renderer as? GroupRenderer 42 | let aRenderer = rootRenderer?.renderers[0] 43 | let bRenderer = rootRenderer?.renderers[1] as? GroupRenderer 44 | let cRenderer = bRenderer?.renderers[0] 45 | let dRenderer = bRenderer?.renderers[1] 46 | let eRenderer = rootRenderer?.renderers[2] 47 | let fRenderer = rootRenderer?.renderers[3] 48 | 49 | XCTAssert(rootRenderer?.zPosition == 0) 50 | XCTAssert(aRenderer?.zPosition == 1) 51 | XCTAssert(bRenderer?.zPosition == 2) 52 | XCTAssert(cRenderer?.zPosition == 3) 53 | XCTAssert(dRenderer?.zPosition == 4) 54 | XCTAssert(eRenderer?.zPosition == 5) 55 | XCTAssert(fRenderer?.zPosition == 6) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /MacawTests/Animation/DelayedAnimationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DelayedAnimationTests.swift 3 | // Macaw 4 | // 5 | // Created by Victor Sukochev on 21/02/2017. 6 | // Copyright © 2017 Exyte. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | #if os(OSX) 12 | @testable import MacawOSX 13 | #elseif os(iOS) 14 | @testable import Macaw 15 | #endif 16 | 17 | class DelayedAnimationTests: XCTestCase { 18 | 19 | var testView: MacawView! 20 | var testGroup: Group! 21 | var window: MWindow! 22 | 23 | override func setUp() { 24 | super.setUp() 25 | 26 | testGroup = [Shape(form:Rect(x: 0.0, y: 0.0, w: 0.0, h: 0.0))].group() 27 | testView = MacawView(node: testGroup, frame: .zero) 28 | 29 | window = MWindow() 30 | window.addSubview(testView) 31 | } 32 | 33 | func testStates() { 34 | let animation = testGroup.placeVar.animation(to: Transform.move(dx: 1.0, dy: 1.0), delay: 1000.0) as! TransformAnimation 35 | animation.play() 36 | 37 | let playExpectation = expectation(description: "play expectation") 38 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 39 | playExpectation.fulfill() 40 | } 41 | 42 | self.waitForExpectations(timeout: 2.0) { error in 43 | XCTAssertNil(error, "Async test failed") 44 | } 45 | 46 | animation.pause() 47 | let pauseExpectation = expectation(description: "pause expectation") 48 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 49 | pauseExpectation.fulfill() 50 | } 51 | 52 | self.waitForExpectations(timeout: 2.0) { error in 53 | XCTAssertNil(error, "Async test failed") 54 | } 55 | 56 | XCTAssert(animation.paused && !animation.manualStop, "Wrong animation state on pause") 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MacawTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /MacawTests/MacawTests.bundle/circle.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/MacawTests.bundle/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MacawTests/SVGParserTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGParserTest.swift 3 | // MacawTests 4 | // 5 | // Created by Julius Lundang on 19/08/2018. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | #if os(OSX) 12 | @testable import MacawOSX 13 | #elseif os(iOS) 14 | @testable import Macaw 15 | #endif 16 | 17 | class SVGParserTest: XCTestCase { 18 | func testParseFromOtherBundle() { 19 | let bundle = Bundle(for: type(of: TestUtils())) 20 | let bundleMacawTestsURL = bundle.resourceURL?.appendingPathComponent("MacawTests.bundle") 21 | let macawTestsBundle = Bundle(url: bundleMacawTestsURL!)! 22 | do { 23 | let node = try SVGParser.parse(resource: "circle", fromBundle: macawTestsBundle) 24 | XCTAssertNotNil(node) 25 | if let fullPath = macawTestsBundle.path(forResource: "circle", ofType: "svg") { 26 | let node2 = try SVGParser.parse(fullPath: fullPath) 27 | XCTAssertNotNil(node2) 28 | } else { 29 | XCTFail("No circle.svg found") 30 | } 31 | } catch { 32 | XCTFail(error.localizedDescription) 33 | } 34 | } 35 | 36 | func testParseGivenInvalidPath() { 37 | let fullPath = "invalid fullPath" 38 | XCTAssertThrowsError(try SVGParser.parse(fullPath: fullPath)) { error in 39 | XCTAssertEqual(error as! SVGParserError, SVGParserError.noSuchFile(path: "invalid fullPath")) 40 | } 41 | } 42 | 43 | func testParseGiventEmptyPath() { 44 | XCTAssertThrowsError(try SVGParser.parse(fullPath: "")) { error in 45 | XCTAssertEqual(error as! SVGParserError, SVGParserError.noSuchFile(path: "")) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MacawTests/png/color-prop-01-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/color-prop-01-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/color-prop-03-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/color-prop-03-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/color-prop-04-t-manual-osx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/color-prop-04-t-manual-osx.png -------------------------------------------------------------------------------- /MacawTests/png/color-prop-05-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/color-prop-05-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-coord-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-coord-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-coord-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-coord-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-01-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-01-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-03-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-03-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-04-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-04-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-05-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-05-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-06-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-06-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-07-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-07-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-08-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-08-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-09-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-09-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-10-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-10-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-11-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-11-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-12-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-12-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-13-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-13-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-trans-14-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-trans-14-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-transformattr-01-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-transformattr-01-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-transformattr-02-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-transformattr-02-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-transformattr-03-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-transformattr-03-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-transformattr-04-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-transformattr-04-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/coords-transformattr-05-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/coords-transformattr-05-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/masking-filter-01-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/masking-filter-01-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/masking-intro-01-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/masking-intro-01-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/masking-mask-02-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/masking-mask-02-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/masking-path-02-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/masking-path-02-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/masking-path-13-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/masking-path-13-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/metadata-example-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/metadata-example-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-control-01-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-control-01-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-control-02-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-control-02-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-control-03-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-control-03-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-control-06-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-control-06-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-fill-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-fill-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-fill-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-fill-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-fill-03-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-fill-03-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-fill-04-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-fill-04-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-fill-05-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-fill-05-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-stroke-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-stroke-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-stroke-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-stroke-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-stroke-03-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-stroke-03-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-stroke-04-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-stroke-04-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-stroke-05-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-stroke-05-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-stroke-06-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-stroke-06-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-stroke-07-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-stroke-07-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-stroke-08-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-stroke-08-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/painting-stroke-09-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/painting-stroke-09-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-03-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-03-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-04-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-04-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-05-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-05-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-06-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-06-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-07-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-07-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-08-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-08-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-09-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-09-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-10-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-10-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-12-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-12-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-13-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-13-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-14-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-14-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-15-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-15-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-16-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-16-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-17-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-17-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-18-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-18-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-19-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-19-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/paths-data-20-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/paths-data-20-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-01-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-01-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-02-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-02-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-03-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-03-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-07-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-07-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-09-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-09-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-12-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-12-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-13-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-13-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-15-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-15-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-22-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-22-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-23-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-23-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-24-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-24-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/pservers-grad-stops-01-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/pservers-grad-stops-01-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/render-elems-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/render-elems-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/render-elems-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/render-elems-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/render-elems-03-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/render-elems-03-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-circle-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-circle-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-circle-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-circle-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-ellipse-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-ellipse-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-ellipse-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-ellipse-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-ellipse-03-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-ellipse-03-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-grammar-01-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-grammar-01-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-intro-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-intro-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-line-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-line-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-line-02-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-line-02-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-polygon-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-polygon-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-polygon-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-polygon-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-polygon-03-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-polygon-03-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-polyline-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-polyline-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-polyline-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-polyline-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-rect-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-rect-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-rect-03-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-rect-03-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-rect-04-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-rect-04-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-rect-05-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-rect-05-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-rect-06-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-rect-06-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/shapes-rect-07-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/shapes-rect-07-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/struct-defs-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/struct-defs-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/struct-frag-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/struct-frag-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/struct-frag-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/struct-frag-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/struct-frag-03-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/struct-frag-03-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/struct-frag-04-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/struct-frag-04-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/struct-frag-06-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/struct-frag-06-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/struct-group-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/struct-group-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/struct-use-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/struct-use-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/struct-use-03-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/struct-use-03-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/struct-use-12-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/struct-use-12-f-manual.png -------------------------------------------------------------------------------- /MacawTests/png/text-align-01-b-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/text-align-01-b-manual.png -------------------------------------------------------------------------------- /MacawTests/png/text-fonts-01-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/text-fonts-01-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/text-fonts-02-t-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/text-fonts-02-t-manual.png -------------------------------------------------------------------------------- /MacawTests/png/types-basic-01-f-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/png/types-basic-01-f-manual.png -------------------------------------------------------------------------------- /MacawTests/svg/arcsgroup.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/circle.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MacawTests/svg/clearColor.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/clip.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/clip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /MacawTests/svg/clipManual.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/ellipse.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/ellipse.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MacawTests/svg/group.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/group.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MacawTests/svg/line.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/line.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MacawTests/svg/polygon.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/polygon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MacawTests/svg/polyline.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/polyline.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MacawTests/svg/rect.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/rect.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MacawTests/svg/roundRect.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/roundRect.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MacawTests/svg/rounded.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/rounded.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /MacawTests/svg/small-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/MacawTests/svg/small-logo.png -------------------------------------------------------------------------------- /MacawTests/svg/style.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/style.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MacawTests/svg/svglist.txt: -------------------------------------------------------------------------------- 1 | ellipse 2 | -------------------------------------------------------------------------------- /MacawTests/svg/textBasicTransform.reference: -------------------------------------------------------------------------------- 1 | Point -------------------------------------------------------------------------------- /MacawTests/svg/transform.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/transform.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /MacawTests/svg/triangle.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MacawTests/svg/viewBox.reference: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MacawTests/svg/viewBox.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/color-prop-05-t-manual.reference: -------------------------------------------------------------------------------- 1 | { 2 | "contents" : [ 3 | { 4 | "contents" : [ 5 | { 6 | "contents" : [ 7 | { 8 | "fill" : { 9 | "type" : "Color", 10 | "val" : 65280 11 | }, 12 | "form" : { 13 | "h" : 150, 14 | "type" : "Rect", 15 | "w" : 150, 16 | "x" : 120, 17 | "y" : 60 18 | }, 19 | "node" : "Shape" 20 | } 21 | ], 22 | "node" : "Group" 23 | } 24 | ], 25 | "node" : "Group" 26 | }, 27 | { 28 | "contents" : [ 29 | { 30 | "align" : "min", 31 | "baseline" : "bottom", 32 | "fill" : { 33 | "type" : "Color", 34 | "val" : 0 35 | }, 36 | "font" : { 37 | "name" : "SVGFreeSansASCII,sans-serif", 38 | "size" : 32, 39 | "weight" : "normal" 40 | }, 41 | "node" : "Text", 42 | "place" : "1, 0, 0, 1, 10, 340", 43 | "text" : "$Revision: 1.8 $" 44 | } 45 | ], 46 | "node" : "Group" 47 | }, 48 | { 49 | "form" : { 50 | "h" : 358, 51 | "type" : "Rect", 52 | "w" : 478, 53 | "x" : 1, 54 | "y" : 1 55 | }, 56 | "node" : "Shape", 57 | "stroke" : { 58 | "cap" : "butt", 59 | "dashes" : [ 60 | 61 | ], 62 | "fill" : { 63 | "type" : "Color", 64 | "val" : 0 65 | }, 66 | "join" : "miter", 67 | "width" : 1 68 | } 69 | } 70 | ], 71 | "layout" : { 72 | "scalingMode" : "meet", 73 | "svgSize" : { 74 | "height" : "100.0%", 75 | "width" : "100.0%" 76 | }, 77 | "viewBox" : { 78 | "h" : 360, 79 | "type" : "Rect", 80 | "w" : 480, 81 | "x" : 0, 82 | "y" : 0 83 | }, 84 | "xAligningMode" : "mid", 85 | "yAligningMode" : "mid" 86 | }, 87 | "node" : "Canvas" 88 | } -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/masking-mask-02-f-manual.reference: -------------------------------------------------------------------------------- 1 | { 2 | "contents" : [ 3 | { 4 | "contents" : [ 5 | { 6 | "fill" : { 7 | "type" : "Color", 8 | "val" : 32768 9 | }, 10 | "form" : { 11 | "h" : 150, 12 | "type" : "Rect", 13 | "w" : 250, 14 | "x" : 100, 15 | "y" : 100 16 | }, 17 | "node" : "Shape" 18 | } 19 | ], 20 | "node" : "Group" 21 | }, 22 | { 23 | "contents" : [ 24 | { 25 | "align" : "min", 26 | "baseline" : "bottom", 27 | "fill" : { 28 | "type" : "Color", 29 | "val" : 0 30 | }, 31 | "font" : { 32 | "name" : "SVGFreeSansASCII,sans-serif", 33 | "size" : 32, 34 | "weight" : "normal" 35 | }, 36 | "node" : "Text", 37 | "place" : "1, 0, 0, 1, 10, 340", 38 | "text" : "$Revision: 1.3 $" 39 | } 40 | ], 41 | "node" : "Group" 42 | }, 43 | { 44 | "form" : { 45 | "h" : 358, 46 | "type" : "Rect", 47 | "w" : 478, 48 | "x" : 1, 49 | "y" : 1 50 | }, 51 | "node" : "Shape", 52 | "stroke" : { 53 | "cap" : "butt", 54 | "dashes" : [ 55 | 56 | ], 57 | "fill" : { 58 | "type" : "Color", 59 | "val" : 0 60 | }, 61 | "join" : "miter", 62 | "width" : 1 63 | } 64 | } 65 | ], 66 | "layout" : { 67 | "scalingMode" : "meet", 68 | "svgSize" : { 69 | "height" : "100.0%", 70 | "width" : "100.0%" 71 | }, 72 | "viewBox" : { 73 | "h" : 360, 74 | "type" : "Rect", 75 | "w" : 480, 76 | "x" : 0, 77 | "y" : 0 78 | }, 79 | "xAligningMode" : "mid", 80 | "yAligningMode" : "mid" 81 | }, 82 | "node" : "Canvas" 83 | } -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/masking-mask-02-f-manual.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

12 | If the 'mask' property references a 'mask' element containing no children, the element referencing it is not rendered. 13 |

14 | 15 | 16 |

Run the test. No interaction required.

17 |
18 | 19 |

20 | Test passes if there is a single green rectangle, with no red visible on the page. 21 |

22 |
23 | 24 | $RCSfile: masking-mask-02-f.svg,v $ 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | $Revision: 1.3 $ 42 | 43 | 44 | 50 | 51 | -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/masking-path-13-f-manual.reference: -------------------------------------------------------------------------------- 1 | { 2 | "contents" : [ 3 | { 4 | "contents" : [ 5 | { 6 | "fill" : { 7 | "type" : "Color", 8 | "val" : 32768 9 | }, 10 | "form" : { 11 | "h" : 100, 12 | "type" : "Rect", 13 | "w" : 200, 14 | "x" : 100, 15 | "y" : 100 16 | }, 17 | "node" : "Shape" 18 | } 19 | ], 20 | "node" : "Group" 21 | }, 22 | { 23 | "contents" : [ 24 | { 25 | "align" : "min", 26 | "baseline" : "bottom", 27 | "fill" : { 28 | "type" : "Color", 29 | "val" : 0 30 | }, 31 | "font" : { 32 | "name" : "SVGFreeSansASCII,sans-serif", 33 | "size" : 32, 34 | "weight" : "normal" 35 | }, 36 | "node" : "Text", 37 | "place" : "1, 0, 0, 1, 10, 340", 38 | "text" : "$Revision: 1.2 $" 39 | } 40 | ], 41 | "node" : "Group" 42 | }, 43 | { 44 | "form" : { 45 | "h" : 358, 46 | "type" : "Rect", 47 | "w" : 478, 48 | "x" : 1, 49 | "y" : 1 50 | }, 51 | "node" : "Shape", 52 | "stroke" : { 53 | "cap" : "butt", 54 | "dashes" : [ 55 | 56 | ], 57 | "fill" : { 58 | "type" : "Color", 59 | "val" : 0 60 | }, 61 | "join" : "miter", 62 | "width" : 1 63 | } 64 | } 65 | ], 66 | "layout" : { 67 | "scalingMode" : "meet", 68 | "svgSize" : { 69 | "height" : "100.0%", 70 | "width" : "100.0%" 71 | }, 72 | "viewBox" : { 73 | "h" : 360, 74 | "type" : "Rect", 75 | "w" : 480, 76 | "x" : 0, 77 | "y" : 0 78 | }, 79 | "xAligningMode" : "mid", 80 | "yAligningMode" : "mid" 81 | }, 82 | "node" : "Canvas" 83 | } -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/painting-stroke-09-t-manual.reference: -------------------------------------------------------------------------------- 1 | { 2 | "contents" : [ 3 | { 4 | "contents" : [ 5 | { 6 | "form" : { 7 | "segments" : [ 8 | { 9 | "data" : [ 10 | 50, 11 | 120 12 | ], 13 | "type" : "M" 14 | }, 15 | { 16 | "data" : [ 17 | 385 18 | ], 19 | "type" : "h" 20 | } 21 | ], 22 | "type" : "Path" 23 | }, 24 | "node" : "Shape", 25 | "stroke" : { 26 | "cap" : "butt", 27 | "dashes" : [ 28 | 25, 29 | 5, 30 | 5, 31 | 5 32 | ], 33 | "fill" : { 34 | "type" : "Color", 35 | "val" : 0 36 | }, 37 | "join" : "miter", 38 | "width" : 25 39 | } 40 | } 41 | ], 42 | "node" : "Group" 43 | }, 44 | { 45 | "contents" : [ 46 | { 47 | "align" : "min", 48 | "baseline" : "bottom", 49 | "fill" : { 50 | "type" : "Color", 51 | "val" : 0 52 | }, 53 | "font" : { 54 | "name" : "SVGFreeSansASCII,sans-serif", 55 | "size" : 32, 56 | "weight" : "normal" 57 | }, 58 | "node" : "Text", 59 | "place" : "1, 0, 0, 1, 10, 340", 60 | "text" : "$Revision: 1.7 $" 61 | } 62 | ], 63 | "node" : "Group" 64 | }, 65 | { 66 | "form" : { 67 | "h" : 358, 68 | "type" : "Rect", 69 | "w" : 478, 70 | "x" : 1, 71 | "y" : 1 72 | }, 73 | "node" : "Shape", 74 | "stroke" : { 75 | "cap" : "butt", 76 | "dashes" : [ 77 | 78 | ], 79 | "fill" : { 80 | "type" : "Color", 81 | "val" : 0 82 | }, 83 | "join" : "miter", 84 | "width" : 1 85 | } 86 | } 87 | ], 88 | "layout" : { 89 | "scalingMode" : "meet", 90 | "svgSize" : { 91 | "height" : "100.0%", 92 | "width" : "100.0%" 93 | }, 94 | "viewBox" : { 95 | "h" : 360, 96 | "type" : "Rect", 97 | "w" : 480, 98 | "x" : 0, 99 | "y" : 0 100 | }, 101 | "xAligningMode" : "mid", 102 | "yAligningMode" : "mid" 103 | }, 104 | "node" : "Canvas" 105 | } -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/pservers-grad-09-b-manual.reference: -------------------------------------------------------------------------------- 1 | { 2 | "contents" : [ 3 | { 4 | "contents" : [ 5 | { 6 | "align" : "min", 7 | "baseline" : "bottom", 8 | "fill" : { 9 | "type" : "Color", 10 | "val" : 0 11 | }, 12 | "font" : { 13 | "name" : "SVGFreeSansASCII,sans-serif", 14 | "size" : 15, 15 | "weight" : "normal" 16 | }, 17 | "node" : "Text", 18 | "place" : "1, 0, 0, 1, 10, 25", 19 | "text" : "Testing gradientUnits attribute" 20 | }, 21 | { 22 | "form" : { 23 | "h" : 50, 24 | "type" : "Rect", 25 | "w" : 200, 26 | "x" : 125, 27 | "y" : 35 28 | }, 29 | "node" : "Shape" 30 | }, 31 | { 32 | "form" : { 33 | "h" : 50, 34 | "type" : "Rect", 35 | "w" : 430, 36 | "x" : 10, 37 | "y" : 125 38 | }, 39 | "node" : "Shape" 40 | }, 41 | { 42 | "form" : { 43 | "h" : 430, 44 | "type" : "Rect", 45 | "w" : 50, 46 | "x" : 0, 47 | "y" : 0 48 | }, 49 | "node" : "Shape", 50 | "place" : "0, -1, 1, 0, 10, 260" 51 | } 52 | ], 53 | "node" : "Group" 54 | }, 55 | { 56 | "contents" : [ 57 | { 58 | "align" : "min", 59 | "baseline" : "bottom", 60 | "fill" : { 61 | "type" : "Color", 62 | "val" : 0 63 | }, 64 | "font" : { 65 | "name" : "SVGFreeSansASCII,sans-serif", 66 | "size" : 32, 67 | "weight" : "normal" 68 | }, 69 | "node" : "Text", 70 | "place" : "1, 0, 0, 1, 10, 340", 71 | "text" : "$Revision: 1.9 $" 72 | } 73 | ], 74 | "node" : "Group" 75 | }, 76 | { 77 | "form" : { 78 | "h" : 358, 79 | "type" : "Rect", 80 | "w" : 478, 81 | "x" : 1, 82 | "y" : 1 83 | }, 84 | "node" : "Shape", 85 | "stroke" : { 86 | "cap" : "butt", 87 | "dashes" : [ 88 | 89 | ], 90 | "fill" : { 91 | "type" : "Color", 92 | "val" : 0 93 | }, 94 | "join" : "miter", 95 | "width" : 1 96 | } 97 | } 98 | ], 99 | "layout" : { 100 | "scalingMode" : "meet", 101 | "svgSize" : { 102 | "height" : "100.0%", 103 | "width" : "100.0%" 104 | }, 105 | "viewBox" : { 106 | "h" : 360, 107 | "type" : "Rect", 108 | "w" : 480, 109 | "x" : 0, 110 | "y" : 0 111 | }, 112 | "xAligningMode" : "mid", 113 | "yAligningMode" : "mid" 114 | }, 115 | "node" : "Canvas" 116 | } -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/pservers-grad-15-b-manual.reference: -------------------------------------------------------------------------------- 1 | { 2 | "contents" : [ 3 | { 4 | "contents" : [ 5 | { 6 | "form" : { 7 | "h" : 80, 8 | "type" : "Rect", 9 | "w" : 440, 10 | "x" : 20, 11 | "y" : 20 12 | }, 13 | "node" : "Shape" 14 | }, 15 | { 16 | "form" : { 17 | "h" : 80, 18 | "type" : "Rect", 19 | "w" : 440, 20 | "x" : 20, 21 | "y" : 150 22 | }, 23 | "node" : "Shape" 24 | } 25 | ], 26 | "node" : "Group" 27 | }, 28 | { 29 | "contents" : [ 30 | { 31 | "align" : "min", 32 | "baseline" : "bottom", 33 | "fill" : { 34 | "type" : "Color", 35 | "val" : 0 36 | }, 37 | "font" : { 38 | "name" : "SVGFreeSansASCII,sans-serif", 39 | "size" : 32, 40 | "weight" : "normal" 41 | }, 42 | "node" : "Text", 43 | "place" : "1, 0, 0, 1, 10, 340", 44 | "text" : "$Revision: 1.6 $" 45 | } 46 | ], 47 | "node" : "Group" 48 | }, 49 | { 50 | "form" : { 51 | "h" : 358, 52 | "type" : "Rect", 53 | "w" : 478, 54 | "x" : 1, 55 | "y" : 1 56 | }, 57 | "node" : "Shape", 58 | "stroke" : { 59 | "cap" : "butt", 60 | "dashes" : [ 61 | 62 | ], 63 | "fill" : { 64 | "type" : "Color", 65 | "val" : 0 66 | }, 67 | "join" : "miter", 68 | "width" : 1 69 | } 70 | } 71 | ], 72 | "layout" : { 73 | "scalingMode" : "meet", 74 | "svgSize" : { 75 | "height" : "100.0%", 76 | "width" : "100.0%" 77 | }, 78 | "viewBox" : { 79 | "h" : 360, 80 | "type" : "Rect", 81 | "w" : 480, 82 | "x" : 0, 83 | "y" : 0 84 | }, 85 | "xAligningMode" : "mid", 86 | "yAligningMode" : "mid" 87 | }, 88 | "node" : "Canvas" 89 | } -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/shapes-line-02-f-manual.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 |

16 | The 'fill' attribute has no effect on the 'line' element. 17 |

18 | 19 | 20 |

21 | Run the test. No interaction required. 22 |

23 |
24 | 25 |

26 | Test passes if there is no red visible on the page. 27 |

28 |
29 | 30 | $RCSfile: shapes-line-02-f.svg,v $ 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | $Revision: 1.3 $ 49 | 50 | 51 | 57 | 58 | -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/struct-defs-01-t-manual.reference: -------------------------------------------------------------------------------- 1 | { 2 | "contents" : [ 3 | { 4 | "contents" : [ 5 | { 6 | "fill" : { 7 | "type" : "Color", 8 | "val" : 65280 9 | }, 10 | "form" : { 11 | "h" : 200, 12 | "type" : "Rect", 13 | "w" : 200, 14 | "x" : 140, 15 | "y" : 80 16 | }, 17 | "node" : "Shape" 18 | } 19 | ], 20 | "node" : "Group" 21 | }, 22 | { 23 | "contents" : [ 24 | { 25 | "align" : "min", 26 | "baseline" : "bottom", 27 | "fill" : { 28 | "type" : "Color", 29 | "val" : 0 30 | }, 31 | "font" : { 32 | "name" : "SVGFreeSansASCII,sans-serif", 33 | "size" : 32, 34 | "weight" : "normal" 35 | }, 36 | "node" : "Text", 37 | "place" : "1, 0, 0, 1, 10, 340", 38 | "text" : "$Revision: 1.6 $" 39 | } 40 | ], 41 | "node" : "Group" 42 | }, 43 | { 44 | "form" : { 45 | "h" : 358, 46 | "type" : "Rect", 47 | "w" : 478, 48 | "x" : 1, 49 | "y" : 1 50 | }, 51 | "node" : "Shape", 52 | "stroke" : { 53 | "cap" : "butt", 54 | "dashes" : [ 55 | 56 | ], 57 | "fill" : { 58 | "type" : "Color", 59 | "val" : 0 60 | }, 61 | "join" : "miter", 62 | "width" : 1 63 | } 64 | } 65 | ], 66 | "layout" : { 67 | "scalingMode" : "meet", 68 | "svgSize" : { 69 | "height" : "100.0%", 70 | "width" : "100.0%" 71 | }, 72 | "viewBox" : { 73 | "h" : 360, 74 | "type" : "Rect", 75 | "w" : 480, 76 | "x" : 0, 77 | "y" : 0 78 | }, 79 | "xAligningMode" : "mid", 80 | "yAligningMode" : "mid" 81 | }, 82 | "node" : "Canvas" 83 | } -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/struct-frag-01-t-manual.reference: -------------------------------------------------------------------------------- 1 | { 2 | "contents" : [ 3 | { 4 | "contents" : [ 5 | 6 | ], 7 | "node" : "Group" 8 | } 9 | ], 10 | "layout" : { 11 | "scalingMode" : "meet", 12 | "svgSize" : { 13 | "height" : "100.0%", 14 | "width" : "100.0%" 15 | }, 16 | "viewBox" : null, 17 | "xAligningMode" : "mid", 18 | "yAligningMode" : "mid" 19 | }, 20 | "node" : "Canvas" 21 | } -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/struct-frag-01-t-manual.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 |

18 | This is an empty SVG document. 19 |

20 | 21 | 22 |

23 | Run the test. No interaction required. 24 |

25 |
26 | 27 |

28 | Nothing should be rendered by the User Agent. 29 |

30 |
31 | 32 | $RCSfile: struct-frag-01-t.svg,v $ 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 49 | 50 | -------------------------------------------------------------------------------- /MacawTests/w3cSVGTests/struct-frag-04-t-manual.reference: -------------------------------------------------------------------------------- 1 | { 2 | "contents" : [ 3 | { 4 | "contents" : [ 5 | { 6 | "fill" : { 7 | "type" : "Color", 8 | "val" : 14423100 9 | }, 10 | "form" : { 11 | "h" : 50, 12 | "type" : "Rect", 13 | "w" : 50, 14 | "x" : 100, 15 | "y" : 100 16 | }, 17 | "node" : "Shape" 18 | }, 19 | { 20 | "fill" : { 21 | "type" : "Color", 22 | "val" : 14423100 23 | }, 24 | "form" : { 25 | "h" : 50, 26 | "type" : "Rect", 27 | "w" : 50, 28 | "x" : 150, 29 | "y" : 150 30 | }, 31 | "node" : "Shape" 32 | }, 33 | { 34 | "fill" : { 35 | "type" : "Color", 36 | "val" : 16766720 37 | }, 38 | "form" : { 39 | "cx" : 125, 40 | "cy" : 175, 41 | "r" : 25, 42 | "type" : "Circle" 43 | }, 44 | "node" : "Shape" 45 | }, 46 | { 47 | "fill" : { 48 | "type" : "Color", 49 | "val" : 16766720 50 | }, 51 | "form" : { 52 | "cx" : 175, 53 | "cy" : 125, 54 | "r" : 25, 55 | "type" : "Circle" 56 | }, 57 | "node" : "Shape" 58 | } 59 | ], 60 | "node" : "Group" 61 | }, 62 | { 63 | "contents" : [ 64 | { 65 | "align" : "min", 66 | "baseline" : "bottom", 67 | "fill" : { 68 | "type" : "Color", 69 | "val" : 0 70 | }, 71 | "font" : { 72 | "name" : "SVGFreeSansASCII,sans-serif", 73 | "size" : 32, 74 | "weight" : "normal" 75 | }, 76 | "node" : "Text", 77 | "place" : "1, 0, 0, 1, 10, 340", 78 | "text" : "$Revision: 1.5 $" 79 | } 80 | ], 81 | "node" : "Group" 82 | }, 83 | { 84 | "form" : { 85 | "h" : 358, 86 | "type" : "Rect", 87 | "w" : 478, 88 | "x" : 1, 89 | "y" : 1 90 | }, 91 | "node" : "Shape", 92 | "stroke" : { 93 | "cap" : "butt", 94 | "dashes" : [ 95 | 96 | ], 97 | "fill" : { 98 | "type" : "Color", 99 | "val" : 0 100 | }, 101 | "join" : "miter", 102 | "width" : 1 103 | } 104 | } 105 | ], 106 | "layout" : { 107 | "scalingMode" : "meet", 108 | "svgSize" : { 109 | "height" : "100.0%", 110 | "width" : "100.0%" 111 | }, 112 | "viewBox" : null, 113 | "xAligningMode" : "mid", 114 | "yAligningMode" : "mid" 115 | }, 116 | "node" : "Canvas" 117 | } -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Macaw", 7 | platforms: [ 8 | .macOS(.v10_12), 9 | .iOS(.v9) 10 | ], 11 | products: [ 12 | .library( 13 | name: "Macaw", 14 | targets: ["Macaw"] 15 | ) 16 | ], 17 | dependencies: [ 18 | .package( 19 | url: "https://github.com/drmohundro/SWXMLHash", 20 | from: "6.0.0" 21 | ) 22 | ], 23 | targets: [ 24 | .target( 25 | name: "Macaw", 26 | dependencies: ["SWXMLHash"], 27 | path: "Source" 28 | ), 29 | .testTarget( 30 | name: "MacawTests", 31 | dependencies: ["Macaw"], 32 | path: "MacawTests" 33 | ) 34 | ], 35 | swiftLanguageVersions: [.v5] 36 | ) 37 | -------------------------------------------------------------------------------- /Source/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/MCAMediaTimingFillMode_iOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCAMediaTimingFillMode.swift 3 | // Macaw 4 | // 5 | // Created by Anton Marunko on 27/09/2018. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) 12 | import UIKit 13 | 14 | public struct MCAMediaTimingFillMode { 15 | public static let forwards = CAMediaTimingFillMode.forwards 16 | public static let backwards = CAMediaTimingFillMode.backwards 17 | public static let both = CAMediaTimingFillMode.both 18 | public static let removed = CAMediaTimingFillMode.removed 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /Source/MCAMediaTimingFillMode_macOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCAMediaTimingFillMode_macOS.swift 3 | // MacawOSX 4 | // 5 | // Created by Anton Marunko on 27/09/2018. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(OSX) 12 | import AppKit 13 | 14 | public struct MCAMediaTimingFillMode { 15 | public static let forwards = CAMediaTimingFillMode.forwards 16 | public static let backwards = CAMediaTimingFillMode.backwards 17 | public static let both = CAMediaTimingFillMode.both 18 | public static let removed = CAMediaTimingFillMode.removed 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /Source/MCAMediaTimingFunctionName_iOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCAMediaTimingFunctionName_iOS.swift 3 | // Macaw 4 | // 5 | // Created by Anton Marunko on 27/09/2018. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) 12 | import UIKit 13 | 14 | public struct MCAMediaTimingFunctionName { 15 | static let linear = CAMediaTimingFunctionName.linear 16 | static let easeIn = CAMediaTimingFunctionName.easeIn 17 | static let easeOut = CAMediaTimingFunctionName.easeOut 18 | static let easeInEaseOut = CAMediaTimingFunctionName.easeInEaseOut 19 | static let `default` = CAMediaTimingFunctionName.default 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Source/MCAMediaTimingFunctionName_macOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCAMediaTimingFunctionName_macOS.swift 3 | // MacawOSX 4 | // 5 | // Created by Anton Marunko on 27/09/2018. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(OSX) 12 | import AppKit 13 | 14 | public struct MCAMediaTimingFunctionName { 15 | static let linear = CAMediaTimingFunctionName.default 16 | static let easeIn = CAMediaTimingFunctionName.easeIn 17 | static let easeOut = CAMediaTimingFunctionName.easeOut 18 | static let easeInEaseOut = CAMediaTimingFunctionName.easeInEaseOut 19 | static let `default` = CAMediaTimingFunctionName.default 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Source/MCAShapeLayerLineCap_iOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCAShapeLayerLineCap_iOS.swift 3 | // Macaw 4 | // 5 | // Created by Anton Marunko on 27/09/2018. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) 12 | import UIKit 13 | 14 | public struct MCAShapeLayerLineCap { 15 | static let butt = CAShapeLayerLineCap.butt 16 | static let round = CAShapeLayerLineCap.round 17 | static let square = CAShapeLayerLineCap.square 18 | 19 | static func mapToGraphics(model: LineCap) -> CAShapeLayerLineCap { 20 | switch model { 21 | case .butt: 22 | return MCAShapeLayerLineCap.butt 23 | case .round: 24 | return MCAShapeLayerLineCap.round 25 | case .square: 26 | return MCAShapeLayerLineCap.square 27 | } 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /Source/MCAShapeLayerLineCap_macOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCAShapeLayerLineCap_macOS.swift 3 | // MacawOSX 4 | // 5 | // Created by Anton Marunko on 27/09/2018. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(OSX) 12 | import AppKit 13 | 14 | public struct MCAShapeLayerLineCap { 15 | static let butt = CAShapeLayerLineCap.butt 16 | static let round = CAShapeLayerLineCap.round 17 | static let square = CAShapeLayerLineCap.square 18 | 19 | static func mapToGraphics(model: LineCap) -> CAShapeLayerLineCap { 20 | switch model { 21 | case .butt: 22 | return MCAShapeLayerLineCap.butt 23 | case .round: 24 | return MCAShapeLayerLineCap.round 25 | case .square: 26 | return MCAShapeLayerLineCap.square 27 | } 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /Source/MCAShapeLayerLineJoin_iOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCAShapeLayerLineJoin_iOS.swift 3 | // MacawOSX 4 | // 5 | // Created by Anton Marunko on 27/09/2018. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) 12 | import UIKit 13 | 14 | public struct MCAShapeLayerLineJoin { 15 | static let miter = CAShapeLayerLineJoin.miter 16 | static let round = CAShapeLayerLineJoin.round 17 | static let bevel = CAShapeLayerLineJoin.bevel 18 | 19 | static func mapToGraphics(model: LineJoin) -> CAShapeLayerLineJoin { 20 | switch model { 21 | case .miter: 22 | return MCAShapeLayerLineJoin.miter 23 | case .round: 24 | return MCAShapeLayerLineJoin.round 25 | case .bevel: 26 | return MCAShapeLayerLineJoin.bevel 27 | } 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /Source/MCAShapeLayerLineJoin_macOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCAShapeLayerLineJoin_macOS.swift 3 | // MacawOSX 4 | // 5 | // Created by Anton Marunko on 27/09/2018. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(OSX) 12 | import AppKit 13 | 14 | public struct MCAShapeLayerLineJoin { 15 | public static let miter = CAShapeLayerLineJoin.miter 16 | public static let round = CAShapeLayerLineJoin.round 17 | public static let bevel = CAShapeLayerLineJoin.bevel 18 | 19 | static func mapToGraphics(model: LineJoin) -> CAShapeLayerLineJoin { 20 | switch model { 21 | case .miter: 22 | return MCAShapeLayerLineJoin.miter 23 | case .round: 24 | return MCAShapeLayerLineJoin.round 25 | case .bevel: 26 | return MCAShapeLayerLineJoin.bevel 27 | } 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /Source/animation/AnimatableVariable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimatableVariable.swift 3 | // Pods 4 | // 5 | // Created by Yuri Strot on 8/24/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | open class AnimatableVariable: Variable { 12 | weak internal var node: Node? 13 | } 14 | -------------------------------------------------------------------------------- /Source/animation/Animation.swift: -------------------------------------------------------------------------------- 1 | public enum AnimationState { 2 | case initial 3 | case running 4 | case paused 5 | } 6 | 7 | public class Animation { 8 | 9 | internal init() { 10 | } 11 | 12 | public func play() { 13 | } 14 | 15 | public func stop() { 16 | } 17 | 18 | public func pause() { 19 | 20 | } 21 | 22 | public func state() -> AnimationState { 23 | return .initial 24 | } 25 | 26 | public func easing(_ easing: Easing) -> Animation { 27 | return self 28 | } 29 | 30 | public func delay(_ delay: Double) -> Animation { 31 | return self 32 | } 33 | 34 | public func cycle(_ count: Double) -> Animation { 35 | return self 36 | } 37 | 38 | public func cycle() -> Animation { 39 | return self 40 | } 41 | 42 | public func reverse() -> Animation { 43 | return self 44 | } 45 | 46 | public func autoreversed() -> Animation { 47 | return self 48 | } 49 | 50 | @discardableResult public func onComplete(_: @escaping (() -> Void)) -> Animation { 51 | return self 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Source/animation/AnimationUtils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class AbsoluteUtils { 4 | 5 | class func absolutePosition(_ nodeRenderer: NodeRenderer?, _ context: AnimationContext) -> Transform { 6 | return AbsoluteUtils.absoluteTransform(nodeRenderer, context, pos: nodeRenderer?.node.place ?? .identity) 7 | } 8 | 9 | class func absoluteTransform(_ nodeRenderer: NodeRenderer?, _ context: AnimationContext, pos: Transform) -> Transform { 10 | var transform = pos 11 | var parentRenderer = nodeRenderer?.parentRenderer 12 | while parentRenderer != nil { 13 | if let node = parentRenderer?.node { 14 | transform = node.place.concat(with: transform) 15 | } 16 | parentRenderer = parentRenderer?.parentRenderer 17 | } 18 | return transform.concat(with: context.getLayoutTransform(nodeRenderer)) 19 | } 20 | 21 | class func absoluteClip(_ nodeRenderer: NodeRenderer?) -> Locus? { 22 | // shouldn't this be a superposition of all parents' clips? 23 | let node = nodeRenderer?.node 24 | if let nodeClip = node?.clip { 25 | return nodeClip 26 | } 27 | 28 | var parentRenderer = nodeRenderer?.parentRenderer 29 | while parentRenderer != nil { 30 | if let parentClip = parentRenderer?.node.clip { 31 | return parentClip 32 | } 33 | 34 | parentRenderer = parentRenderer?.parentRenderer 35 | } 36 | 37 | return .none 38 | } 39 | 40 | private static var indexCache = [Node: Int]() 41 | } 42 | -------------------------------------------------------------------------------- /Source/animation/layer_animation/Extensions/AnimOperators.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // swiftlint:disable trailing_closure 4 | public func >> (a: Double, b: Double) -> OpacityAnimationDescription { 5 | return OpacityAnimationDescription(valueFunc: { t in 6 | a.interpolate(b, progress: t) 7 | }) 8 | } 9 | 10 | public func >> (a: Transform, b: Transform) -> TransformAnimationDescription { 11 | return TransformAnimationDescription(valueFunc: { t in 12 | a.interpolate(b, progress: t) 13 | }) 14 | } 15 | 16 | public func >> (a: Locus, b: Locus) -> MorphingAnimationDescription { 17 | return MorphingAnimationDescription(valueFunc: { t in 18 | // return a.interpolate(b, progress: t) 19 | if t == 0.0 { 20 | return a 21 | } 22 | 23 | return b 24 | }) 25 | } 26 | // swiftlint:enable trailing_closure 27 | -------------------------------------------------------------------------------- /Source/animation/layer_animation/Extensions/ContentsInterpolation.swift: -------------------------------------------------------------------------------- 1 | public protocol ContentsInterpolation: Interpolable { 2 | 3 | } 4 | 5 | extension Array: ContentsInterpolation { 6 | public func interpolate(_ endValue: Array, progress: Double) -> Array { 7 | return self 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Source/animation/layer_animation/Extensions/DoubleInterpolation.swift: -------------------------------------------------------------------------------- 1 | public protocol DoubleInterpolation: Interpolable { 2 | 3 | } 4 | 5 | extension Double: DoubleInterpolation { 6 | public func interpolate(_ endValue: Double, progress: Double) -> Double { 7 | return self + (endValue - self) * progress 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Source/animation/layer_animation/Extensions/FillInterpolation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FillInterpolation.swift 3 | // Pods 4 | // 5 | // Created by Victor Sukochev on 14/02/2017. 6 | // 7 | // 8 | 9 | public protocol FillInterpolation: Interpolable { 10 | 11 | } 12 | 13 | extension Fill: FillInterpolation { 14 | public func interpolate(_ endValue: Fill, progress: Double) -> Self { 15 | return self 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/animation/layer_animation/Extensions/Interpolable.swift: -------------------------------------------------------------------------------- 1 | public protocol Interpolable { 2 | func interpolate(_ endValue: Self, progress: Double) -> Self 3 | } 4 | -------------------------------------------------------------------------------- /Source/animation/layer_animation/Extensions/LocusInterpolation.swift: -------------------------------------------------------------------------------- 1 | public protocol LocusInterpolation: Interpolable { 2 | 3 | } 4 | 5 | extension Locus: LocusInterpolation { 6 | public func interpolate(_ endValue: Locus, progress: Double) -> Self { 7 | return self 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Source/animation/layer_animation/Extensions/ShapeInterpolation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShapeInterpolation.swift 3 | // Pods 4 | // 5 | // Created by Victor Sukochev on 03/02/2017. 6 | // 7 | // 8 | 9 | public protocol ShapeInterpolation: Interpolable { 10 | 11 | } 12 | 13 | extension Shape: ShapeInterpolation { 14 | public func interpolate(_ endValue: Shape, progress: Double) -> Self { 15 | return self 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/animation/layer_animation/Extensions/StrokeInterpolation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StrokeInterpolation.swift 3 | // Pods 4 | // 5 | // Created by Victor Sukochev on 14/02/2017. 6 | // 7 | // 8 | 9 | public protocol StrokeInterpolation: Interpolable { 10 | 11 | } 12 | 13 | extension Stroke: StrokeInterpolation { 14 | public func interpolate(_ endValue: Stroke, progress: Double) -> Self { 15 | return self 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/animation/layer_animation/Extensions/TransformInterpolation.swift: -------------------------------------------------------------------------------- 1 | public protocol TransformInterpolation: Interpolable { 2 | 3 | } 4 | 5 | extension Transform: TransformInterpolation { 6 | public func interpolate(_ endValue: Transform, progress: Double) -> Transform { 7 | return Transform(m11: self.m11.interpolate(endValue.m11, progress: progress), 8 | m12: self.m12.interpolate(endValue.m12, progress: progress), 9 | m21: self.m21.interpolate(endValue.m21, progress: progress), 10 | m22: self.m22.interpolate(endValue.m22, progress: progress), 11 | dx: self.dx.interpolate(endValue.dx, progress: progress), 12 | dy: self.dy.interpolate(endValue.dy, progress: progress)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Source/animation/layer_animation/PathFunctions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | typealias Func2D = ((_ t: Double) -> (Point)) 4 | 5 | func BezierFunc2D(_ t: Double, p0: Point, p1: Point, p2: Point, p3: Point) -> Point { 6 | return Point( 7 | x: polynom3(t, p0: p0.x, p1: p1.x, p2: p2.x, p3: p3.x), 8 | y: polynom3(t, p0: p0.y, p1: p1.y, p2: p2.y, p3: p3.y)) 9 | } 10 | 11 | func polynom3(_ t: Double, p0: Double, p1: Double, p2: Double, p3: Double) -> Double { 12 | let t1 = 1.0 - t 13 | return pow(t1, 3.0) * p0 + 3.0 * t * pow(t1, 2.0) * p1 + 3.0 * t * t * t1 * p2 + pow(t, 3.0) * p3 14 | } 15 | -------------------------------------------------------------------------------- /Source/animation/types/AnimationSequence.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class AnimationSequence: BasicAnimation { 4 | 5 | let animations: [BasicAnimation] 6 | 7 | required init(animations: [BasicAnimation], delay: Double = 0.0) { 8 | self.animations = animations 9 | 10 | super.init() 11 | 12 | self.type = .sequence 13 | self.node = animations.first?.node 14 | self.delay = delay 15 | } 16 | 17 | override func getDuration() -> Double { 18 | let originalDuration = animations.map { $0.getDuration() } .reduce(0) { $0 + $1 } 19 | 20 | if autoreverses { 21 | return originalDuration * 2.0 22 | } 23 | 24 | return originalDuration 25 | } 26 | 27 | open override func stop() { 28 | super.stop() 29 | 30 | guard let active = animations.first(where: { $0.isActive() }) else { 31 | return 32 | } 33 | 34 | active.stop() 35 | } 36 | 37 | open override func pause() { 38 | super.pause() 39 | 40 | guard let active = animations.first(where: { $0.isActive() }) else { 41 | return 42 | } 43 | 44 | active.pause() 45 | } 46 | 47 | open override func play() { 48 | guard let active = animations.first(where: { $0.isActive() }) else { 49 | super.play() 50 | return 51 | } 52 | 53 | manualStop = false 54 | paused = false 55 | 56 | active.play() 57 | } 58 | 59 | open override func state() -> AnimationState { 60 | for animation in animations { 61 | let state = animation.state() 62 | if state != .initial { 63 | return state 64 | } 65 | } 66 | 67 | return .initial 68 | } 69 | 70 | open override func reverse() -> Animation { 71 | var reversedAnimations = [BasicAnimation]() 72 | animations.forEach { animation in 73 | reversedAnimations.append(animation.reverse() as! BasicAnimation) 74 | } 75 | 76 | let reversedSequence = reversedAnimations.reversed().sequence(delay: self.delay) as! BasicAnimation 77 | reversedSequence.completion = completion 78 | reversedSequence.progress = progress 79 | 80 | return reversedSequence 81 | } 82 | } 83 | 84 | public extension Sequence where Iterator.Element: Animation { 85 | func sequence(delay: Double = 0.0) -> Animation { 86 | 87 | var sequence = [BasicAnimation]() 88 | self.forEach { animation in 89 | sequence.append(animation as! BasicAnimation) 90 | } 91 | return AnimationSequence(animations: sequence, delay: delay) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Source/animation/types/CombineAnimation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class CombineAnimation: BasicAnimation { 4 | 5 | let animations: [BasicAnimation] 6 | let toNodes: [Node] 7 | let duration: Double 8 | 9 | required init(animations: [BasicAnimation], during: Double = 1.0, delay: Double = 0.0, node: Node? = .none, toNodes: [Node] = []) { 10 | self.animations = animations 11 | self.duration = during 12 | self.toNodes = toNodes 13 | 14 | super.init() 15 | 16 | self.type = .combine 17 | self.node = node ?? animations.first?.node 18 | self.delay = delay 19 | } 20 | 21 | override func getDuration() -> Double { 22 | if let maxElement = animations.map({ $0.getDuration() }).max() { 23 | return maxElement 24 | } 25 | 26 | return 0.0 27 | } 28 | 29 | open override func reverse() -> Animation { 30 | var reversedAnimations = [BasicAnimation]() 31 | animations.forEach { animation in 32 | reversedAnimations.append(animation.reverse() as! BasicAnimation) 33 | } 34 | 35 | let combineReversed = reversedAnimations.combine(delay: self.delay) as! BasicAnimation 36 | combineReversed.completion = completion 37 | combineReversed.progress = progress 38 | 39 | return combineReversed 40 | } 41 | 42 | open override func play() { 43 | animations.forEach { animation in 44 | animation.paused = false 45 | animation.manualStop = false 46 | } 47 | 48 | super.play() 49 | } 50 | 51 | open override func stop() { 52 | super.stop() 53 | 54 | animations.forEach { animation in 55 | animation.stop() 56 | } 57 | } 58 | 59 | open override func pause() { 60 | super.pause() 61 | 62 | animations.forEach { animation in 63 | animation.pause() 64 | } 65 | } 66 | 67 | open override func state() -> AnimationState { 68 | var result = AnimationState.initial 69 | for animation in animations { 70 | let state = animation.state() 71 | if state == .running { 72 | return .running 73 | } 74 | 75 | if state != .initial { 76 | result = state 77 | } 78 | } 79 | 80 | return result 81 | } 82 | } 83 | 84 | public extension Sequence where Iterator.Element: Animation { 85 | func combine(delay: Double = 0.0, node: Node? = .none, toNodes: [Node] = []) -> Animation { 86 | 87 | var toCombine = [BasicAnimation]() 88 | self.forEach { animation in 89 | toCombine.append(animation as! BasicAnimation) 90 | } 91 | return CombineAnimation(animations: toCombine, delay: delay, node: node ?? toCombine.first?.node, toNodes: toNodes) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Source/animation/types/animation_generators/Cache/NodeHashable.swift: -------------------------------------------------------------------------------- 1 | // TODO: Implement better hash 2 | 3 | public func == (lhs: Node, rhs: Node) -> Bool { 4 | return lhs === rhs 5 | } 6 | 7 | extension NodeRenderer: Hashable { 8 | func hash(into hasher: inout Hasher) { 9 | hasher.combine(Unmanaged.passUnretained(self).toOpaque()) 10 | } 11 | } 12 | 13 | func == (lhs: NodeRenderer, rhs: NodeRenderer) -> Bool { 14 | return lhs === rhs 15 | } 16 | -------------------------------------------------------------------------------- /Source/animation/types/animation_generators/Cache/TransformHashable.swift: -------------------------------------------------------------------------------- 1 | extension Transform: Hashable { 2 | public func hash(into hasher: inout Hasher) { 3 | hasher.combine(m11) 4 | hasher.combine(m12) 5 | hasher.combine(m21) 6 | hasher.combine(m22) 7 | hasher.combine(dx) 8 | hasher.combine(dy) 9 | } 10 | } 11 | 12 | public func == (lhs: Transform, rhs: Transform) -> Bool { 13 | return lhs.m11 == rhs.m11 && 14 | lhs.m12 == rhs.m12 && 15 | lhs.m21 == rhs.m21 && 16 | lhs.m22 == rhs.m22 && 17 | lhs.dx == rhs.dx && 18 | lhs.dy == rhs.dy 19 | } 20 | -------------------------------------------------------------------------------- /Source/animation/types/animation_generators/TimingFunction.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | #elseif os(OSX) 4 | import AppKit 5 | #endif 6 | 7 | func caTimingFunction(_ easing: Easing) -> CAMediaTimingFunction { 8 | if easing === Easing.ease { 9 | return CAMediaTimingFunction(name: MCAMediaTimingFunctionName.default) 10 | } 11 | if easing === Easing.linear { 12 | return CAMediaTimingFunction(name: MCAMediaTimingFunctionName.linear) 13 | } 14 | if easing === Easing.easeIn { 15 | return CAMediaTimingFunction(name: MCAMediaTimingFunctionName.easeIn) 16 | } 17 | if easing === Easing.easeOut { 18 | return CAMediaTimingFunction(name: MCAMediaTimingFunctionName.easeOut) 19 | } 20 | if easing === Easing.easeInOut { 21 | return CAMediaTimingFunction(name: MCAMediaTimingFunctionName.easeInEaseOut) 22 | } 23 | return CAMediaTimingFunction(name: MCAMediaTimingFunctionName.linear) 24 | } 25 | -------------------------------------------------------------------------------- /Source/bindings/Disposable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Disposable.swift 3 | // Pods 4 | // 5 | // Created by Victor Sukochev on 29/11/2016. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | open class Disposable { 12 | 13 | let handler: (() -> Void) 14 | 15 | init (_ disposeHandler: @escaping (() -> Void) ) { 16 | handler = disposeHandler 17 | } 18 | 19 | open func dispose() { 20 | handler() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/bindings/GroupDisposable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GroupDisposable.swift 3 | // Pods 4 | // 5 | // Created by Yuri Strot on 9/5/16. 6 | // 7 | // 8 | 9 | open class GroupDisposable { 10 | 11 | fileprivate var items: [Disposable] = [] 12 | 13 | open func dispose() { 14 | for disposable in items { 15 | disposable.dispose() 16 | } 17 | items = [] 18 | } 19 | 20 | open func add(_ item: Disposable) { 21 | items.append(item) 22 | } 23 | 24 | } 25 | 26 | extension Disposable { 27 | public func addTo(_ group: GroupDisposable) { 28 | group.add(self) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Source/bindings/Variable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Variable.swift 3 | // Pods 4 | // 5 | // Created by Victor Sukochev on 29/11/2016. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | class ChangeHandler: Equatable { 12 | let handle: ((T) -> Void) 13 | 14 | init(_ f: @escaping ((T) -> Void) ) { 15 | handle = f 16 | } 17 | 18 | static func == (lhs: ChangeHandler, rhs: ChangeHandler) -> Bool { 19 | return lhs === rhs 20 | } 21 | } 22 | 23 | open class Variable { 24 | var handlers = [ChangeHandler]() 25 | 26 | open var value: T { 27 | didSet { 28 | handlers.forEach { handler in handler.handle(value) } 29 | } 30 | } 31 | 32 | init(_ v: T) { 33 | value = v 34 | } 35 | 36 | @discardableResult open func onChange(_ f: @escaping ((T) -> Void)) -> Disposable { 37 | let handler = ChangeHandler(f) 38 | handlers.append(handler) 39 | return Disposable { [weak self, unowned handler] in 40 | guard let index = self?.handlers.firstIndex(of: handler) else { 41 | return 42 | } 43 | 44 | self?.handlers.remove(at: index) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Source/events/Event.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Event.swift 3 | // Pods 4 | // 5 | // Created by Yuri Strot on 12/20/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | open class Event { 12 | 13 | public weak var node: Node? 14 | 15 | var consumed = false 16 | 17 | init(node: Node) { 18 | self.node = node 19 | } 20 | 21 | public func consume() { 22 | consumed = true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Source/events/PanEvent.swift: -------------------------------------------------------------------------------- 1 | open class PanEvent: Event { 2 | 3 | public let dx: Double 4 | public let dy: Double 5 | public let count: Int 6 | 7 | init(node: Node, dx: Double, dy: Double, count: Int) { 8 | self.dx = dx 9 | self.dy = dy 10 | self.count = count 11 | super.init(node: node) 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /Source/events/PinchEvent.swift: -------------------------------------------------------------------------------- 1 | open class PinchEvent: Event { 2 | 3 | public let scale: Double 4 | 5 | init(node: Node, scale: Double) { 6 | self.scale = scale 7 | super.init(node: node) 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /Source/events/RotateEvent.swift: -------------------------------------------------------------------------------- 1 | open class RotateEvent: Event { 2 | 3 | public let angle: Double 4 | 5 | init(node: Node, angle: Double) { 6 | self.angle = angle 7 | super.init(node: node) 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /Source/events/TapEvent.swift: -------------------------------------------------------------------------------- 1 | open class TapEvent: Event { 2 | 3 | public let location: Point 4 | 5 | init(node: Node, location: Point) { 6 | self.location = location 7 | super.init(node: node) 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /Source/events/TouchEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TouchEvent.swift 3 | // Pods 4 | // 5 | // Created by Victor Sukochev on 13/02/2017. 6 | // 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #elseif os(OSX) 12 | import AppKit 13 | #endif 14 | 15 | public enum Relativity { 16 | case parent 17 | case scene 18 | case view 19 | } 20 | 21 | class NodePath { 22 | let node: Node 23 | let location: CGPoint 24 | let parent: NodePath? 25 | 26 | init(node: Node, location: CGPoint, parent: NodePath? = nil) { 27 | self.node = node 28 | self.location = location 29 | self.parent = parent 30 | } 31 | } 32 | 33 | public struct TouchPoint { 34 | public let id: Int 35 | @available(*, deprecated) public var location: Point // absolute location 36 | { return absoluteLocation } 37 | 38 | private let absoluteLocation: Point 39 | private let relativeLocation: Point // location inside the node 40 | private let viewLocation: Point // location relative to containing view - no content layout or zoom transformations 41 | 42 | init(id: Int, location: Point, relativeToNodeLocation: Point, relativeToViewLocation: Point) { 43 | self.id = id 44 | self.absoluteLocation = location 45 | self.relativeLocation = relativeToNodeLocation 46 | self.viewLocation = relativeToViewLocation 47 | } 48 | 49 | public func location(in relativity: Relativity = .parent) -> Point { 50 | switch relativity { 51 | case .parent: 52 | return relativeLocation 53 | case .scene: 54 | return absoluteLocation 55 | case .view: 56 | return viewLocation 57 | } 58 | } 59 | } 60 | 61 | public class TouchEvent: Event { 62 | 63 | public let points: [TouchPoint] 64 | 65 | public init(node: Node, points: [TouchPoint]) { 66 | self.points = points 67 | 68 | super.init(node: node) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Source/export/MacawView+PDF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MacawView+PDF.swift 3 | // Macaw 4 | // 5 | // Created by Victor Sukochev on 06/10/2017. 6 | // Copyright © 2017 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | 12 | public extension MacawView { 13 | func toPDF(size: CGSize, path: URL) { 14 | let currentColor = backgroundColor 15 | backgroundColor = MColor.white 16 | defer { 17 | backgroundColor = currentColor 18 | } 19 | 20 | var frame = CGRect(origin: CGPoint.zero, size: size) 21 | let ctx = CGContext(path as CFURL, mediaBox: &frame, .none)! 22 | 23 | ctx.beginPDFPage(.none) 24 | ctx.translateBy(x: 0.0, y: size.height) 25 | ctx.scaleBy( 26 | x: size.width / bounds.width, 27 | y: -size.height / bounds.height 28 | ) 29 | 30 | drawingView.context.cgContext = ctx 31 | drawingView.renderer?.render(in: ctx, force: false, opacity: node.opacity) 32 | 33 | ctx.endPDFPage() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/model/draw/Align.swift: -------------------------------------------------------------------------------- 1 | open class Align { 2 | 3 | public static let min: Align = Align() 4 | public static let mid: Align = MidAlign() 5 | public static let max: Align = MaxAlign() 6 | 7 | open func align(outer: Double, inner: Double) -> Double { 8 | return 0 9 | } 10 | 11 | open func align(size: Double) -> Double { 12 | return align(outer: size, inner: 0) 13 | } 14 | 15 | } 16 | 17 | private class MidAlign: Align { 18 | 19 | override func align(outer: Double, inner: Double) -> Double { 20 | return (outer - inner) / 2 21 | } 22 | } 23 | 24 | private class MaxAlign: Align { 25 | 26 | override func align(outer: Double, inner: Double) -> Double { 27 | return outer - inner 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/model/draw/AlphaEffect.swift: -------------------------------------------------------------------------------- 1 | open class AlphaEffect: Effect { 2 | } 3 | -------------------------------------------------------------------------------- /Source/model/draw/AspectRatio.swift: -------------------------------------------------------------------------------- 1 | open class AspectRatio { 2 | 3 | public static let none: AspectRatio = AspectRatio() 4 | public static let meet: AspectRatio = MeetAspectRatio() 5 | public static let slice: AspectRatio = SliceAspectRatio() 6 | internal static let doNothing: AspectRatio = DoNothingAspectRatio() 7 | 8 | open func fit(size: Size, into sizeToFitIn: Size) -> Size { 9 | return Size(w: sizeToFitIn.w, h: sizeToFitIn.h) 10 | } 11 | 12 | open func fit(rect: Rect, into rectToFitIn: Rect) -> Size { 13 | return fit(size: rect.size(), into: rectToFitIn.size()) 14 | } 15 | 16 | open func fit(size: Size, into rectToFitIn: Rect) -> Size { 17 | return fit(size: size, into: rectToFitIn.size()) 18 | } 19 | 20 | } 21 | 22 | internal class DoNothingAspectRatio: AspectRatio { 23 | 24 | override func fit(size: Size, into sizeToFitIn: Size) -> Size { 25 | return size 26 | } 27 | } 28 | 29 | private class MeetAspectRatio: AspectRatio { 30 | 31 | override func fit(size: Size, into sizeToFitIn: Size) -> Size { 32 | let widthRatio = sizeToFitIn.w / size.w 33 | let heightRatio = sizeToFitIn.h / size.h 34 | 35 | if heightRatio < widthRatio { 36 | return Size(w: size.w * heightRatio, h: sizeToFitIn.h) 37 | } else { 38 | return Size(w: sizeToFitIn.w, h: size.h * widthRatio) 39 | } 40 | } 41 | } 42 | 43 | private class SliceAspectRatio: AspectRatio { 44 | 45 | override func fit(size: Size, into sizeToFitIn: Size) -> Size { 46 | let widthRatio = sizeToFitIn.w / size.w 47 | let heightRatio = sizeToFitIn.h / size.h 48 | 49 | if heightRatio > widthRatio { 50 | return Size(w: size.w * heightRatio, h: sizeToFitIn.h) 51 | } else { 52 | return Size(w: sizeToFitIn.w, h: size.h * widthRatio) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Source/model/draw/Baseline.swift: -------------------------------------------------------------------------------- 1 | public enum Baseline { 2 | case top 3 | case alphabetic 4 | case bottom 5 | case mid 6 | } 7 | -------------------------------------------------------------------------------- /Source/model/draw/BlendEffect.swift: -------------------------------------------------------------------------------- 1 | open class BlendEffect: Effect { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /Source/model/draw/ColorMatrix.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorMatrix.swift 3 | // Macaw 4 | // 5 | // Created by Yuri Strot on 6/20/18. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | open class ColorMatrix { 11 | 12 | public static let identity = ColorMatrix( 13 | values: [1, 0, 0, 0, 0, 14 | 0, 1, 0, 0, 0, 15 | 0, 0, 1, 0, 0, 16 | 0, 0, 0, 1, 0]) 17 | 18 | public static let luminanceToAlpha = ColorMatrix( 19 | values: [1, 0, 0, 0, 0, 20 | 0, 1, 0, 0, 0, 21 | 0, 0, 1, 0, 0, 22 | 0.2125, 0.7154, 0.0721, 0, 0]) 23 | 24 | public let values: [Double] 25 | 26 | public init(values: [Double]) { 27 | if values.count != 20 { 28 | fatalError("ColorMatrix: wrong matrix count") 29 | } 30 | self.values = values 31 | } 32 | 33 | public convenience init(color: Color) { 34 | self.init(values: [0, 0, 0, 0, Double(color.r()) / 255.0, 35 | 0, 0, 0, 0, Double(color.g()) / 255.0, 36 | 0, 0, 0, 0, Double(color.b()) / 255.0, 37 | 0, 0, 0, Double(color.a()) / 255.0, 0]) 38 | } 39 | 40 | public convenience init(saturate: Double) { 41 | let s = max(min(saturate, 1), 0) 42 | self.init(values: [0.213 + 0.787 * s, 0.715 - 0.715 * s, 0.072 - 0.072 * s, 0, 0, 43 | 0.213 - 0.213 * s, 0.715 + 0.285 * s, 0.072 - 0.072 * s, 0, 0, 44 | 0.213 - 0.213 * s, 0.715 - 0.715 * s, 0.072 + 0.928 * s, 0, 0, 45 | 0, 0, 0, 1, 0]) 46 | } 47 | 48 | public convenience init(hueRotate: Double) { 49 | let c = cos(hueRotate) 50 | let s = sin(hueRotate) 51 | let m1 = [0.213, 0.715, 0.072, 52 | 0.213, 0.715, 0.072, 53 | 0.213, 0.715, 0.072] 54 | let m2 = [0.787, -0.715, -0.072, 55 | -0.213, 0.285, -0.072, 56 | -0.213, -0.715, 0.928] 57 | let m3 = [-0.213, -0.715, 0.928, 58 | 0.143, 0.140, -0.283, 59 | -0.787, 0.715, 0.072] 60 | let a = { (i: Int) -> Double in 61 | m1[i] + c * m2[i] + s * m3[i] 62 | } 63 | self.init(values: [a(0), a(1), a(2), 0, 0, 64 | a(3), a(4), a(5), 0, 0, 65 | a(6), a(7), a(8), 0, 0, 66 | 0, 0, 0, 1, 0]) 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Source/model/draw/ColorMatrixEffect.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class ColorMatrixEffect: Effect { 4 | 5 | public let matrix: ColorMatrix 6 | 7 | public init(matrix: ColorMatrix, input: Effect? = nil) { 8 | self.matrix = matrix 9 | super.init(input: input) 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Source/model/draw/Drawable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class Drawable: NSObject { 4 | 5 | public let visible: Bool 6 | public let tag: [String] 7 | 8 | public init(visible: Bool = true, tag: [String] = []) { 9 | self.visible = visible 10 | self.tag = tag 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Source/model/draw/Effect.swift: -------------------------------------------------------------------------------- 1 | open class Effect { 2 | 3 | public let input: Effect? 4 | 5 | public init(input: Effect?) { 6 | self.input = input 7 | } 8 | 9 | public static func dropShadow(dx: Double = 0, dy: Double = -3, r: Double = 3, color: Color = .black) -> Effect? { 10 | return OffsetEffect(dx: dx, dy: dy).setColor(to: color).blur(r: r).blend() 11 | } 12 | 13 | public func offset(dx: Double, dy: Double) -> Effect { 14 | return OffsetEffect(dx: dx, dy: dy, input: self) 15 | } 16 | 17 | public func mapColor(with matrix: ColorMatrix) -> Effect { 18 | return ColorMatrixEffect(matrix: matrix, input: self) 19 | } 20 | 21 | public func setColor(to color: Color) -> Effect { 22 | return ColorMatrixEffect(matrix: ColorMatrix(color: color), input: self) 23 | } 24 | 25 | public func blur(r: Double) -> Effect { 26 | return GaussianBlur(r: r, input: self) 27 | } 28 | 29 | public func blend() -> Effect { 30 | return BlendEffect(input: self) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Source/model/draw/Fill.swift: -------------------------------------------------------------------------------- 1 | open class Fill: Equatable { 2 | 3 | public init() { 4 | } 5 | 6 | func equals(other: T) -> Bool where T: Fill { 7 | fatalError("Equals can't be implemented for Fill") 8 | } 9 | } 10 | 11 | public func == (lhs: T, rhs: T) -> Bool where T: Fill { 12 | return lhs.equals(other: rhs) 13 | } 14 | -------------------------------------------------------------------------------- /Source/model/draw/Font.swift: -------------------------------------------------------------------------------- 1 | open class Font { 2 | 3 | public let name: String 4 | public let size: Int 5 | public let weight: String 6 | 7 | public init(name: String = "Serif", size: Int = 12, weight: String = "normal") { 8 | self.name = name 9 | self.size = size 10 | self.weight = weight 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Source/model/draw/GaussianBlur.swift: -------------------------------------------------------------------------------- 1 | open class GaussianBlur: Effect { 2 | 3 | public let r: Double 4 | 5 | public init(r: Double = 0, input: Effect? = nil) { 6 | self.r = r 7 | super.init(input: input) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Source/model/draw/Gradient.swift: -------------------------------------------------------------------------------- 1 | open class Gradient: Fill { 2 | 3 | public let userSpace: Bool 4 | public let stops: [Stop] 5 | 6 | public init(userSpace: Bool = false, stops: [Stop] = []) { 7 | self.userSpace = userSpace 8 | self.stops = stops 9 | } 10 | 11 | override func equals(other: T) -> Bool where T: Fill { 12 | guard let other = other as? Gradient, userSpace == other.userSpace else { 13 | return false 14 | } 15 | 16 | if stops.isEmpty && other.stops.isEmpty { 17 | return true 18 | } 19 | 20 | return stops.elementsEqual(other.stops) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/model/draw/LineCap.swift: -------------------------------------------------------------------------------- 1 | public enum LineCap { 2 | case butt 3 | case round 4 | case square 5 | } 6 | -------------------------------------------------------------------------------- /Source/model/draw/LineJoin.swift: -------------------------------------------------------------------------------- 1 | public enum LineJoin { 2 | case miter 3 | case round 4 | case bevel 5 | } 6 | -------------------------------------------------------------------------------- /Source/model/draw/LinearGradient.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(iOS) 4 | import UIKit 5 | #endif 6 | 7 | open class LinearGradient: Gradient { 8 | 9 | public let x1: Double 10 | public let y1: Double 11 | public let x2: Double 12 | public let y2: Double 13 | 14 | public init(x1: Double = 0, y1: Double = 0, x2: Double = 0, y2: Double = 0, userSpace: Bool = false, stops: [Stop] = []) { 15 | self.x1 = x1 16 | self.y1 = y1 17 | self.x2 = x2 18 | self.y2 = y2 19 | super.init( 20 | userSpace: userSpace, 21 | stops: stops 22 | ) 23 | } 24 | 25 | public convenience init(degree: Double = 0, from: Color, to: Color) { 26 | self.init(degree: degree, stops: [Stop(offset: 0, color: from), Stop(offset: 1, color: to)]) 27 | } 28 | 29 | public init(degree: Double = 0, stops: [Stop]) { 30 | let rad = degree * .pi / 180 31 | var v = [0, 0, cos(rad), sin(rad)] 32 | let mmax = 1 / max(abs(v[2]), abs(v[3])) 33 | v[2] *= mmax 34 | v[3] *= mmax 35 | if v[2] < 0 { 36 | v[0] = -v[2] 37 | v[2] = 0 38 | } 39 | if v[3] < 0 { 40 | v[1] = -v[3] 41 | v[3] = 0 42 | } 43 | 44 | self.x1 = v[0] 45 | self.y1 = v[1] 46 | self.x2 = v[2] 47 | self.y2 = v[3] 48 | 49 | super.init( 50 | userSpace: false, 51 | stops: stops 52 | ) 53 | } 54 | 55 | override func equals(other: T) -> Bool where T: Fill { 56 | guard let other = other as? LinearGradient else { 57 | return false 58 | } 59 | return super.equals(other: other) && x1 == other.x1 && x2 == other.x2 && y1 == other.y1 && y2 == other.y2 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Source/model/draw/OffsetEffect.swift: -------------------------------------------------------------------------------- 1 | open class OffsetEffect: Effect { 2 | 3 | public let dx: Double 4 | public let dy: Double 5 | 6 | public init(dx: Double = 0, dy: Double = 0, input: Effect? = nil) { 7 | self.dx = dx 8 | self.dy = dy 9 | super.init(input: input) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Source/model/draw/Pattern.swift: -------------------------------------------------------------------------------- 1 | open class Pattern: Fill { 2 | 3 | public let content: Node 4 | public let bounds: Rect 5 | public let userSpace: Bool 6 | 7 | public init(content: Node, bounds: Rect, userSpace: Bool = false) { 8 | self.content = content 9 | self.bounds = bounds 10 | self.userSpace = userSpace 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Source/model/draw/RadialGradient.swift: -------------------------------------------------------------------------------- 1 | open class RadialGradient: Gradient { 2 | 3 | public let cx: Double 4 | public let cy: Double 5 | public let fx: Double 6 | public let fy: Double 7 | public let r: Double 8 | 9 | public init(cx: Double = 0.5, cy: Double = 0.5, fx: Double = 0.5, fy: Double = 0.5, r: Double = 0.5, userSpace: Bool = false, stops: [Stop] = []) { 10 | self.cx = cx 11 | self.cy = cy 12 | self.fx = fx 13 | self.fy = fy 14 | self.r = r 15 | super.init( 16 | userSpace: userSpace, 17 | stops: stops 18 | ) 19 | } 20 | 21 | override func equals(other: T) -> Bool where T: Fill { 22 | guard let other = other as? RadialGradient else { 23 | return false 24 | } 25 | let cxEquals = cx == other.cx 26 | let cyEquals = cy == other.cy 27 | let fxEquals = fx == other.fx 28 | let fyEquals = fy == other.fy 29 | let rEquals = r == other.r 30 | return super.equals(other: other) && cxEquals && cyEquals && fxEquals && fyEquals && rEquals 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Source/model/draw/Stop.swift: -------------------------------------------------------------------------------- 1 | open class Stop: Equatable { 2 | 3 | public let offset: Double 4 | public let color: Color 5 | 6 | public init(offset: Double = 0, color: Color) { 7 | self.color = color 8 | self.offset = max(0, min(1, offset)) 9 | } 10 | } 11 | 12 | public func == (lhs: Stop, rhs: Stop) -> Bool { 13 | return lhs.offset == rhs.offset && lhs.color == rhs.color 14 | } 15 | -------------------------------------------------------------------------------- /Source/model/draw/Stroke.swift: -------------------------------------------------------------------------------- 1 | open class Stroke: Equatable { 2 | 3 | public let fill: Fill 4 | public let width: Double 5 | public let cap: LineCap 6 | public let join: LineJoin 7 | public let miterLimit: Double 8 | public let dashes: [Double] 9 | public let offset: Double 10 | 11 | public init(fill: Fill = Color.black, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, miterLimit: Double = 10, dashes: [Double] = [], offset: Double = 0.0) { 12 | self.fill = fill 13 | self.width = width 14 | self.cap = cap 15 | self.join = join 16 | self.miterLimit = miterLimit 17 | self.dashes = dashes 18 | self.offset = offset 19 | } 20 | } 21 | 22 | public func == (lhs: T, rhs: T) -> Bool where T: Stroke { 23 | return lhs.fill == rhs.fill 24 | && lhs.width == rhs.width 25 | && lhs.cap == rhs.cap 26 | && lhs.join == rhs.join 27 | && lhs.miterLimit == rhs.miterLimit 28 | && lhs.dashes == rhs.dashes 29 | && lhs.offset == rhs.offset 30 | } 31 | -------------------------------------------------------------------------------- /Source/model/geom2d/Arc.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class Arc: Locus { 4 | 5 | public let ellipse: Ellipse 6 | public let shift: Double 7 | public let extent: Double 8 | 9 | public init(ellipse: Ellipse, shift: Double = 0, extent: Double = 0) { 10 | self.ellipse = ellipse 11 | self.shift = shift 12 | self.extent = extent 13 | } 14 | 15 | override open func bounds() -> Rect { 16 | return Rect( 17 | x: ellipse.cx - ellipse.rx, 18 | y: ellipse.cy - ellipse.ry, 19 | w: ellipse.rx * 2.0, 20 | h: ellipse.ry * 2.0) 21 | } 22 | 23 | override open func toPath() -> Path { 24 | let rx = ellipse.rx 25 | let ry = ellipse.ry 26 | let cx = ellipse.cx 27 | let cy = ellipse.cy 28 | 29 | var delta = extent 30 | if shift == 0.0 && abs(extent - .pi * 2.0) < 0.00001 { 31 | delta = .pi * 2.0 - 0.001 32 | } 33 | let theta1 = shift 34 | 35 | let theta2 = theta1 + delta 36 | 37 | let x1 = cx + rx * cos(theta1) 38 | let y1 = cy + ry * sin(theta1) 39 | 40 | let x2 = cx + rx * cos(theta2) 41 | let y2 = cy + ry * sin(theta2) 42 | 43 | let largeArcFlag = abs(delta) > .pi ? true : false 44 | let sweepFlag = delta > 0.0 ? true : false 45 | 46 | return PathBuilder(segment: PathSegment(type: .M, data: [x1, y1])).A(rx, ry, 0.0, largeArcFlag, sweepFlag, x2, y2).build() 47 | } 48 | 49 | override func equals(other: T) -> Bool where T: Locus { 50 | guard let other = other as? Arc else { 51 | return false 52 | } 53 | return ellipse == other.ellipse 54 | && shift == other.shift 55 | && extent == other.extent 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Source/model/geom2d/Circle.swift: -------------------------------------------------------------------------------- 1 | open class Circle: Locus { 2 | 3 | public let cx: Double 4 | public let cy: Double 5 | public let r: Double 6 | 7 | public init(cx: Double = 0, cy: Double = 0, r: Double = 0) { 8 | self.cx = cx 9 | self.cy = cy 10 | self.r = r 11 | } 12 | 13 | override open func bounds() -> Rect { 14 | return Rect( 15 | x: cx - r, 16 | y: cy - r, 17 | w: r * 2.0, 18 | h: r * 2.0) 19 | } 20 | 21 | open func arc(shift: Double, extent: Double) -> Arc { 22 | return Arc(ellipse: Ellipse(cx: cx, cy: cy, rx: r, ry: r), shift: shift, extent: extent) 23 | } 24 | 25 | override open func toPath() -> Path { 26 | return MoveTo(x: cx, y: cy).m(-r, 0).a(r, r, 0.0, true, false, r * 2.0, 0.0).a(r, r, 0.0, true, false, -(r * 2.0), 0.0).build() 27 | } 28 | 29 | override func equals(other: T) -> Bool where T: Locus { 30 | guard let other = other as? Circle else { 31 | return false 32 | } 33 | return cx == other.cx 34 | && cy == other.cy 35 | && r == other.r 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Source/model/geom2d/Ellipse.swift: -------------------------------------------------------------------------------- 1 | open class Ellipse: Locus { 2 | 3 | public let cx: Double 4 | public let cy: Double 5 | public let rx: Double 6 | public let ry: Double 7 | 8 | public init(cx: Double = 0, cy: Double = 0, rx: Double = 0, ry: Double = 0) { 9 | self.cx = cx 10 | self.cy = cy 11 | self.rx = rx 12 | self.ry = ry 13 | } 14 | 15 | override open func bounds() -> Rect { 16 | return Rect( 17 | x: cx - rx, 18 | y: cy - ry, 19 | w: rx * 2.0, 20 | h: ry * 2.0) 21 | } 22 | 23 | open func arc(shift: Double, extent: Double) -> Arc { 24 | return Arc(ellipse: self, shift: shift, extent: extent) 25 | } 26 | 27 | override func equals(other: T) -> Bool where T: Locus { 28 | guard let other = other as? Ellipse else { 29 | return false 30 | } 31 | return cx == other.cx 32 | && cy == other.cy 33 | && rx == other.rx 34 | && ry == other.ry 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Source/model/geom2d/GeomUtils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // TODO need to replace this class with model methods 4 | open class GeomUtils { 5 | 6 | @available(*, deprecated) 7 | open class func concat(t1: Transform, t2: Transform) -> Transform { 8 | return t1.concat(with: t2) 9 | } 10 | 11 | open class func centerRotation(node: Node, place: Transform, angle: Double) -> Transform { 12 | let center = GeomUtils.center(node: node) 13 | return GeomUtils.anchorRotation(node: node, place: place, anchor: center, angle: angle) 14 | } 15 | 16 | open class func anchorRotation(node: Node, place: Transform, anchor: Point, angle: Double) -> Transform { 17 | let move = Transform.move(dx: anchor.x, dy: anchor.y) 18 | 19 | let asin = sin(angle); let acos = cos(angle) 20 | 21 | let rotation = Transform( 22 | m11: acos, m12: -asin, 23 | m21: asin, m22: acos, 24 | dx: 0.0, dy: 0.0 25 | ) 26 | 27 | let t1 = move.concat(with: rotation) 28 | let t2 = t1.concat(with: move.invert()!) 29 | let result = place.concat(with: t2) 30 | 31 | return result 32 | } 33 | 34 | open class func centerScale(node: Node, sx: Double, sy: Double) -> Transform { 35 | let center = GeomUtils.center(node: node) 36 | return Transform.move(dx: center.x * (1.0 - sx), dy: center.y * (1.0 - sy)).scale(sx: sx, sy: sy) 37 | } 38 | 39 | open class func center(node: Node) -> Point { 40 | guard let bounds = node.bounds else { 41 | return Point() 42 | } 43 | 44 | return Point(x: bounds.x + bounds.w / 2.0, y: bounds.y + bounds.h / 2.0) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Source/model/geom2d/Insets.swift: -------------------------------------------------------------------------------- 1 | open class Insets { 2 | 3 | public let top: Double 4 | public let right: Double 5 | public let bottom: Double 6 | public let left: Double 7 | 8 | public init(top: Double = 0, right: Double = 0, bottom: Double = 0, left: Double = 0) { 9 | self.top = top 10 | self.right = right 11 | self.bottom = bottom 12 | self.left = left 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Source/model/geom2d/Line.swift: -------------------------------------------------------------------------------- 1 | open class Line: Locus { 2 | 3 | public let x1: Double 4 | public let y1: Double 5 | public let x2: Double 6 | public let y2: Double 7 | 8 | public init(_ x1: Double, _ y1: Double, _ x2: Double, _ y2: Double) { 9 | self.x1 = x1 10 | self.y1 = y1 11 | self.x2 = x2 12 | self.y2 = y2 13 | } 14 | 15 | public init(x1: Double = 0, y1: Double = 0, x2: Double = 0, y2: Double = 0) { 16 | self.x1 = x1 17 | self.y1 = y1 18 | self.x2 = x2 19 | self.y2 = y2 20 | } 21 | 22 | override open func bounds() -> Rect { 23 | return Rect(x: min(x1, x2), y: min(y1, y2), w: abs(x1 - x2), h: abs(y1 - y2)) 24 | } 25 | 26 | override open func toPath() -> Path { 27 | return MoveTo(x: x1, y: y1).lineTo(x: x2, y: y2).build() 28 | } 29 | 30 | override func equals(other: T) -> Bool where T: Locus { 31 | guard let other = other as? Line else { 32 | return false 33 | } 34 | return x1 == other.x1 35 | && y1 == other.y1 36 | && x2 == other.x2 37 | && y2 == other.y2 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Source/model/geom2d/Locus.swift: -------------------------------------------------------------------------------- 1 | open class Locus: Equatable { 2 | 3 | public init() { 4 | } 5 | 6 | open func bounds() -> Rect { 7 | return Rect() 8 | } 9 | 10 | open func stroke(with: Stroke) -> Shape { 11 | return Shape(form: self, stroke: with) 12 | } 13 | 14 | open func fill(with: Fill) -> Shape { 15 | return Shape(form: self, fill: with) 16 | } 17 | 18 | open func fill(_ hex: Int) -> Shape { 19 | return Shape(form: self, fill: Color(val: hex)) 20 | } 21 | 22 | open func fill(_ fill: Fill) -> Shape { 23 | return Shape(form: self, fill: fill) 24 | } 25 | 26 | open func stroke(fill: Fill = Color.black, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, dashes: [Double] = []) -> Shape { 27 | return Shape(form: self, stroke: Stroke(fill: fill, width: width, cap: cap, join: join, dashes: dashes)) 28 | } 29 | 30 | open func stroke(color: Color, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, dashes: [Double] = []) -> Shape { 31 | return Shape(form: self, stroke: Stroke(fill: color, width: width, cap: cap, join: join, dashes: dashes)) 32 | } 33 | 34 | open func stroke(color: Int, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, dashes: [Double] = []) -> Shape { 35 | return Shape(form: self, stroke: Stroke(fill: Color(color), width: width, cap: cap, join: join, dashes: dashes)) 36 | } 37 | 38 | open func toPath() -> Path { 39 | fatalError("Unsupported locus: \(self)") 40 | } 41 | 42 | func equals(other: T) -> Bool where T: Locus { 43 | fatalError("Equals can't be implemented for Locus") 44 | } 45 | } 46 | 47 | public func == (lhs: T, rhs: T) -> Bool where T: Locus { 48 | return type(of: lhs) == type(of: rhs) 49 | && lhs.equals(other: rhs) 50 | } 51 | -------------------------------------------------------------------------------- /Source/model/geom2d/MoveTo.swift: -------------------------------------------------------------------------------- 1 | open class MoveTo: PathBuilder { 2 | 3 | public init(_ x: Double, _ y: Double) { 4 | super.init(segment: PathSegment(type: .M, data: [x, y])) 5 | } 6 | 7 | public init(x: Double, y: Double) { 8 | super.init(segment: PathSegment(type: .M, data: [x, y])) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Source/model/geom2d/Path.swift: -------------------------------------------------------------------------------- 1 | public enum FillRule { 2 | case nonzero, evenodd 3 | } 4 | 5 | open class Path: Locus { 6 | 7 | public let segments: [PathSegment] 8 | public let fillRule: FillRule 9 | 10 | public init(segments: [PathSegment] = [], fillRule: FillRule = .nonzero) { 11 | self.segments = segments 12 | self.fillRule = fillRule 13 | } 14 | 15 | override open func bounds() -> Rect { 16 | return toCGPath().boundingBoxOfPath.toMacaw() 17 | } 18 | 19 | override open func toPath() -> Path { 20 | return self 21 | } 22 | 23 | override func equals(other: T) -> Bool where T: Locus { 24 | guard let other = other as? Path else { 25 | return false 26 | } 27 | return segments == other.segments 28 | && fillRule == other.fillRule 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Source/model/geom2d/PathSegment.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class PathSegment: Equatable { 4 | 5 | public let type: PathSegmentType 6 | public let data: [Double] 7 | 8 | public init(type: PathSegmentType = .M, data: [Double] = []) { 9 | self.type = type 10 | self.data = data 11 | } 12 | 13 | open func isAbsolute() -> Bool { 14 | switch type { 15 | case .M, .L, .H, .V, .C, .S, .Q, .T, .A, .E: 16 | return true 17 | default: 18 | return false 19 | } 20 | } 21 | 22 | public static func == (lhs: PathSegment, rhs: PathSegment) -> Bool { 23 | return lhs.type == rhs.type 24 | && lhs.data == rhs.data 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/model/geom2d/PathSegmentType.swift: -------------------------------------------------------------------------------- 1 | public enum PathSegmentType { 2 | case M 3 | case L 4 | case C 5 | case Q 6 | case A 7 | case z 8 | case H 9 | case V 10 | case S 11 | case T 12 | case m 13 | case l 14 | case c 15 | case q 16 | case a 17 | case h 18 | case v 19 | case s 20 | case t 21 | case E 22 | case e 23 | } 24 | -------------------------------------------------------------------------------- /Source/model/geom2d/Point.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class Point: Locus { 4 | 5 | public let x: Double 6 | public let y: Double 7 | 8 | public static let origin = Point(0, 0) 9 | 10 | public init(_ x: Double, _ y: Double) { 11 | self.x = x 12 | self.y = y 13 | } 14 | 15 | public init(x: Double = 0, y: Double = 0) { 16 | self.x = x 17 | self.y = y 18 | } 19 | 20 | override open func bounds() -> Rect { 21 | return Rect(x: x, y: y, w: 0.0, h: 0.0) 22 | } 23 | 24 | open func add(_ point: Point) -> Point { 25 | return Point( x: x + point.x, y: y + point.y) 26 | } 27 | 28 | open func rect(size: Size) -> Rect { 29 | return Rect(point: self, size: size) 30 | } 31 | 32 | open func distance(to point: Point) -> Double { 33 | let dx = point.x - x 34 | let dy = point.y - y 35 | return sqrt(dx * dx + dy * dy) 36 | } 37 | 38 | override open func toPath() -> Path { 39 | return MoveTo(x: x, y: y).lineTo(x: x, y: y).build() 40 | } 41 | 42 | override func equals(other: T) -> Bool where T: Locus { 43 | guard let other = other as? Point else { 44 | return false 45 | } 46 | return x == other.y 47 | && y == other.y 48 | } 49 | } 50 | 51 | extension Point { 52 | 53 | public static func - (lhs: Point, rhs: Point) -> Size { 54 | return Size(w: lhs.x - rhs.x, h: lhs.y - rhs.y) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Source/model/geom2d/Polygon.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class Polygon: Locus { 4 | 5 | public let points: [Double] 6 | 7 | public init(_ points: [Double]) { 8 | self.points = points 9 | } 10 | 11 | public init(points: [Double] = []) { 12 | self.points = points 13 | } 14 | 15 | override open func bounds() -> Rect { 16 | guard !points.isEmpty else { 17 | return Rect.zero() 18 | } 19 | 20 | var minX = Double(INT16_MAX) 21 | var minY = Double(INT16_MAX) 22 | var maxX = Double(INT16_MIN) 23 | var maxY = Double(INT16_MIN) 24 | 25 | var isX = true 26 | for point in points { 27 | if isX { 28 | if minX > point { 29 | minX = point 30 | } 31 | 32 | if maxX < point { 33 | maxX = point 34 | } 35 | } else { 36 | if minY > point { 37 | minY = point 38 | } 39 | 40 | if maxY < point { 41 | maxY = point 42 | } 43 | } 44 | 45 | isX = !isX 46 | } 47 | 48 | return Rect(x: minX, y: minY, 49 | w: maxX - minX, 50 | h: maxY - minY) 51 | } 52 | 53 | override open func toPath() -> Path { 54 | var pb = PathBuilder(segment: PathSegment(type: .M, data: [points[0], points[1]])) 55 | if points.count > 2 { 56 | let parts = stride(from: 2, to: points.count, by: 2).map { Array(points[$0 ..< $0 + 2]) } 57 | for part in parts { 58 | pb = pb.lineTo(x: part[0], y: part[1]) 59 | } 60 | } 61 | return pb.close().build() 62 | } 63 | 64 | override func equals(other: T) -> Bool where T: Locus { 65 | guard let other = other as? Polygon else { 66 | return false 67 | } 68 | return points == other.points 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Source/model/geom2d/Polyline.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class Polyline: Locus { 4 | 5 | public let points: [Double] 6 | 7 | public init(_ points: [Double]) { 8 | self.points = points 9 | } 10 | 11 | public init(points: [Double] = []) { 12 | self.points = points 13 | } 14 | 15 | override open func bounds() -> Rect { 16 | guard !points.isEmpty else { 17 | return Rect.zero() 18 | } 19 | 20 | var minX = Double(INT16_MAX) 21 | var minY = Double(INT16_MAX) 22 | var maxX = Double(INT16_MIN) 23 | var maxY = Double(INT16_MIN) 24 | 25 | var isX = true 26 | for point in points { 27 | if isX { 28 | if minX > point { 29 | minX = point 30 | } 31 | 32 | if maxX < point { 33 | maxX = point 34 | } 35 | } else { 36 | if minY > point { 37 | minY = point 38 | } 39 | 40 | if maxY < point { 41 | maxY = point 42 | } 43 | } 44 | 45 | isX = !isX 46 | } 47 | 48 | return Rect(x: minX, y: minY, 49 | w: maxX - minX, 50 | h: maxY - minY) 51 | } 52 | 53 | override open func toPath() -> Path { 54 | var pb = PathBuilder(segment: PathSegment(type: .M, data: [points[0], points[1]])) 55 | if points.count > 2 { 56 | let parts = stride(from: 2, to: points.count, by: 2).map { Array(points[$0 ..< $0 + 2]) } 57 | for part in parts { 58 | pb = pb.lineTo(x: part[0], y: part[1]) 59 | } 60 | } 61 | return pb.build() 62 | } 63 | 64 | override func equals(other: T) -> Bool where T: Locus { 65 | guard let other = other as? Polyline else { 66 | return false 67 | } 68 | return points == other.points 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Source/model/geom2d/Rect.swift: -------------------------------------------------------------------------------- 1 | open class Rect: Locus { 2 | 3 | public let x: Double 4 | public let y: Double 5 | public let w: Double 6 | public let h: Double 7 | 8 | public init(_ x: Double, _ y: Double, _ w: Double, _ h: Double) { 9 | self.x = x 10 | self.y = y 11 | self.w = w 12 | self.h = h 13 | } 14 | 15 | public init(x: Double = 0, y: Double = 0, w: Double = 0, h: Double = 0) { 16 | self.x = x 17 | self.y = y 18 | self.w = w 19 | self.h = h 20 | } 21 | 22 | public init(point: Point, size: Size) { 23 | self.x = point.x 24 | self.y = point.y 25 | self.w = size.w 26 | self.h = size.h 27 | } 28 | 29 | override open func bounds() -> Rect { 30 | return self 31 | } 32 | 33 | open func round(rx: Double, ry: Double) -> RoundRect { 34 | return RoundRect(rect: self, rx: rx, ry: ry) 35 | } 36 | 37 | public func round(r: Double) -> RoundRect { 38 | return RoundRect(rect: self, rx: r, ry: r) 39 | } 40 | 41 | open func center() -> Point { 42 | return Point(x: x + w / 2, y: y + h / 2) 43 | } 44 | 45 | open func contains(locus: Locus) -> Bool { 46 | return false 47 | } 48 | 49 | class func zero() -> Rect { 50 | return Rect(x: 0.0, y: 0.0, w: 0.0, h: 0.0) 51 | } 52 | 53 | open func move(offset: Point) -> Rect { 54 | return Rect( 55 | x: self.x + offset.x, 56 | y: self.y + offset.y, 57 | w: self.w, 58 | h: self.h) 59 | } 60 | 61 | open func union(rect: Rect) -> Rect { 62 | return Rect( 63 | x: min(self.x, rect.x), 64 | y: min(self.y, rect.y), 65 | w: max(self.x + self.w, rect.x + rect.w) - min(self.x, rect.x), 66 | h: max(self.y + self.h, rect.y + rect.h) - min(self.y, rect.y)) 67 | } 68 | 69 | open func size() -> Size { 70 | return Size(w: w, h: h) 71 | } 72 | 73 | override open func toPath() -> Path { 74 | return MoveTo(x: x, y: y).lineTo(x: x, y: y + h).lineTo(x: x + w, y: y + h).lineTo(x: x + w, y: y).close().build() 75 | } 76 | 77 | override func equals(other: T) -> Bool where T: Locus { 78 | guard let other = other as? Rect else { 79 | return false 80 | } 81 | return x == other.x 82 | && y == other.y 83 | && w == other.w 84 | && h == other.h 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Source/model/geom2d/RoundRect.swift: -------------------------------------------------------------------------------- 1 | open class RoundRect: Locus { 2 | 3 | public let rect: Rect 4 | public let rx: Double 5 | public let ry: Double 6 | 7 | public init(rect: Rect, rx: Double = 0, ry: Double = 0) { 8 | self.rect = rect 9 | self.rx = rx 10 | self.ry = ry 11 | } 12 | 13 | override open func bounds() -> Rect { 14 | return rect 15 | } 16 | 17 | override func equals(other: T) -> Bool where T: Locus { 18 | guard let other = other as? RoundRect else { 19 | return false 20 | } 21 | return rect == other.rect 22 | && rx == other.rx 23 | && ry == other.ry 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Source/model/geom2d/Size.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class Size { 4 | 5 | public let w: Double 6 | public let h: Double 7 | 8 | public static let zero = Size(0, 0) 9 | 10 | public init(_ w: Double, _ h: Double) { 11 | self.w = w 12 | self.h = h 13 | } 14 | 15 | public init(w: Double = 0, h: Double = 0) { 16 | self.w = w 17 | self.h = h 18 | } 19 | 20 | open func rect(at point: Point = Point.origin) -> Rect { 21 | return Rect(point: point, size: self) 22 | } 23 | 24 | open func angle() -> Double { 25 | return atan2(h, w) 26 | } 27 | 28 | } 29 | 30 | extension Size { 31 | 32 | public static func == (lhs: Size, rhs: Size) -> Bool { 33 | return lhs.w == rhs.w && lhs.h == rhs.h 34 | } 35 | 36 | public static func + (lhs: Size, rhs: Size) -> Size { 37 | return Size(w: lhs.w + rhs.w, h: lhs.h + rhs.h) 38 | } 39 | 40 | public static func - (lhs: Size, rhs: Size) -> Size { 41 | return Size(w: lhs.w - rhs.w, h: lhs.h - rhs.h) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Source/model/geom2d/TransformedLocus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransformedLocus.swift 3 | // Macaw 4 | // 5 | // Created by Yuri Strot on 5/21/18. 6 | // 7 | 8 | open class TransformedLocus: Locus { 9 | 10 | public let locus: Locus 11 | public let transform: Transform 12 | 13 | public init(locus: Locus, transform: Transform) { 14 | self.locus = locus 15 | self.transform = transform 16 | } 17 | 18 | open override func bounds() -> Rect { 19 | return locus.bounds().applying(transform) 20 | } 21 | 22 | override func equals(other: T) -> Bool where T: Locus { 23 | guard let other = other as? TransformedLocus else { 24 | return false 25 | } 26 | return locus == other.locus 27 | && transform == other.transform 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/platform/MDisplayLink.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDisplayLink.swift 3 | // Pods 4 | // 5 | // Created by Victor Sukochev on 28/08/2017. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | protocol MDisplayLinkProtocol { 12 | func startUpdates(_ onUpdate: @escaping () -> Void) 13 | func invalidate() 14 | } 15 | -------------------------------------------------------------------------------- /Source/platform/iOS/Graphics_iOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Graphics_iOS.swift 3 | // Macaw 4 | // 5 | // Created by Daniil Manin on 8/17/17. 6 | // Copyright © 2017 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) 12 | import UIKit 13 | 14 | func MGraphicsGetCurrentContext() -> CGContext? { 15 | return UIGraphicsGetCurrentContext() 16 | } 17 | 18 | func MGraphicsGetImageFromCurrentImageContext() -> MImage! { 19 | return UIGraphicsGetImageFromCurrentImageContext() 20 | } 21 | 22 | func MGraphicsPushContext(_ context: CGContext) { 23 | UIGraphicsPushContext(context) 24 | } 25 | 26 | func MGraphicsPopContext() { 27 | UIGraphicsPopContext() 28 | } 29 | 30 | func MGraphicsEndImageContext() { 31 | UIGraphicsEndImageContext() 32 | } 33 | 34 | func MImagePNGRepresentation(_ image: MImage) -> Data? { 35 | return image.pngData() 36 | } 37 | 38 | func MImageJPEGRepresentation(_ image: MImage, _ quality: CGFloat = 0.8) -> Data? { 39 | return image.jpegData(compressionQuality: quality) 40 | } 41 | 42 | func MMainScreen() -> MScreen? { 43 | return MScreen.main 44 | } 45 | 46 | func MGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat) { 47 | UIGraphicsBeginImageContextWithOptions(size, opaque, scale) 48 | } 49 | 50 | func MNoIntrinsicMetric() -> CGFloat { 51 | return UIView.noIntrinsicMetric 52 | } 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /Source/platform/iOS/MDisplayLink_iOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDisplayLink_iOS.swift 3 | // Pods 4 | // 5 | // Created by Victor Sukochev on 28/08/2017. 6 | // 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | 12 | class MDisplayLink: MDisplayLinkProtocol { 13 | private var displayLink: CADisplayLink? 14 | private var onUpdate: (() -> Void)? 15 | 16 | // MARK: - Lifecycle 17 | deinit { 18 | displayLink?.invalidate() 19 | } 20 | 21 | // MARK: - MDisplayLinkProtocol 22 | func startUpdates(_ onUpdate: @escaping () -> Void) { 23 | self.onUpdate = onUpdate 24 | 25 | displayLink = CADisplayLink(target: self, selector: #selector(updateHandler)) 26 | if #available(iOS 10.0, *) { 27 | displayLink?.preferredFramesPerSecond = 60 28 | } else { 29 | displayLink?.frameInterval = 1 30 | } 31 | displayLink?.add(to: RunLoop.current, forMode: RunLoop.Mode.default) 32 | } 33 | 34 | func invalidate() { 35 | displayLink?.invalidate() 36 | } 37 | 38 | // MARK: - Private 39 | @objc func updateHandler() { 40 | onUpdate?() 41 | } 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /Source/platform/iOS/MView_iOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MView_iOS.swift 3 | // Macaw 4 | // 5 | // Created by Daniil Manin on 8/17/17. 6 | // Copyright © 2017 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) 12 | import UIKit 13 | 14 | open class MView: UIView, Touchable { 15 | var mLayer: CALayer? { 16 | return self.layer 17 | } 18 | 19 | var mGestureRecognizers: [MGestureRecognizer]? { 20 | return self.gestureRecognizers 21 | } 22 | 23 | func removeGestureRecognizers() { 24 | self.gestureRecognizers?.removeAll() 25 | } 26 | 27 | open override func touchesBegan(_ touches: Set, with event: MEvent?) { 28 | super.touchesBegan(touches, with: event) 29 | mTouchesBegan(touches, with: event) 30 | } 31 | 32 | open override func touchesMoved(_ touches: Set, with event: MEvent?) { 33 | super.touchesMoved(touches, with: event) 34 | mTouchesMoved(touches, with: event) 35 | } 36 | 37 | open override func touchesEnded(_ touches: Set, with event: MEvent?) { 38 | super.touchesEnded(touches, with: event) 39 | mTouchesEnded(touches, with: event) 40 | } 41 | 42 | override open func touchesCancelled(_ touches: Set, with event: MEvent?) { 43 | super.touchesCancelled(touches, with: event) 44 | mTouchesCancelled(touches, with: event) 45 | } 46 | 47 | func mTouchesBegan(_ touches: Set, with event: MEvent?) { 48 | } 49 | 50 | func mTouchesMoved(_ touches: Set, with event: MEvent?) { 51 | } 52 | 53 | func mTouchesEnded(_ touches: Set, with event: MEvent?) { 54 | } 55 | 56 | func mTouchesCancelled(_ touches: Set, with event: MEvent?) { 57 | } 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /Source/platform/macOS/MDisplayLink_macOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDisplayLink_macOS.swift 3 | // Macaw 4 | // 5 | // Created by Daniil Manin on 8/17/17. 6 | // Copyright © 2017 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(OSX) 12 | import AppKit 13 | 14 | public class MDisplayLink: MDisplayLinkProtocol { 15 | private var displayLink: CVDisplayLink? 16 | private var onUpdate: (() -> Void)? 17 | 18 | // MARK: - Lifecycle 19 | deinit { 20 | stop() 21 | } 22 | 23 | // MARK: - MDisplayLinkProtocol 24 | func startUpdates(_ onUpdate: @escaping () -> Void) { 25 | self.onUpdate = onUpdate 26 | 27 | if CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) != kCVReturnSuccess { 28 | return 29 | } 30 | 31 | CVDisplayLinkSetOutputCallback(displayLink!, { _, _, _, _, _, userData -> CVReturn in 32 | 33 | let `self` = unsafeBitCast(userData, to: MDisplayLink.self) 34 | `self`.onUpdate?() 35 | 36 | return kCVReturnSuccess 37 | }, Unmanaged.passUnretained(self).toOpaque()) 38 | 39 | if displayLink != nil { 40 | CVDisplayLinkStart(displayLink!) 41 | } 42 | } 43 | 44 | func invalidate() { 45 | stop() 46 | } 47 | 48 | private func stop() { 49 | if displayLink != nil { 50 | CVDisplayLinkStop(displayLink!) 51 | } 52 | } 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /Source/render/GroupRenderer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(iOS) 4 | import UIKit 5 | #endif 6 | 7 | class GroupRenderer: NodeRenderer { 8 | 9 | var group: Group 10 | var renderers: [NodeRenderer] = [] 11 | 12 | override var node: Node { 13 | return group 14 | } 15 | 16 | init(group: Group, view: DrawingView?, parentRenderer: GroupRenderer? = nil) { 17 | self.group = group 18 | super.init(node: group, view: view, parentRenderer: parentRenderer) 19 | updateRenderers() 20 | } 21 | 22 | deinit { 23 | dispose() 24 | } 25 | 26 | override func doAddObservers() { 27 | super.doAddObservers() 28 | 29 | group.contentsVar.onChange { [weak self] _ in 30 | self?.updateRenderers() 31 | } 32 | observe(group.contentsVar) 33 | } 34 | 35 | override func freeCachedAbsPlace() { 36 | for renderer in renderers { 37 | renderer.freeCachedAbsPlace() 38 | } 39 | } 40 | 41 | override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) { 42 | renderers.forEach { renderer in 43 | if !renderer.isAnimating() { 44 | renderer.render(in: context, force: force, opacity: opacity, coloringMode: coloringMode) 45 | } 46 | } 47 | } 48 | 49 | override func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? { 50 | for renderer in renderers.reversed() { 51 | if let result = renderer.findNodeAt(parentNodePath: path, ctx: ctx) { 52 | return result 53 | } 54 | } 55 | return .none 56 | } 57 | 58 | override func dispose() { 59 | super.dispose() 60 | renderers.forEach { renderer in renderer.dispose() } 61 | renderers.removeAll() 62 | } 63 | 64 | private func updateRenderers() { 65 | 66 | renderers.forEach { 67 | $0.dispose() 68 | } 69 | renderers.removeAll() 70 | 71 | renderers = group.contents.compactMap { child -> NodeRenderer? in 72 | RenderUtils.createNodeRenderer(child, view: view, parentRenderer: self) 73 | } 74 | 75 | var parent: NodeRenderer = self 76 | while let parentRenderer = parent.parentRenderer { 77 | parent = parentRenderer 78 | } 79 | parent.calculateZPositionRecursively() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Source/render/ImageRenderer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(OSX) 4 | import AppKit 5 | #endif 6 | 7 | #if os(iOS) 8 | import UIKit 9 | #endif 10 | 11 | class ImageRenderer: NodeRenderer { 12 | var image: Image 13 | 14 | var renderedPaths: [CGPath] = [CGPath]() 15 | 16 | override var node: Node { 17 | return image 18 | } 19 | 20 | init(image: Image, view: DrawingView?, parentRenderer: GroupRenderer? = nil) { 21 | self.image = image 22 | super.init(node: image, view: view, parentRenderer: parentRenderer) 23 | } 24 | 25 | deinit { 26 | dispose() 27 | } 28 | 29 | override func doAddObservers() { 30 | super.doAddObservers() 31 | 32 | observe(image.srcVar) 33 | observe(image.xAlignVar) 34 | observe(image.yAlignVar) 35 | observe(image.aspectRatioVar) 36 | observe(image.wVar) 37 | observe(image.hVar) 38 | } 39 | 40 | override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) { 41 | 42 | var mImage: MImage? 43 | if image.src.contains("memory") { 44 | let id = image.src.replacingOccurrences(of: "memory://", with: "") 45 | mImage = imagesMap[id] 46 | } else { 47 | mImage = image.image() 48 | } 49 | 50 | if let mImage = mImage, 51 | let rect = BoundsUtils.getRect(of: image, mImage: mImage) { 52 | context.scaleBy(x: 1.0, y: -1.0) 53 | context.translateBy(x: 0.0, y: -1.0 * rect.height) 54 | context.setAlpha(CGFloat(opacity)) 55 | context.draw(mImage.cgImage!, in: rect) 56 | } 57 | } 58 | 59 | override func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? { 60 | 61 | if let mImage = MImage(named: image.src), 62 | let rect = BoundsUtils.getRect(of: image, mImage: mImage) { 63 | 64 | if rect.contains(path.location) { 65 | return path 66 | } 67 | } 68 | return .none 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Source/render/RenderContext.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(iOS) 4 | import UIKit 5 | #endif 6 | 7 | class RenderContext { 8 | weak var view: DrawingView? 9 | var cgContext: CGContext? 10 | 11 | init(view: DrawingView?) { 12 | self.view = view 13 | self.cgContext = nil 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Source/svg/SVGCanvas.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGCanvas.swift 3 | // Macaw 4 | // 5 | // Created by Yuri Strot on 4/11/18. 6 | // 7 | 8 | class SVGCanvas: Group { 9 | 10 | let layout: NodeLayout 11 | 12 | public init(layout: NodeLayout, contents: [Node] = []) { 13 | self.layout = layout 14 | super.init(contents: contents) 15 | } 16 | 17 | public func layout(size: Size) -> Size { 18 | let size = layout.computeSize(parent: size) 19 | layout.layout(node: self, in: size) 20 | return size 21 | } 22 | 23 | override var bounds: Rect? { 24 | let size = layout.computeSize(parent: .zero) 25 | if size.w == 0 || size.h == 0 { 26 | return .none 27 | } 28 | return size.rect(at: .origin) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Source/svg/SVGNodeLayout.swift: -------------------------------------------------------------------------------- 1 | enum SVGLength { 2 | case percent(Double) 3 | case pixels(Double) 4 | 5 | init(percent: Double) { 6 | self = .percent(percent) 7 | } 8 | 9 | init(pixels: Double) { 10 | self = .pixels(pixels) 11 | } 12 | 13 | func toPixels(total: Double) -> Double { 14 | switch self { 15 | case let .percent(percent): 16 | return total * percent / 100.0 17 | case let .pixels(pixels): 18 | return pixels 19 | } 20 | } 21 | 22 | } 23 | 24 | class SVGSize { 25 | 26 | let width: SVGLength 27 | let height: SVGLength 28 | 29 | public init(width: SVGLength, height: SVGLength) { 30 | self.width = width 31 | self.height = height 32 | } 33 | 34 | func toPixels(total: Size) -> Size { 35 | return Size(w: width.toPixels(total: total.w), 36 | h: height.toPixels(total: total.h)) 37 | } 38 | 39 | } 40 | 41 | protocol NodeLayout { 42 | 43 | func computeSize(parent: Size) -> Size 44 | 45 | func layout(node: Node, in size: Size) 46 | } 47 | 48 | class SVGNodeLayout: NodeLayout { 49 | 50 | let svgSize: SVGSize 51 | let viewBox: Rect? 52 | let scaling: AspectRatio 53 | let xAlign: Align 54 | let yAlign: Align 55 | 56 | init(svgSize: SVGSize, viewBox: Rect? = .none, scaling: AspectRatio? = nil, xAlign: Align? = nil, yAlign: Align? = nil) { 57 | self.svgSize = svgSize 58 | self.viewBox = viewBox 59 | self.scaling = scaling ?? .meet 60 | self.xAlign = xAlign ?? .mid 61 | self.yAlign = yAlign ?? .mid 62 | } 63 | 64 | func computeSize(parent: Size) -> Size { 65 | return svgSize.toPixels(total: parent) 66 | } 67 | 68 | func layout(node: Node, in size: Size) { 69 | let svgSizeInPixels = svgSize.toPixels(total: size) 70 | 71 | if let viewBox = self.viewBox { 72 | node.clip = viewBox 73 | } 74 | let viewBox = self.viewBox ?? Rect(x: 0, y: 0, w: svgSizeInPixels.w, h: svgSizeInPixels.h) 75 | 76 | if scaling === AspectRatio.slice { 77 | // setup new clipping to slice extra bits 78 | let newSize = AspectRatio.meet.fit(size: svgSizeInPixels, into: viewBox) 79 | let newX = viewBox.x + xAlign.align(outer: viewBox.w, inner: newSize.w) 80 | let newY = viewBox.y + yAlign.align(outer: viewBox.h, inner: newSize.h) 81 | node.clip = Rect(x: newX, y: newY, w: newSize.w, h: newSize.h) 82 | } 83 | 84 | let layout = ContentLayout.of(scaling: scaling, xAlign: xAlign, yAlign: yAlign) 85 | node.place = layout.layout(size: viewBox.size(), into: svgSizeInPixels) 86 | 87 | // move to (0, 0) 88 | node.place = node.place.move(dx: -viewBox.x, dy: -viewBox.y) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Source/svg/SVGParserError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGParserError.swift 3 | // Pods 4 | // 5 | // Created by Yuri Strot on 1/26/17. 6 | // 7 | // 8 | 9 | enum SVGParserError: Error, Equatable { 10 | case noSuchFile(path: String) 11 | case incorrectFilterEffectsOrder 12 | case maskUnsupportedNodeType 13 | case invalidContentMode 14 | } 15 | -------------------------------------------------------------------------------- /Source/svg/SVGView.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(iOS) 4 | import UIKit 5 | #elseif os(OSX) 6 | import AppKit 7 | #endif 8 | 9 | open class SVGView: MacawView { 10 | 11 | @IBInspectable open var fileName: String? { 12 | didSet { 13 | node = (try? SVGParser.parse(resource: fileName ?? "")) ?? Group() 14 | } 15 | } 16 | 17 | public init(node: Node, frame: CGRect) { 18 | super.init(frame: frame) 19 | self.node = node 20 | } 21 | 22 | @objc override public init?(node: Node, coder aDecoder: NSCoder) { 23 | super.init(node: node, coder: aDecoder) 24 | } 25 | 26 | required public convenience init?(coder aDecoder: NSCoder) { 27 | self.init(node: Group(), coder: aDecoder) 28 | } 29 | 30 | override public init(frame: CGRect) { 31 | super.init(frame: frame) 32 | } 33 | 34 | override func initializeView() { 35 | super.initializeView() 36 | self.contentLayout = ContentLayout.of(contentMode: contentMode) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Source/thirdparty/CGFloat+Double.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(iOS) 4 | import UIKit 5 | #endif 6 | 7 | internal extension CGFloat { 8 | 9 | var doubleValue: Double { 10 | return Double(self) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Source/thirdparty/NSTimer+Closure.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Timer { 4 | /** 5 | Creates and schedules a one-time `NSTimer` instance. 6 | 7 | - Parameters: 8 | - delay: The delay before execution. 9 | - handler: A closure to execute after `delay`. 10 | 11 | - Returns: The newly-created `NSTimer` instance. 12 | */ 13 | class func schedule(delay: TimeInterval, handler: @escaping (CFRunLoopTimer?) -> Void) -> CFRunLoopTimer? { 14 | let fireDate = delay + CFAbsoluteTimeGetCurrent() 15 | let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, 0, 0, 0, handler) 16 | CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, CFRunLoopMode.commonModes) 17 | return timer 18 | } 19 | 20 | /** 21 | Creates and schedules a repeating `NSTimer` instance. 22 | 23 | - Parameters: 24 | - repeatInterval: The interval (in seconds) between each execution of 25 | `handler`. Note that individual calls may be delayed; subsequent calls 26 | to `handler` will be based on the time the timer was created. 27 | - handler: A closure to execute at each `repeatInterval`. 28 | 29 | - Returns: The newly-created `NSTimer` instance. 30 | */ 31 | class func schedule(repeatInterval interval: TimeInterval, handler: @escaping (CFRunLoopTimer?) -> Void) -> CFRunLoopTimer? { 32 | let fireDate = interval + CFAbsoluteTimeGetCurrent() 33 | let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, interval, 0, 0, handler) 34 | CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, CFRunLoopMode.commonModes) 35 | return timer 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Source/utils/CommonError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonError.swift 3 | // Macaw 4 | // 5 | // Created by Daniil Manin on 04.10.2021. 6 | // Copyright © 2021 Exyte. All rights reserved. 7 | // 8 | 9 | enum CommonError: Error, Equatable { 10 | case invalidImageContext 11 | } 12 | -------------------------------------------------------------------------------- /Source/utils/DescriptionExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DescriptionExtensions.swift 3 | // Macaw 4 | // 5 | // Created by Anton Marunko on 21/06/2018. 6 | // Copyright © 2018 Exyte. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Rect: CustomStringConvertible { 12 | public var description: String { 13 | return "x: \(String(format: "%f", x)), y: \(String(format: "%f", y)), w: \(String(format: "%f", w)), h: \(String(format: "%f", h))" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Source/utils/UIImage2Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage2Image.swift 3 | // Pods 4 | // 5 | // Created by Victor Sukochev on 14/04/2017. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) 12 | import UIKit 13 | #endif 14 | 15 | var imagesMap = [String: MImage]() 16 | 17 | public extension MImage { 18 | func image( xAlign: Align = .min, yAlign: Align = .min, aspectRatio: AspectRatio = .none, w: Int = 0, h: Int = 0, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) -> Image { 19 | 20 | var oldId: String? 21 | for key in imagesMap.keys where self === imagesMap[key] { 22 | oldId = key 23 | } 24 | 25 | let id = oldId ?? UUID().uuidString 26 | imagesMap[id] = self 27 | 28 | return Image(src: "memory://\(id)", 29 | xAlign: xAlign, yAlign: yAlign, 30 | aspectRatio: aspectRatio, 31 | w: w, h: h, 32 | place: place, 33 | opaque: opaque, 34 | opacity: opacity, 35 | clip: clip, 36 | effect: effect, 37 | visible: visible, 38 | tag: tag) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Source/views/ShapeLayer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(iOS) 4 | import UIKit 5 | #elseif os(OSX) 6 | import AppKit 7 | #endif 8 | 9 | class ShapeLayer: CAShapeLayer { 10 | weak var renderer: NodeRenderer? 11 | var shouldRenderContent = true 12 | var isForceRenderingEnabled = true 13 | 14 | override func draw(in ctx: CGContext) { 15 | if !shouldRenderContent { 16 | super.draw(in: ctx) 17 | return 18 | } 19 | 20 | renderer?.directRender(in: ctx, force: isForceRenderingEnabled) 21 | } 22 | } 23 | 24 | extension ShapeLayer { 25 | 26 | func setupStrokeAndFill(_ shape: Shape) { 27 | 28 | // Stroke 29 | if let stroke = shape.stroke { 30 | if let color = stroke.fill as? Color { 31 | strokeColor = color.toCG() 32 | } else { 33 | strokeColor = MColor.black.cgColor 34 | } 35 | 36 | lineWidth = CGFloat(stroke.width) 37 | lineCap = MCAShapeLayerLineCap.mapToGraphics(model: stroke.cap) 38 | lineJoin = MCAShapeLayerLineJoin.mapToGraphics(model: stroke.join) 39 | lineDashPattern = stroke.dashes.map { NSNumber(value: $0) } 40 | lineDashPhase = CGFloat(stroke.offset) 41 | } else if shape.fill == nil { 42 | strokeColor = MColor.black.cgColor 43 | lineWidth = 1.0 44 | } 45 | 46 | // Fill 47 | if let color = shape.fill as? Color { 48 | fillColor = color.toCG() 49 | } else { 50 | fillColor = MColor.clear.cgColor 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Source/views/Touchable.swift: -------------------------------------------------------------------------------- 1 | class MTouchEvent: Hashable { 2 | let id: Int 3 | let x: Double 4 | let y: Double 5 | 6 | init(x: Double, y: Double, id: Int) { 7 | self.x = x 8 | self.y = y 9 | self.id = id 10 | } 11 | 12 | func hash(into hasher: inout Hasher) { 13 | hasher.combine(id) 14 | } 15 | 16 | public static func == (lhs: MTouchEvent, rhs: MTouchEvent) -> Bool { 17 | return lhs.id == rhs.id 18 | } 19 | } 20 | 21 | protocol Touchable { 22 | func mTouchesBegan(_ touches: Set, with event: MEvent?) 23 | func mTouchesMoved(_ touches: Set, with event: MEvent?) 24 | func mTouchesEnded(_ touches: Set, with event: MEvent?) 25 | func mTouchesCancelled(_ touches: Set, with event: MEvent?) 26 | } 27 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/demo.gif -------------------------------------------------------------------------------- /header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/header.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/Macaw/273964aa992017077bbb5060fe1698393c3c60b4/logo.png --------------------------------------------------------------------------------