diff --git a/Macaw.xcodeproj/project.pbxproj b/Macaw.xcodeproj/project.pbxproj index b9b14122..0881ae93 100644 --- a/Macaw.xcodeproj/project.pbxproj +++ b/Macaw.xcodeproj/project.pbxproj @@ -253,11 +253,32 @@ 57F1087C1F53CA7E00DC365B /* MDisplayLink_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F1087B1F53CA7E00DC365B /* MDisplayLink_iOS.swift */; }; 57FCD2771D76EA4600CC0FB6 /* Macaw.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57FCD26C1D76EA4600CC0FB6 /* Macaw.framework */; }; 57FCD27C1D76EA4600CC0FB6 /* MacawTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FCD27B1D76EA4600CC0FB6 /* MacawTests.swift */; }; - 602C561D2081C984003AD452 /* rounded.svg in Resources */ = {isa = PBXBuildFile; fileRef = 602C561C2081C984003AD452 /* rounded.svg */; }; 5B1FFD7A207E083600716A46 /* SvgContentLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAA56A7207C73FF0055BC5B /* SvgContentLayout.swift */; }; 5BAA56A8207C73FF0055BC5B /* SvgContentLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAA56A7207C73FF0055BC5B /* SvgContentLayout.swift */; }; + 5BAE201F208E1211006BF277 /* SVGCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAE201E208E1211006BF277 /* SVGCanvas.swift */; }; + 5BAE2038208E163D006BF277 /* polyline.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2022208E1637006BF277 /* polyline.reference */; }; + 5BAE2039208E163D006BF277 /* polygon.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2023208E1637006BF277 /* polygon.reference */; }; + 5BAE203A208E163D006BF277 /* rect.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2024208E1637006BF277 /* rect.reference */; }; + 5BAE203C208E163D006BF277 /* triangle.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2026208E1637006BF277 /* triangle.reference */; }; + 5BAE203D208E163D006BF277 /* clipManual.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2027208E1637006BF277 /* clipManual.reference */; }; + 5BAE203E208E163D006BF277 /* circle.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2028208E1637006BF277 /* circle.reference */; }; + 5BAE203F208E163D006BF277 /* transform.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2029208E1638006BF277 /* transform.reference */; }; + 5BAE2040208E163D006BF277 /* ellipse.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE202A208E1638006BF277 /* ellipse.reference */; }; + 5BAE2042208E163D006BF277 /* group.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE202C208E1638006BF277 /* group.reference */; }; + 5BAE2043208E163D006BF277 /* textBasicTransform.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE202D208E1638006BF277 /* textBasicTransform.reference */; }; + 5BAE2044208E163D006BF277 /* style.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE202E208E1639006BF277 /* style.reference */; }; + 5BAE2045208E163D006BF277 /* arcsgroup.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE202F208E163A006BF277 /* arcsgroup.reference */; }; + 5BAE2047208E163D006BF277 /* viewBox.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2031208E163B006BF277 /* viewBox.reference */; }; + 5BAE2048208E163D006BF277 /* line.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2032208E163B006BF277 /* line.reference */; }; + 5BAE204A208E163D006BF277 /* roundRect.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2034208E163B006BF277 /* roundRect.reference */; }; + 5BAE204B208E163D006BF277 /* clip.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2035208E163C006BF277 /* clip.reference */; }; + 5BAE204C208E1EF4006BF277 /* SVGCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAE201E208E1211006BF277 /* SVGCanvas.swift */; }; + 5BAE2058208F24DE006BF277 /* SceneSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAE2057208F24DE006BF277 /* SceneSerialization.swift */; }; + 5BAE2061208F2504006BF277 /* color-prop-02-f-manual.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE205B208F2504006BF277 /* color-prop-02-f-manual.svg */; }; + 5BAE2062208F2504006BF277 /* shapes-circle-01-t-manual.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE205C208F2504006BF277 /* shapes-circle-01-t-manual.svg */; }; + 5BAE2063208F2504006BF277 /* shapes-circle-01-t-manual.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE205D208F2504006BF277 /* shapes-circle-01-t-manual.reference */; }; + 5BAE2066208F2504006BF277 /* color-prop-02-f-manual.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAE2060208F2504006BF277 /* color-prop-02-f-manual.reference */; }; 5BAEA9C9206CEAA20049AAAE /* viewBox.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5BAEA9C8206CEAA20049AAAE /* viewBox.svg */; }; - 5BAEA9CB206CEB7D0049AAAE /* viewBox.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5BAEA9CA206CEB7D0049AAAE /* viewBox.reference */; }; A718CD441F45C28200966E06 /* Common_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A718CD431F45C28200966E06 /* Common_iOS.swift */; }; A718CD471F45C28700966E06 /* Graphics_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A718CD451F45C28700966E06 /* Graphics_iOS.swift */; }; A718CD481F45C28700966E06 /* MView_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A718CD461F45C28700966E06 /* MView_iOS.swift */; }; @@ -271,13 +292,6 @@ C4153A8F1F8793DE001BA5EE /* small-logo.png in Resources */ = {isa = PBXBuildFile; fileRef = C4153A8E1F8793DD001BA5EE /* small-logo.png */; }; C43B064D1F9738EF00787A35 /* clip.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B064C1F9738EF00787A35 /* clip.svg */; }; C43B06511F9866E400787A35 /* Locus+ToPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43B06501F9866E400787A35 /* Locus+ToPath.swift */; }; - C43B06531F989D9300787A35 /* transform.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B06521F989D9300787A35 /* transform.reference */; }; - C43B06551F98A53600787A35 /* group.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B06541F98A53600787A35 /* group.reference */; }; - C43B06571F98A7B700787A35 /* arcsgroup.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B06561F98A7B700787A35 /* arcsgroup.reference */; }; - C43B06591F98A84200787A35 /* style.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B06581F98A84200787A35 /* style.reference */; }; - C43B065B1F98A9E000787A35 /* clipManual.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B065A1F98A9E000787A35 /* clipManual.reference */; }; - C43B065F1F98AAA500787A35 /* clip.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B065E1F98AAA500787A35 /* clip.reference */; }; - C43B06611F98ACFC00787A35 /* textBasicTransform.reference in Resources */ = {isa = PBXBuildFile; fileRef = C43B06601F98ACFC00787A35 /* textBasicTransform.reference */; }; C43B06631F99A33400787A35 /* pathbounds3.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B06621F99A33400787A35 /* pathbounds3.svg */; }; C43B06661F99EE7300787A35 /* cubicAbsolute.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B06641F99EE7200787A35 /* cubicAbsolute.svg */; }; C43B06671F99EE7300787A35 /* cubicRelative.svg in Resources */ = {isa = PBXBuildFile; fileRef = C43B06651F99EE7300787A35 /* cubicRelative.svg */; }; @@ -454,10 +468,30 @@ 57FCD2761D76EA4600CC0FB6 /* MacawTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MacawTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57FCD27B1D76EA4600CC0FB6 /* MacawTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacawTests.swift; sourceTree = ""; }; 57FCD27D1D76EA4600CC0FB6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 602C561C2081C984003AD452 /* rounded.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = rounded.svg; sourceTree = ""; }; 5BAA56A7207C73FF0055BC5B /* SvgContentLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SvgContentLayout.swift; sourceTree = ""; }; + 5BAE201E208E1211006BF277 /* SVGCanvas.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGCanvas.swift; sourceTree = ""; }; + 5BAE2022208E1637006BF277 /* polyline.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = polyline.reference; sourceTree = ""; }; + 5BAE2023208E1637006BF277 /* polygon.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = polygon.reference; sourceTree = ""; }; + 5BAE2024208E1637006BF277 /* rect.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = rect.reference; sourceTree = ""; }; + 5BAE2026208E1637006BF277 /* triangle.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = triangle.reference; sourceTree = ""; }; + 5BAE2027208E1637006BF277 /* clipManual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = clipManual.reference; sourceTree = ""; }; + 5BAE2028208E1637006BF277 /* circle.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = circle.reference; sourceTree = ""; }; + 5BAE2029208E1638006BF277 /* transform.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = transform.reference; sourceTree = ""; }; + 5BAE202A208E1638006BF277 /* ellipse.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ellipse.reference; sourceTree = ""; }; + 5BAE202C208E1638006BF277 /* group.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = group.reference; sourceTree = ""; }; + 5BAE202D208E1638006BF277 /* textBasicTransform.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = textBasicTransform.reference; sourceTree = ""; }; + 5BAE202E208E1639006BF277 /* style.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = style.reference; sourceTree = ""; }; + 5BAE202F208E163A006BF277 /* arcsgroup.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = arcsgroup.reference; sourceTree = ""; }; + 5BAE2031208E163B006BF277 /* viewBox.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = viewBox.reference; sourceTree = ""; }; + 5BAE2032208E163B006BF277 /* line.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = line.reference; sourceTree = ""; }; + 5BAE2034208E163B006BF277 /* roundRect.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = roundRect.reference; sourceTree = ""; }; + 5BAE2035208E163C006BF277 /* clip.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = clip.reference; sourceTree = ""; }; + 5BAE2057208F24DE006BF277 /* SceneSerialization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneSerialization.swift; sourceTree = ""; }; + 5BAE205B208F2504006BF277 /* color-prop-02-f-manual.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "color-prop-02-f-manual.svg"; sourceTree = ""; }; + 5BAE205C208F2504006BF277 /* shapes-circle-01-t-manual.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "shapes-circle-01-t-manual.svg"; sourceTree = ""; }; + 5BAE205D208F2504006BF277 /* shapes-circle-01-t-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "shapes-circle-01-t-manual.reference"; sourceTree = ""; }; + 5BAE2060208F2504006BF277 /* color-prop-02-f-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "color-prop-02-f-manual.reference"; sourceTree = ""; }; 5BAEA9C8206CEAA20049AAAE /* viewBox.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = viewBox.svg; sourceTree = ""; }; - 5BAEA9CA206CEB7D0049AAAE /* viewBox.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = viewBox.reference; sourceTree = ""; }; A718CD431F45C28200966E06 /* Common_iOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Common_iOS.swift; path = Source/platform/iOS/Common_iOS.swift; sourceTree = SOURCE_ROOT; }; A718CD451F45C28700966E06 /* Graphics_iOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Graphics_iOS.swift; path = Source/platform/iOS/Graphics_iOS.swift; sourceTree = SOURCE_ROOT; }; A718CD461F45C28700966E06 /* MView_iOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MView_iOS.swift; path = Source/platform/iOS/MView_iOS.swift; sourceTree = SOURCE_ROOT; }; @@ -471,13 +505,6 @@ C4153A8E1F8793DD001BA5EE /* small-logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "small-logo.png"; sourceTree = ""; }; C43B064C1F9738EF00787A35 /* clip.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = clip.svg; sourceTree = ""; }; C43B06501F9866E400787A35 /* Locus+ToPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locus+ToPath.swift"; sourceTree = ""; }; - C43B06521F989D9300787A35 /* transform.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = transform.reference; sourceTree = ""; }; - C43B06541F98A53600787A35 /* group.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = group.reference; sourceTree = ""; }; - C43B06561F98A7B700787A35 /* arcsgroup.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = arcsgroup.reference; sourceTree = ""; }; - C43B06581F98A84200787A35 /* style.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = style.reference; sourceTree = ""; }; - C43B065A1F98A9E000787A35 /* clipManual.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = clipManual.reference; sourceTree = ""; }; - C43B065E1F98AAA500787A35 /* clip.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = clip.reference; sourceTree = ""; }; - C43B06601F98ACFC00787A35 /* textBasicTransform.reference */ = {isa = PBXFileReference; lastKnownFileType = text; path = textBasicTransform.reference; sourceTree = ""; }; C43B06621F99A33400787A35 /* pathbounds3.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = pathbounds3.svg; path = Bounds/pathbounds3.svg; sourceTree = ""; }; C43B06641F99EE7200787A35 /* cubicAbsolute.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = cubicAbsolute.svg; path = Bounds/cubicAbsolute.svg; sourceTree = ""; }; C43B06651F99EE7300787A35 /* cubicRelative.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = cubicRelative.svg; path = Bounds/cubicRelative.svg; sourceTree = ""; }; @@ -565,29 +592,36 @@ 57CAB1241D7832E000FD8E47 /* svg */ = { isa = PBXGroup; children = ( - 602C561C2081C984003AD452 /* rounded.svg */, - 5BAEA9C8206CEAA20049AAAE /* viewBox.svg */, - C46E83541F94B20E00208037 /* transform.svg */, - C43B064C1F9738EF00787A35 /* clip.svg */, - C4153A8E1F8793DD001BA5EE /* small-logo.png */, - C410148D1F834D280022EE44 /* style.svg */, + 5BAE202F208E163A006BF277 /* arcsgroup.reference */, + 5BAE2028208E1637006BF277 /* circle.reference */, 57CAB1251D7832E000FD8E47 /* circle.svg */, + 5BAE2035208E163C006BF277 /* clip.reference */, + C43B064C1F9738EF00787A35 /* clip.svg */, + 5BAE2027208E1637006BF277 /* clipManual.reference */, + 5BAE202A208E1638006BF277 /* ellipse.reference */, 57CAB1261D7832E000FD8E47 /* ellipse.svg */, + 5BAE202C208E1638006BF277 /* group.reference */, 57CAB1271D7832E000FD8E47 /* group.svg */, + 5BAE2032208E163B006BF277 /* line.reference */, 57CAB1281D7832E000FD8E47 /* line.svg */, + 5BAE2023208E1637006BF277 /* polygon.reference */, 57CAB1291D7832E000FD8E47 /* polygon.svg */, + 5BAE2022208E1637006BF277 /* polyline.reference */, 57CAB12A1D7832E000FD8E47 /* polyline.svg */, + 5BAE2024208E1637006BF277 /* rect.reference */, 57CAB12B1D7832E000FD8E47 /* rect.svg */, + 5BAE2034208E163B006BF277 /* roundRect.reference */, 57CAB12C1D7832E000FD8E47 /* roundRect.svg */, + C4153A8E1F8793DD001BA5EE /* small-logo.png */, + 5BAE202E208E1639006BF277 /* style.reference */, + C410148D1F834D280022EE44 /* style.svg */, + 5BAE202D208E1638006BF277 /* textBasicTransform.reference */, + 5BAE2029208E1638006BF277 /* transform.reference */, + C46E83541F94B20E00208037 /* transform.svg */, + 5BAE2026208E1637006BF277 /* triangle.reference */, 57CAB12D1D7832E000FD8E47 /* triangle.svg */, - 5BAEA9CA206CEB7D0049AAAE /* viewBox.reference */, - C43B06521F989D9300787A35 /* transform.reference */, - C43B06541F98A53600787A35 /* group.reference */, - C43B06561F98A7B700787A35 /* arcsgroup.reference */, - C43B06581F98A84200787A35 /* style.reference */, - C43B065A1F98A9E000787A35 /* clipManual.reference */, - C43B065E1F98AAA500787A35 /* clip.reference */, - C43B06601F98ACFC00787A35 /* textBasicTransform.reference */, + 5BAE2031208E163B006BF277 /* viewBox.reference */, + 5BAEA9C8206CEAA20049AAAE /* viewBox.svg */, ); path = svg; sourceTree = ""; @@ -803,6 +837,7 @@ 57E5E1451E3B393900D1CB28 /* svg */ = { isa = PBXGroup; children = ( + 5BAE201E208E1211006BF277 /* SVGCanvas.swift */, 57E5E1461E3B393900D1CB28 /* SVGConstants.swift */, 57E5E1471E3B393900D1CB28 /* SVGParser.swift */, 57E5E1481E3B393900D1CB28 /* SVGParserError.swift */, @@ -871,14 +906,27 @@ A7E675541EC4211E00BD9ECB /* Bounds */, 5713C4F11E5AD35900BBA4D9 /* Animation */, 57CAB1241D7832E000FD8E47 /* svg */, + 5BAE205A208F2504006BF277 /* w3cSVGTests */, 57CAB1221D782DFC00FD8E47 /* TestUtils.swift */, 57FCD27B1D76EA4600CC0FB6 /* MacawTests.swift */, C4820B191F458D64008CE0FF /* MacawSVGTests.swift */, + 5BAE2057208F24DE006BF277 /* SceneSerialization.swift */, 57FCD27D1D76EA4600CC0FB6 /* Info.plist */, ); path = MacawTests; sourceTree = ""; }; + 5BAE205A208F2504006BF277 /* w3cSVGTests */ = { + isa = PBXGroup; + children = ( + 5BAE2060208F2504006BF277 /* color-prop-02-f-manual.reference */, + 5BAE205B208F2504006BF277 /* color-prop-02-f-manual.svg */, + 5BAE205D208F2504006BF277 /* shapes-circle-01-t-manual.reference */, + 5BAE205C208F2504006BF277 /* shapes-circle-01-t-manual.svg */, + ); + path = w3cSVGTests; + sourceTree = ""; + }; A718CD2C1F45BC5300966E06 /* Platform */ = { isa = PBXGroup; children = ( @@ -1067,39 +1115,50 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5BAE203C208E163D006BF277 /* triangle.reference in Resources */, C4153A8F1F8793DE001BA5EE /* small-logo.png in Resources */, + 5BAE2061208F2504006BF277 /* color-prop-02-f-manual.svg in Resources */, + 5BAE2048208E163D006BF277 /* line.reference in Resources */, 57CAB1361D7832E000FD8E47 /* triangle.svg in Resources */, C43B06691F99FC2300787A35 /* pathbounds4.svg in Resources */, C43B06631F99A33400787A35 /* pathbounds3.svg in Resources */, 5BAEA9C9206CEAA20049AAAE /* viewBox.svg in Resources */, + 5BAE204B208E163D006BF277 /* clip.reference in Resources */, C43B064D1F9738EF00787A35 /* clip.svg in Resources */, + 5BAE2040208E163D006BF277 /* ellipse.reference in Resources */, 57B7A4E11EE70DA5009D78D7 /* logo_base64.txt in Resources */, + 5BAE2047208E163D006BF277 /* viewBox.reference in Resources */, + 5BAE2042208E163D006BF277 /* group.reference in Resources */, C43B06671F99EE7300787A35 /* cubicRelative.svg in Resources */, - C43B06571F98A7B700787A35 /* arcsgroup.reference in Resources */, - C43B06531F989D9300787A35 /* transform.reference in Resources */, - C43B06611F98ACFC00787A35 /* textBasicTransform.reference in Resources */, + 5BAE204A208E163D006BF277 /* roundRect.reference in Resources */, + 5BAE203E208E163D006BF277 /* circle.reference in Resources */, C43B064D1F9738EF00787A35 /* clip.svg in Resources */, 57B7A4E11EE70DA5009D78D7 /* logo_base64.txt in Resources */, - C43B06591F98A84200787A35 /* style.reference in Resources */, - C43B065F1F98AAA500787A35 /* clip.reference in Resources */, C410148E1F834D290022EE44 /* style.svg in Resources */, + 5BAE2063208F2504006BF277 /* shapes-circle-01-t-manual.reference in Resources */, C4BD40BB1F8F58B0003034F0 /* pathbounds1.svg in Resources */, - C43B065B1F98A9E000787A35 /* clipManual.reference in Resources */, C4BD40BC1F8F58B0003034F0 /* pathbounds2.svg in Resources */, + 5BAE2039208E163D006BF277 /* polygon.reference in Resources */, + 5BAE2043208E163D006BF277 /* textBasicTransform.reference in Resources */, 57CAB1301D7832E000FD8E47 /* group.svg in Resources */, + 5BAE2045208E163D006BF277 /* arcsgroup.reference in Resources */, + 5BAE2062208F2504006BF277 /* shapes-circle-01-t-manual.svg in Resources */, + 5BAE203F208E163D006BF277 /* transform.reference in Resources */, + 5BAE2038208E163D006BF277 /* polyline.reference in Resources */, C43B06661F99EE7300787A35 /* cubicAbsolute.svg in Resources */, C46E83551F94B20E00208037 /* transform.svg in Resources */, 57CAB1351D7832E000FD8E47 /* roundRect.svg in Resources */, + 5BAE203D208E163D006BF277 /* clipManual.reference in Resources */, 57CAB12E1D7832E000FD8E47 /* circle.svg in Resources */, + 5BAE2066208F2504006BF277 /* color-prop-02-f-manual.reference in Resources */, 57CAB1331D7832E000FD8E47 /* polyline.svg in Resources */, - 5BAEA9CB206CEB7D0049AAAE /* viewBox.reference in Resources */, 57CAB1311D7832E000FD8E47 /* line.svg in Resources */, 57B7A4DF1EE70D17009D78D7 /* logo.png in Resources */, - 602C561D2081C984003AD452 /* rounded.svg in Resources */, 57CAB1321D7832E000FD8E47 /* polygon.svg in Resources */, + 5BAE203A208E163D006BF277 /* rect.reference in Resources */, + 5BAE2044208E163D006BF277 /* style.reference in Resources */, 57CAB12F1D7832E000FD8E47 /* ellipse.svg in Resources */, 57CAB1341D7832E000FD8E47 /* rect.svg in Resources */, - C43B06551F98A53600787A35 /* group.reference in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1177,6 +1236,7 @@ 57614B1E1F83D15600875933 /* AnimOperators.swift in Sources */, 57614B1F1F83D15600875933 /* Circle.swift in Sources */, 57614B201F83D15600875933 /* Color.swift in Sources */, + 5BAE204C208E1EF4006BF277 /* SVGCanvas.swift in Sources */, 57614B211F83D15600875933 /* PathSegment.swift in Sources */, 57614B221F83D15600875933 /* ImageRenderer.swift in Sources */, 57614B231F83D15600875933 /* PathFunctions.swift in Sources */, @@ -1303,6 +1363,7 @@ 57E5E18C1E3B393900D1CB28 /* Circle.swift in Sources */, 57E5E17D1E3B393900D1CB28 /* Color.swift in Sources */, 57E5E1951E3B393900D1CB28 /* PathSegment.swift in Sources */, + 5BAE201F208E1211006BF277 /* SVGCanvas.swift in Sources */, 57E5E1A41E3B393900D1CB28 /* ImageRenderer.swift in Sources */, 57E5E1621E3B393900D1CB28 /* PathFunctions.swift in Sources */, C4820B181F458D0E008CE0FF /* SVGSerializer.swift in Sources */, @@ -1402,6 +1463,7 @@ C4BD40B81F8F55AB003034F0 /* SVGBoundsTest.swift in Sources */, 5713C4F51E5AE2C300BBA4D9 /* CombineAnimationTests.swift in Sources */, 57CAB1231D782DFC00FD8E47 /* TestUtils.swift in Sources */, + 5BAE2058208F24DE006BF277 /* SceneSerialization.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MacawTests/Bounds/cubicAbsolute.svg b/MacawTests/Bounds/cubicAbsolute.svg index d1e08312..b9607457 100644 --- a/MacawTests/Bounds/cubicAbsolute.svg +++ b/MacawTests/Bounds/cubicAbsolute.svg @@ -1,5 +1,5 @@ - + diff --git a/MacawTests/Bounds/pathbounds3.svg b/MacawTests/Bounds/pathbounds3.svg index ac94df3e..237032bc 100644 --- a/MacawTests/Bounds/pathbounds3.svg +++ b/MacawTests/Bounds/pathbounds3.svg @@ -1,5 +1,5 @@ - + + + + + + + + + + + +

+ Tests if the color datatype is supported. There are multiple syntaxes for + specifying the same color, such as #37F and #3377FF. This test is focussed on the + X11 color names, which are not part of the tiny profile. + Each group of circles uses four forms - 6-digit hex, rbg() integer form, rgb() percentage form, + and named ('X11') colors. It does not use 3-digit hex, because the colors used in this test + cannot be represented in three digit form. +

+ + +

+ Run the test. No interaction required. +

+
+ +

+ For each of the nine groups of circles shown here, all circles must + be identical in color, and the same color as in the reference image. +

+
+ + $RCSfile: color-prop-02-f.svg,v $ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $Revision: 1.9 $ + + + + + diff --git a/MacawTests/w3cSVGTests/shapes-circle-01-t-manual.reference b/MacawTests/w3cSVGTests/shapes-circle-01-t-manual.reference new file mode 100644 index 00000000..a0645510 --- /dev/null +++ b/MacawTests/w3cSVGTests/shapes-circle-01-t-manual.reference @@ -0,0 +1,222 @@ +{ + "contents" : [ + { + "place" : "1, 0, 0, 1, 0, 0", + "node" : "Group", + "opaque" : "true", + "opacity" : "1.0", + "contents" : [ + { + "place" : "1, 0, 0, 1, 0, 0", + "form" : { + "cy" : 100, + "type" : "Circle", + "r" : 50, + "cx" : 100 + }, + "node" : "Shape", + "opaque" : "true", + "opacity" : "1.0", + "stroke" : { + "join" : "miter", + "cap" : "butt", + "fill" : { + "type" : "Color", + "val" : 0 + }, + "dashes" : [ + + ], + "width" : 1 + } + }, + { + "form" : { + "cy" : 100, + "type" : "Circle", + "r" : 35, + "cx" : 220 + }, + "node" : "Shape", + "fill" : { + "type" : "Color", + "val" : 32768 + }, + "opacity" : "1.0", + "stroke" : { + "join" : "miter", + "cap" : "butt", + "fill" : { + "type" : "Color", + "val" : 0 + }, + "dashes" : [ + + ], + "width" : 1 + }, + "place" : "1, 0, 0, 1, 0, 0", + "opaque" : "true" + }, + { + "form" : { + "cy" : 100, + "type" : "Circle", + "r" : 20, + "cx" : 340 + }, + "node" : "Shape", + "fill" : { + "type" : "Color", + "val" : 0 + }, + "opacity" : "1.0", + "stroke" : { + "join" : "miter", + "cap" : "butt", + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "dashes" : [ + + ], + "width" : 4 + }, + "place" : "1, 0, 0, 1, 0, 0", + "opaque" : "true" + }, + { + "form" : { + "cy" : 260, + "type" : "Circle", + "r" : 20, + "cx" : 100 + }, + "node" : "Shape", + "fill" : { + "type" : "Color", + "val" : 16776960 + }, + "opacity" : "1.0", + "stroke" : { + "join" : "miter", + "cap" : "butt", + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "dashes" : [ + + ], + "width" : 4 + }, + "place" : "1, 0, 0, 1, 0, 0", + "opaque" : "true" + }, + { + "place" : "1, 0, 0, 1, 0, 0", + "form" : { + "cy" : 260, + "type" : "Circle", + "r" : 35, + "cx" : 220 + }, + "node" : "Shape", + "opaque" : "true", + "fill" : { + "type" : "Color", + "val" : 255 + }, + "opacity" : "1.0" + }, + { + "place" : "1, 0, 0, 1, 0, 0", + "form" : { + "cy" : 260, + "type" : "Circle", + "r" : 50, + "cx" : 340 + }, + "node" : "Shape", + "opaque" : "true", + "opacity" : "1.0", + "stroke" : { + "join" : "miter", + "cap" : "butt", + "fill" : { + "type" : "Color", + "val" : 32768 + }, + "dashes" : [ + + ], + "width" : 10 + } + } + ] + }, + { + "place" : "1, 0, 0, 1, 0, 0", + "node" : "Group", + "opaque" : "true", + "opacity" : "1.0", + "contents" : [ + { + "baseline" : "bottom", + "fill" : { + "type" : "Color", + "val" : 0 + }, + "node" : "Text", + "align" : "min", + "text" : "$Revision: 1.7 $", + "opacity" : "1.0", + "place" : "1, 0, 0, 1, 10, 340", + "opaque" : "true", + "font" : { + "name" : "SVGFreeSansASCII,sans-serif", + "size" : 32, + "weight" : "normal" + } + } + ] + }, + { + "place" : "1, 0, 0, 1, 0, 0", + "form" : { + "x" : 1, + "w" : 478, + "type" : "Rect", + "y" : 1, + "h" : 358 + }, + "node" : "Shape", + "opaque" : "true", + "opacity" : "1.0", + "stroke" : { + "join" : "miter", + "cap" : "butt", + "fill" : { + "type" : "Color", + "val" : 0 + }, + "dashes" : [ + + ], + "width" : 1 + } + } + ], + "place" : "1, 0, 0, 1, 0, 0", + "node" : "Group", + "opaque" : "true", + "opacity" : "1.0", + "clip" : { + "x" : 0, + "w" : 480, + "type" : "Rect", + "y" : 0, + "h" : 360 + } +} \ No newline at end of file diff --git a/MacawTests/w3cSVGTests/shapes-circle-01-t-manual.svg b/MacawTests/w3cSVGTests/shapes-circle-01-t-manual.svg new file mode 100755 index 00000000..9d8aa46b --- /dev/null +++ b/MacawTests/w3cSVGTests/shapes-circle-01-t-manual.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + +

+Tests the circle element +

+ + +

+Run the test. No interaction required. +

+
+ +

+ Six circles are displayed, with position, size, fill and stroke matching the reference image +

+
+ + $RCSfile: shapes-circle-01-t.svg,v $ + + + + + + + + + + + + + + + + + $Revision: 1.7 $ + + + + + diff --git a/Source/animation/types/ShapeAnimation.swift b/Source/animation/types/ShapeAnimation.swift index 53cd2256..90e72de6 100644 --- a/Source/animation/types/ShapeAnimation.swift +++ b/Source/animation/types/ShapeAnimation.swift @@ -9,21 +9,18 @@ class ShapeAnimation: AnimationImpl { convenience init(animatedNode: Shape, finalValue: Shape, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) { - let nodeId = animatedNode.id let interpolationFunc = { (t: Double) -> Shape in if t == 0 { - let initialNode = Node.nodeBy(id: nodeId) as! Shape - - return Shape(form: initialNode.form, - fill: initialNode.fill, - stroke: initialNode.stroke, - place: initialNode.place, - opaque: initialNode.opaque, - opacity: initialNode.opacity, - clip: initialNode.clip, - effect: initialNode.effect, - visible: initialNode.visible, - tag: initialNode.tag) + return Shape(form: animatedNode.form, + fill: animatedNode.fill, + stroke: animatedNode.stroke, + place: animatedNode.place, + opaque: animatedNode.opaque, + opacity: animatedNode.opacity, + clip: animatedNode.clip, + effect: animatedNode.effect, + visible: animatedNode.visible, + tag: animatedNode.tag) } return finalValue @@ -56,6 +53,25 @@ class ShapeAnimation: AnimationImpl { override public func pause() { stop() } + + open override func reverse() -> Animation { + let factory = { () -> (Double) -> Shape in + let original = self.timeFactory() + return { (t: Double) -> Shape in + return original(1.0 - t) + } + } + + let node = Node.nodeBy(id: nodeId!) + let reversedAnimation = ShapeAnimation(animatedNode: node as! Shape, + factory: factory, + animationDuration: duration, + fps: logicalFps) + reversedAnimation.progress = progress + reversedAnimation.completion = completion + + return reversedAnimation + } } public extension AnimatableVariable { diff --git a/Source/model/geom2d/Path.swift b/Source/model/geom2d/Path.swift index e125738f..52ee970f 100644 --- a/Source/model/geom2d/Path.swift +++ b/Source/model/geom2d/Path.swift @@ -1,11 +1,17 @@ import Foundation +public enum FillRule { + case nonzero, evenodd +} + open class Path: Locus { open let segments: [PathSegment] + open let fillRule: FillRule - public init(segments: [PathSegment] = []) { + public init(segments: [PathSegment] = [], fillRule: FillRule = .nonzero) { self.segments = segments + self.fillRule = fillRule } override open func bounds() -> Rect { diff --git a/Source/platform/iOS/Common_iOS.swift b/Source/platform/iOS/Common_iOS.swift index 349f5ed7..4f418535 100644 --- a/Source/platform/iOS/Common_iOS.swift +++ b/Source/platform/iOS/Common_iOS.swift @@ -77,6 +77,10 @@ extension MFont { class var mSystemFontSize: CGFloat { return UIFont.systemFontSize } + + class var mFamilyNames: [String] { + return UIFont.familyNames + } } extension UIScreen { diff --git a/Source/platform/macOS/Common_macOS.swift b/Source/platform/macOS/Common_macOS.swift index 7ac0eb45..638e38a6 100644 --- a/Source/platform/macOS/Common_macOS.swift +++ b/Source/platform/macOS/Common_macOS.swift @@ -92,6 +92,10 @@ extension NSFont { class var mSystemFontSize: CGFloat { return NSFont.systemFontSize } + + class var mFamilyNames: [String] { + return NSFontManager.shared.availableFontFamilies + } } extension NSScreen { diff --git a/Source/render/RenderUtils.swift b/Source/render/RenderUtils.swift index 9717fec2..4f3462e8 100644 --- a/Source/render/RenderUtils.swift +++ b/Source/render/RenderUtils.swift @@ -90,26 +90,28 @@ class RenderUtils { fatalError("Unsupported node: \(node)") } + static let availableFonts = MFont.mFamilyNames.map{ $0.lowercased() } + class func loadFont(name: String, size: Int) -> MFont? { - let separationSet = CharacterSet(charactersIn: ",") - let names = name.components(separatedBy: separationSet) - var customFont: MFont? = .none - names.forEach { fontName in - if customFont != .none { - return + + let fontPriorities = name.split(separator: ",").map{ String($0).trimmingCharacters(in: CharacterSet(charactersIn: " '")).lowercased() } + for font in fontPriorities { + if availableFonts.contains(font) { + return MFont(name: font, size: CGFloat(size)) } - - if fontName.first == " " { - let index = fontName.index(fontName.startIndex, offsetBy: 1) - let fixedName = String(fontName.suffix(from: index)) - customFont = MFont(name: fixedName, size: CGFloat(size)) - return + + if font == "serif" { + return MFont(name: "Georgia", size: CGFloat(size)) + } + if font == "sans-serif" { + return MFont(name: "Arial", size: CGFloat(size)) + } + if font == "monospace" { + return MFont(name: "Courier", size: CGFloat(size)) } - - customFont = MFont(name: fontName, size: CGFloat(size)) } - return customFont + return .none } class func applyOpacity(_ color: Color, opacity: Double) -> Color { diff --git a/Source/render/ShapeRenderer.swift b/Source/render/ShapeRenderer.swift index f519df4f..d9cb4d92 100644 --- a/Source/render/ShapeRenderer.swift +++ b/Source/render/ShapeRenderer.swift @@ -38,7 +38,12 @@ class ShapeRenderer: NodeRenderer { if shape.fill != nil || shape.stroke != nil { setGeometry(shape.form, ctx: ctx.cgContext!) - drawPath(shape.fill, stroke: shape.stroke, ctx: ctx.cgContext!, opacity: opacity) + + var fillRule = FillRule.nonzero + if let path = shape.form as? Path { + fillRule = path.fillRule + } + drawPath(shape.fill, stroke: shape.stroke, ctx: ctx.cgContext!, opacity: opacity, fillRule: fillRule) } } @@ -102,7 +107,7 @@ class ShapeRenderer: NodeRenderer { return CGRect(x: CGFloat(rect.x), y: CGFloat(rect.y), width: CGFloat(rect.w), height: CGFloat(rect.h)) } - fileprivate func drawPath(_ fill: Fill?, stroke: Stroke?, ctx: CGContext?, opacity: Double) { + fileprivate func drawPath(_ fill: Fill?, stroke: Stroke?, ctx: CGContext?, opacity: Double, fillRule: FillRule) { var shouldStrokePath = false if fill is Gradient || stroke?.fill is Gradient { shouldStrokePath = true @@ -112,15 +117,15 @@ class ShapeRenderer: NodeRenderer { let path = ctx!.path setFill(fill, ctx: ctx, opacity: opacity) if stroke.fill is Gradient && !(fill is Gradient) { - ctx!.drawPath(using: .fill) + ctx!.drawPath(using: fillRule == .nonzero ? .fill : .eoFill) } - drawWithStroke(stroke, ctx: ctx, opacity: opacity, shouldStrokePath: shouldStrokePath, path: path, mode: .fillStroke) + drawWithStroke(stroke, ctx: ctx, opacity: opacity, shouldStrokePath: shouldStrokePath, path: path, mode: fillRule == .nonzero ? .fillStroke : .eoFillStroke) return } if let fill = fill { setFill(fill, ctx: ctx, opacity: opacity) - ctx!.drawPath(using: .fill) + ctx!.drawPath(using: fillRule == .nonzero ? .fill : .eoFill) return } diff --git a/Source/render/TextRenderer.swift b/Source/render/TextRenderer.swift index 81a0ac30..45760dd5 100644 --- a/Source/render/TextRenderer.swift +++ b/Source/render/TextRenderer.swift @@ -79,18 +79,18 @@ class TextRenderer: NodeRenderer { guard let text = text else { return MFont.systemFont(ofSize: 18.0) } - - if let textFont = text.font { - if let customFont = RenderUtils.loadFont(name: textFont.name, size: textFont.size) { - return customFont - } else { - if let weight = getWeight(textFont.weight) { - return MFont.systemFont(ofSize: CGFloat(textFont.size), weight: weight) - } - return MFont.systemFont(ofSize: CGFloat(textFont.size)) - } + guard let textFont = text.font else { + return MFont.systemFont(ofSize: MFont.mSystemFontSize) + } + + if let customFont = RenderUtils.loadFont(name: textFont.name, size: textFont.size) { + return customFont + } else { + if let weight = getWeight(textFont.weight) { + return MFont.systemFont(ofSize: CGFloat(textFont.size), weight: weight) + } + return MFont.systemFont(ofSize: CGFloat(textFont.size)) } - return MFont.systemFont(ofSize: MFont.mSystemFontSize) } fileprivate func getWeight(_ weight: String) -> MFont.Weight? { diff --git a/Source/svg/SVGParser.swift b/Source/svg/SVGParser.swift index eb49c369..0eb43ad8 100644 --- a/Source/svg/SVGParser.swift +++ b/Source/svg/SVGParser.swift @@ -42,10 +42,10 @@ open class SVGParser { } let availableStyleAttributes = ["stroke", "stroke-width", "stroke-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", - "fill", "text-anchor", "clip-path", "fill-opacity", + "fill", "fill-rule", "text-anchor", "clip-path", "fill-opacity", "stop-color", "stop-opacity", "font-family", "font-size", - "font-weight", "opacity", "color"] + "font-weight", "opacity", "color", "visibility"] fileprivate let xmlString: String fileprivate let initialPosition: Transform @@ -88,12 +88,15 @@ open class SVGParser { } } parseSvg(parsedXml.children) - - let group = Group(contents: self.nodes, place: initialPosition) + if let viewBoxParams = viewBoxParams { + let group = viewBoxParams.svgSize != nil ? + SVGCanvas(bounds: Rect(x: 0, y: 0, w: viewBoxParams.svgSize!.w, h: viewBoxParams.svgSize!.h), contents: nodes) : + Group(contents: nodes) addViewBoxClip(toNode: group, viewBoxParams: viewBoxParams) + return group } - return group + return Group(contents: nodes) } fileprivate func prepareSvg(_ children: [XMLIndexer]) { @@ -262,11 +265,18 @@ open class SVGParser { if styleAttributes["display"] == "none" { return .none } + if styleAttributes["visibility"] == "hidden" { + return .none + } + let position = getPosition(element) switch element.name { case "path": - if let path = parsePath(node) { + if var path = parsePath(node) { + if let rule = getFillRule(styleAttributes) { + path = Path(segments: path.segments, fillRule: rule) + } return Shape(form: path, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) } case "line": @@ -629,13 +639,8 @@ open class SVGParser { } fileprivate func getStrokeWidth(_ styleParts: [String: String]) -> Double { - if let strokeWidth = styleParts["stroke-width"] { - let characterSet = NSCharacterSet.decimalDigits.union(NSCharacterSet.punctuationCharacters).inverted - let digitsArray = strokeWidth.components(separatedBy: characterSet) - let digits = digitsArray.joined() - if let value = Double(digits) { - return value - } + if let strokeWidth = styleParts["stroke-width"], let value = doubleFromString(strokeWidth) { + return value } return 1 } @@ -836,7 +841,7 @@ open class SVGParser { if let anchor = textAnchor { if anchor == "middle" { return .mid - } else if anchor == "right" { + } else if anchor == "end" { return .max } } @@ -1261,17 +1266,14 @@ open class SVGParser { } fileprivate func getFontName(_ attributes: [String: String]) -> String? { - return attributes["font-family"] + return attributes["font-family"]?.trimmingCharacters(in: .whitespacesAndNewlines) } fileprivate func getFontSize(_ attributes: [String: String]) -> Int? { - guard let fontSize = attributes["font-size"] else { + guard let fontSize = attributes["font-size"], let size = doubleFromString(fontSize) else { return .none } - if let size = Double(fontSize) { - return (Int(round(size))) - } - return .none + return Int(round(size)) } fileprivate func getFontStyle(_ attributes: [String: String], style: String) -> Bool? { @@ -1331,6 +1333,20 @@ open class SVGParser { } return false } + + fileprivate func getFillRule(_ attributes: [String: String]) -> FillRule? { + if let rule = attributes["fill-rule"] { + switch rule { + case "nonzero": + return .nonzero + case "evenodd": + return .evenodd + default: + return .none + } + } + return .none + } fileprivate func copyNode(_ referenceNode: Node) -> Node? { let pos = referenceNode.place