diff --git a/Macaw.xcodeproj/project.pbxproj b/Macaw.xcodeproj/project.pbxproj index 8eaf5d54..8284b563 100644 --- a/Macaw.xcodeproj/project.pbxproj +++ b/Macaw.xcodeproj/project.pbxproj @@ -670,6 +670,15 @@ A74C8326229EB77B0085A832 /* masking-path-13-f-manual.reference in Resources */ = {isa = PBXBuildFile; fileRef = A74C8325229EB77B0085A832 /* masking-path-13-f-manual.reference */; }; A74C832C229FB7690085A832 /* color-prop-04-t-manual-osx.svg in Resources */ = {isa = PBXBuildFile; fileRef = A74C832B229FB7690085A832 /* color-prop-04-t-manual-osx.svg */; }; A74C832E229FBA4C0085A832 /* color-prop-04-t-manual-osx.reference in Resources */ = {isa = PBXBuildFile; fileRef = A74C832D229FBA4C0085A832 /* color-prop-04-t-manual-osx.reference */; }; + A75A2F5922AE92B40056A89D /* pservers-pattern-03-f-manual.svg in Resources */ = {isa = PBXBuildFile; fileRef = A75A2F5822AE92B40056A89D /* pservers-pattern-03-f-manual.svg */; }; + A75A2F5B22AE96050056A89D /* pservers-pattern-03-f-manual.reference in Resources */ = {isa = PBXBuildFile; fileRef = A75A2F5A22AE96050056A89D /* pservers-pattern-03-f-manual.reference */; }; + A75A2F6122AF818B0056A89D /* pservers-pattern-02-f-manual.svg in Resources */ = {isa = PBXBuildFile; fileRef = A75A2F6022AF818B0056A89D /* pservers-pattern-02-f-manual.svg */; }; + A75A2F6322AF81D90056A89D /* pservers-pattern-02-f-manual.reference in Resources */ = {isa = PBXBuildFile; fileRef = A75A2F6222AF81D80056A89D /* pservers-pattern-02-f-manual.reference */; }; + A75A2F7522AFBD0C0056A89D /* pservers-pattern-09-f-manual.svg in Resources */ = {isa = PBXBuildFile; fileRef = A75A2F7422AFBD0C0056A89D /* pservers-pattern-09-f-manual.svg */; }; + A75A2F7722AFBD5C0056A89D /* pservers-pattern-09-f-manual.reference in Resources */ = {isa = PBXBuildFile; fileRef = A75A2F7622AFBD5C0056A89D /* pservers-pattern-09-f-manual.reference */; }; + A75A2F8522AFC6570056A89D /* pservers-pattern-02-f-manual.png in Resources */ = {isa = PBXBuildFile; fileRef = A75A2F8222AFC6560056A89D /* pservers-pattern-02-f-manual.png */; }; + A75A2F8622AFC6570056A89D /* pservers-pattern-03-f-manual.png in Resources */ = {isa = PBXBuildFile; fileRef = A75A2F8322AFC6560056A89D /* pservers-pattern-03-f-manual.png */; }; + A75A2F8722AFC6570056A89D /* pservers-pattern-09-f-manual.png in Resources */ = {isa = PBXBuildFile; fileRef = A75A2F8422AFC6560056A89D /* pservers-pattern-09-f-manual.png */; }; A7E675561EC4213500BD9ECB /* NodeBoundsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E675551EC4213500BD9ECB /* NodeBoundsTests.swift */; }; C410148E1F834D290022EE44 /* style.svg in Resources */ = {isa = PBXBuildFile; fileRef = C410148D1F834D280022EE44 /* style.svg */; }; C4153A8F1F8793DE001BA5EE /* small-logo.png in Resources */ = {isa = PBXBuildFile; fileRef = C4153A8E1F8793DD001BA5EE /* small-logo.png */; }; @@ -1248,6 +1257,15 @@ A74C8325229EB77B0085A832 /* masking-path-13-f-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "masking-path-13-f-manual.reference"; sourceTree = ""; }; A74C832B229FB7690085A832 /* color-prop-04-t-manual-osx.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "color-prop-04-t-manual-osx.svg"; sourceTree = ""; }; A74C832D229FBA4C0085A832 /* color-prop-04-t-manual-osx.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "color-prop-04-t-manual-osx.reference"; sourceTree = ""; }; + A75A2F5822AE92B40056A89D /* pservers-pattern-03-f-manual.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "pservers-pattern-03-f-manual.svg"; sourceTree = ""; }; + A75A2F5A22AE96050056A89D /* pservers-pattern-03-f-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "pservers-pattern-03-f-manual.reference"; sourceTree = ""; }; + A75A2F6022AF818B0056A89D /* pservers-pattern-02-f-manual.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "pservers-pattern-02-f-manual.svg"; sourceTree = ""; }; + A75A2F6222AF81D80056A89D /* pservers-pattern-02-f-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "pservers-pattern-02-f-manual.reference"; sourceTree = ""; }; + A75A2F7422AFBD0C0056A89D /* pservers-pattern-09-f-manual.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "pservers-pattern-09-f-manual.svg"; sourceTree = ""; }; + A75A2F7622AFBD5C0056A89D /* pservers-pattern-09-f-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "pservers-pattern-09-f-manual.reference"; sourceTree = ""; }; + A75A2F8222AFC6560056A89D /* pservers-pattern-02-f-manual.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pservers-pattern-02-f-manual.png"; sourceTree = ""; }; + A75A2F8322AFC6560056A89D /* pservers-pattern-03-f-manual.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pservers-pattern-03-f-manual.png"; sourceTree = ""; }; + A75A2F8422AFC6560056A89D /* pservers-pattern-09-f-manual.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pservers-pattern-09-f-manual.png"; sourceTree = ""; }; A7E675551EC4213500BD9ECB /* NodeBoundsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NodeBoundsTests.swift; path = Bounds/NodeBoundsTests.swift; sourceTree = ""; }; C410148D1F834D280022EE44 /* style.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = style.svg; sourceTree = ""; }; C4153A8E1F8793DD001BA5EE /* small-logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "small-logo.png"; sourceTree = ""; }; @@ -1410,6 +1428,9 @@ 4295954322A0F7CE00869079 /* shapes-rect-06-f-manual.png */, 4295954422A0F7CE00869079 /* shapes-polyline-02-t-manual.png */, 4295954522A0F7CE00869079 /* shapes-polygon-01-t-manual.png */, + A75A2F8222AFC6560056A89D /* pservers-pattern-02-f-manual.png */, + A75A2F8322AFC6560056A89D /* pservers-pattern-03-f-manual.png */, + A75A2F8422AFC6560056A89D /* pservers-pattern-09-f-manual.png */, ); path = png; sourceTree = ""; @@ -1979,6 +2000,12 @@ A74C82DB229E35D20085A832 /* pservers-grad-24-f-manual.svg */, 5B1AE1F020B6A669007EECCB /* pservers-grad-stops-01-f-manual.reference */, 5B1AE1EB20B6A669007EECCB /* pservers-grad-stops-01-f-manual.svg */, + A75A2F7622AFBD5C0056A89D /* pservers-pattern-09-f-manual.reference */, + A75A2F7422AFBD0C0056A89D /* pservers-pattern-09-f-manual.svg */, + A75A2F6222AF81D80056A89D /* pservers-pattern-02-f-manual.reference */, + A75A2F6022AF818B0056A89D /* pservers-pattern-02-f-manual.svg */, + A75A2F5A22AE96050056A89D /* pservers-pattern-03-f-manual.reference */, + A75A2F5822AE92B40056A89D /* pservers-pattern-03-f-manual.svg */, 5B1AE1A220B6A669007EECCB /* render-elems-01-t-manual.reference */, 5B1AE1E920B6A669007EECCB /* render-elems-01-t-manual.svg */, 5B1AE19020B6A669007EECCB /* render-elems-02-t-manual.reference */, @@ -2281,6 +2308,7 @@ A74C822D2297D5350085A832 /* coords-trans-13-f-manual.reference in Resources */, 5B1AE26C20B6A669007EECCB /* painting-fill-05-b-manual.svg in Resources */, 429595AF22A0F7CE00869079 /* shapes-polyline-02-t-manual.png in Resources */, + A75A2F8622AFC6570056A89D /* pservers-pattern-03-f-manual.png in Resources */, 5B37139920BE95D7004BB6EE /* pservers-grad-02-b-manual.reference in Resources */, 4295955022A0F7CE00869079 /* painting-fill-02-t-manual.png in Resources */, 4295954622A0F7CE00869079 /* text-fonts-02-t-manual.png in Resources */, @@ -2336,6 +2364,7 @@ 42DAF9B022A6130D0095B936 /* color-prop-03-t-manual.svg in Resources */, 5B1AE2CD20B6A669007EECCB /* paths-data-10-t-manual.svg in Resources */, 42DAF9AE22A612EC0095B936 /* painting-control-01-f-manual.svg in Resources */, + A75A2F7522AFBD0C0056A89D /* pservers-pattern-09-f-manual.svg in Resources */, 5B1AE29C20B6A669007EECCB /* paths-data-01-t-manual.reference in Resources */, 4295955822A0F7CE00869079 /* shapes-ellipse-03-f-manual.png in Resources */, 4295959F22A0F7CE00869079 /* shapes-rect-07-f-manual.png in Resources */, @@ -2356,6 +2385,7 @@ A74C82E5229E35D30085A832 /* pservers-grad-13-b-manual.svg in Resources */, A74C8308229E9CCE0085A832 /* masking-mask-02-f-manual.svg in Resources */, 5B1AE2B220B6A669007EECCB /* types-basic-01-f-manual.svg in Resources */, + A75A2F7722AFBD5C0056A89D /* pservers-pattern-09-f-manual.reference in Resources */, 58944BDA20AC8A9A00657640 /* logo_base64.txt in Resources */, 5B1AE2C520B6A669007EECCB /* coords-trans-02-t-manual.svg in Resources */, A74C81F12292B3940085A832 /* color-prop-05-t-manual.svg in Resources */, @@ -2440,6 +2470,7 @@ 429595A622A0F7CE00869079 /* pservers-grad-stops-01-f-manual.png in Resources */, 5B1AE25120B6A669007EECCB /* coords-trans-05-t-manual.reference in Resources */, 5B1AE29120B6A669007EECCB /* coords-transformattr-01-f-manual.svg in Resources */, + A75A2F8522AFC6570056A89D /* pservers-pattern-02-f-manual.png in Resources */, 6A616BFA2129560A006A07FA /* MacawTests.bundle in Resources */, 4295957F22A0F7CE00869079 /* metadata-example-01-t-manual.png in Resources */, 5B1AE2D320B6A669007EECCB /* shapes-rect-04-f-manual.reference in Resources */, @@ -2460,6 +2491,7 @@ A74C81F32292B45E0085A832 /* color-prop-05-t-manual.reference in Resources */, 4295958922A0F7CE00869079 /* masking-path-02-b-manual.png in Resources */, 5B1AE23420B6A669007EECCB /* painting-control-03-f-manual.reference in Resources */, + A75A2F8722AFC6570056A89D /* pservers-pattern-09-f-manual.png in Resources */, 4295955F22A0F7CE00869079 /* coords-transformattr-05-f-manual.png in Resources */, A74C82342297D8B40085A832 /* shapes-rect-03-t-manual.svg in Resources */, 5B1AE26820B6A669007EECCB /* shapes-polyline-01-t-manual.svg in Resources */, @@ -2518,6 +2550,7 @@ 5B1AE26020B6A669007EECCB /* shapes-polygon-02-t-manual.reference in Resources */, 4295959022A0F7CE00869079 /* painting-control-02-f-manual.png in Resources */, 5B1AE2AA20B6A669007EECCB /* struct-frag-03-t-manual.svg in Resources */, + A75A2F5922AE92B40056A89D /* pservers-pattern-03-f-manual.svg in Resources */, 5B1AE24720B6A669007EECCB /* painting-stroke-01-t-manual.reference in Resources */, 5B1AE29B20B6A669007EECCB /* shapes-line-01-t-manual.reference in Resources */, 429595A722A0F7CE00869079 /* paths-data-08-t-manual.png in Resources */, @@ -2543,6 +2576,7 @@ 5B1AE2B720B6A669007EECCB /* struct-frag-03-t-manual.reference in Resources */, 5B1AE25C20B6A669007EECCB /* shapes-intro-01-t-manual.svg in Resources */, 57CAB12E1D7832E000FD8E47 /* circle.svg in Resources */, + A75A2F6122AF818B0056A89D /* pservers-pattern-02-f-manual.svg in Resources */, 5B1AE2D820B6A669007EECCB /* metadata-example-01-t-manual.svg in Resources */, 4295957422A0F7CE00869079 /* paths-data-12-t-manual.png in Resources */, 5B1AE2C620B6A669007EECCB /* painting-fill-05-b-manual.reference in Resources */, @@ -2554,6 +2588,7 @@ 5B1AE2DE20B6A669007EECCB /* painting-control-03-f-manual.svg in Resources */, 5B1AE25520B6A669007EECCB /* paths-data-15-t-manual.reference in Resources */, 5B1AE24D20B6A669007EECCB /* paths-data-13-t-manual.svg in Resources */, + A75A2F6322AF81D90056A89D /* pservers-pattern-02-f-manual.reference in Resources */, A74C82202297D4810085A832 /* coords-trans-14-f-manual.svg in Resources */, 5B270B5D214BBC27001AD741 /* clearColor.reference in Resources */, 57CAB1311D7832E000FD8E47 /* line.svg in Resources */, @@ -2620,6 +2655,7 @@ 5B1AE27C20B6A669007EECCB /* shapes-rect-04-f-manual.svg in Resources */, 5B1AE2DB20B6A669007EECCB /* color-prop-01-b-manual.reference in Resources */, 4295959122A0F7CE00869079 /* coords-coord-02-t-manual.png in Resources */, + A75A2F5B22AE96050056A89D /* pservers-pattern-03-f-manual.reference in Resources */, 4295958022A0F7CE00869079 /* coords-trans-04-t-manual.png in Resources */, 42DB451422A52A4200091DC6 /* pservers-grad-22-b-manual.png in Resources */, 5B1AE2BF20B6A669007EECCB /* shapes-ellipse-02-t-manual.reference in Resources */, diff --git a/MacawTests/MacawSVGTests.swift b/MacawTests/MacawSVGTests.swift index fb22ef30..ae26c796 100644 --- a/MacawTests/MacawSVGTests.swift +++ b/MacawTests/MacawSVGTests.swift @@ -19,8 +19,8 @@ class MacawSVGTests: XCTestCase { private let testFolderName = "MacawTestOutputData" private let shouldComparePNGImages = true - private let multipleTestsWillRun = false - private let shouldSaveFaildedTestImage = false + private let multipleTestsWillRun = true + private let shouldSaveFaildedTestImage = true override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. @@ -136,6 +136,65 @@ class MacawSVGTests: XCTestCase { } } + func getImage(from svgName: String) -> MImage { + let bundle = Bundle(for: type(of: TestUtils())) + do { + let node = try SVGParser.parse(resource: svgName, fromBundle: bundle) + + var frame = node.bounds + if frame == nil, let group = node as? Group { + frame = Group(contents: group.contents).bounds + } + + let image = node.toNativeImage(size: frame?.size() ?? Size(w: 100, h: 100)) + return image + } catch { + XCTFail(error.localizedDescription) + } + + XCTFail() + return MImage() + } + + func saveImage(image: MImage, fileName: String) { + #if os(OSX) + guard let data = image.tiffRepresentation else { + return + } + #endif + + #if os(iOS) + guard let data = image.pngData() else { + return + } + #endif + + let _ = writeToFile(data: data, fileName: "\(fileName).png") + } + + fileprivate func setupTestFolderDirectory() { + guard let myDocuments = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + return + } + + let testDirectoryPath = myDocuments.appendingPathComponent("\(testFolderName)") + + do { + if !multipleTestsWillRun { + try FileManager.default.removeItem(at: testDirectoryPath) + } + + var isDirectory: ObjCBool = ObjCBool(true) + if !FileManager.default.fileExists(atPath: testDirectoryPath.absoluteString, isDirectory: &isDirectory) { + try FileManager.default.createDirectory(at: testDirectoryPath, withIntermediateDirectories: true, attributes: .none) + } + } catch { + XCTFail(error.localizedDescription) + return + } + } + + func testViewBox() { validate("viewBox") } @@ -194,17 +253,16 @@ class MacawSVGTests: XCTestCase { if let path = bundle.path(forResource: referenceFile, ofType: "reference") { let referenceContent = try String(contentsOfFile: path) - let nodeContent = String(data: getJSONData(node: node), encoding: String.Encoding.utf8) + let nodeContent = String(data: getJSONData(node: node), encoding: .utf8) if nodeContent != referenceContent { XCTFail("nodeContent is not equal to referenceContent") } - + + #if os(OSX) let nativeImage = getImage(from: referenceFile) - //To save new PNG image for test, uncomment this //saveImage(image: nativeImage, fileName: referenceFile) - #if os(OSX) if shouldComparePNGImages { validateImage(nodeImage: nativeImage, referenceFile: referenceFile) } @@ -814,62 +872,15 @@ class MacawSVGTests: XCTestCase { validateJSON("masking-mask-02-f-manual") } - func getImage(from svgName: String) -> MImage { - let bundle = Bundle(for: type(of: TestUtils())) - do { - let node = try SVGParser.parse(resource: svgName, fromBundle: bundle) - - var frame = node.bounds - if frame == nil, let group = node as? Group { - frame = Group(contents: group.contents).bounds - } - - let image = node.toNativeImage(size: frame?.size() ?? Size.init(w: 100, h: 100)) - return image - } catch { - XCTFail(error.localizedDescription) - } - - XCTFail() - return MImage() + func testPserversPattern02() { + validateJSON("pservers-pattern-02-f-manual") } - func saveImage(image: MImage, fileName: String) { - #if os(OSX) - guard let data = image.tiffRepresentation else { - return - } - #endif - - #if os(iOS) - guard let data = image.pngData() else { - return - } - #endif - - let _ = writeToFile(data: data, fileName: "\(fileName).png") + func testPserversPattern03() { + validateJSON("pservers-pattern-03-f-manual") } - fileprivate func setupTestFolderDirectory() { - guard let myDocuments = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { - return - } - - let testDirectoryPath = myDocuments.appendingPathComponent("\(testFolderName)") - - do { - if !multipleTestsWillRun { - try FileManager.default.removeItem(at: testDirectoryPath) - } - - var isDirectory: ObjCBool = ObjCBool(true) - if !FileManager.default.fileExists(atPath: testDirectoryPath.absoluteString, isDirectory: &isDirectory) { - try FileManager.default.createDirectory(at: testDirectoryPath, withIntermediateDirectories: true, attributes: .none) - } - } catch { - XCTFail(error.localizedDescription) - return - } + func testPserversPattern09() { + validateJSON("pservers-pattern-09-f-manual") } - } diff --git a/MacawTests/png/pservers-pattern-02-f-manual.png b/MacawTests/png/pservers-pattern-02-f-manual.png new file mode 100644 index 00000000..04c5723e Binary files /dev/null and b/MacawTests/png/pservers-pattern-02-f-manual.png differ diff --git a/MacawTests/png/pservers-pattern-03-f-manual.png b/MacawTests/png/pservers-pattern-03-f-manual.png new file mode 100644 index 00000000..5989cb79 Binary files /dev/null and b/MacawTests/png/pservers-pattern-03-f-manual.png differ diff --git a/MacawTests/png/pservers-pattern-09-f-manual.png b/MacawTests/png/pservers-pattern-09-f-manual.png new file mode 100644 index 00000000..7bd060c4 Binary files /dev/null and b/MacawTests/png/pservers-pattern-09-f-manual.png differ diff --git a/MacawTests/w3c-test-suite.md b/MacawTests/w3c-test-suite.md index 55f40ea9..81c78e56 100644 --- a/MacawTests/w3c-test-suite.md +++ b/MacawTests/w3c-test-suite.md @@ -10,12 +10,12 @@ There are [521 SVG tests](https://github.com/web-platform-tests/wpt/tree/master/ * [switch/object](https://www.w3.org/TR/SVG11/backward.html) (7) The rest 306 tests can be split into following categories: -* 38.6% passed (118) +* 39.5% passed (121) * 14.1% filters (43) [#390](https://github.com/exyte/Macaw/issues/390) * 8.5% images (26) [wpt issue](https://github.com/web-platform-tests/wpt/issues/11178) * 2.6% markers (8) [#392](https://github.com/exyte/Macaw/issues/392) * 20.6% text (63) [#391](https://github.com/exyte/Macaw/issues/391) -* 15.7% blocked by issues (48) +* 14.7% blocked by issues (45) Status of each test: @@ -209,14 +209,14 @@ Status of each test: |[pservers-grad-24-f-manual](w3cSVGTests/pservers-grad-24-f-manual.svg) | ✅ | |[pservers-grad-stops-01-f-manual](w3cSVGTests/pservers-grad-stops-01-f-manual.svg) | ✅ | |[pservers-pattern-01-b-manual](w3cSVGTests/pservers-pattern-01-b-manual.svg) | [#203](https://github.com/exyte/Macaw/issues/203) | -|[pservers-pattern-02-f-manual](w3cSVGTests/pservers-pattern-02-f-manual.svg) | [#203](https://github.com/exyte/Macaw/issues/203) | -|[pservers-pattern-03-f-manual](w3cSVGTests/pservers-pattern-03-f-manual.svg) | [#203](https://github.com/exyte/Macaw/issues/203) | +|[pservers-pattern-02-f-manual](w3cSVGTests/pservers-pattern-02-f-manual.svg) | ✅ | +|[pservers-pattern-03-f-manual](w3cSVGTests/pservers-pattern-03-f-manual.svg) | ✅ | |[pservers-pattern-04-f-manual](w3cSVGTests/pservers-pattern-04-f-manual.svg) | [#203](https://github.com/exyte/Macaw/issues/203) | |[pservers-pattern-05-f-manual](w3cSVGTests/pservers-pattern-05-f-manual.svg) | [#203](https://github.com/exyte/Macaw/issues/203) | |[pservers-pattern-06-f-manual](w3cSVGTests/pservers-pattern-06-f-manual.svg) | [#203](https://github.com/exyte/Macaw/issues/203) | |[pservers-pattern-07-f-manual](w3cSVGTests/pservers-pattern-07-f-manual.svg) | [#203](https://github.com/exyte/Macaw/issues/203) | |[pservers-pattern-08-f-manual](w3cSVGTests/pservers-pattern-08-f-manual.svg) | [#203](https://github.com/exyte/Macaw/issues/203) | -|[pservers-pattern-09-f-manual](w3cSVGTests/pservers-pattern-09-f-manual.svg) | [#203](https://github.com/exyte/Macaw/issues/203) | +|[pservers-pattern-09-f-manual](w3cSVGTests/pservers-pattern-09-f-manual.svg) | ✅ | |[render-elems-01-t-manual](w3cSVGTests/render-elems-01-t-manual.svg) | ✅ | |[render-elems-02-t-manual](w3cSVGTests/render-elems-02-t-manual.svg) | ✅ | |[render-elems-03-t-manual](w3cSVGTests/render-elems-03-t-manual.svg) | ✅ | diff --git a/MacawTests/w3cSVGTests/pservers-pattern-02-f-manual.reference b/MacawTests/w3cSVGTests/pservers-pattern-02-f-manual.reference new file mode 100644 index 00000000..f7742263 --- /dev/null +++ b/MacawTests/w3cSVGTests/pservers-pattern-02-f-manual.reference @@ -0,0 +1,79 @@ +{ + "contents" : [ + { + "contents" : [ + { + "form" : { + "h" : 480, + "type" : "Rect", + "w" : 480, + "x" : 0, + "y" : 0 + }, + "node" : "Shape" + } + ], + "node" : "Group" + }, + { + "contents" : [ + { + "align" : "min", + "baseline" : "bottom", + "fill" : { + "type" : "Color", + "val" : 0 + }, + "font" : { + "name" : "SVGFreeSansASCII,sans-serif", + "size" : 32, + "weight" : "normal" + }, + "node" : "Text", + "place" : "1, 0, 0, 1, 10, 340", + "text" : "$Revision: 1.3 $" + } + ], + "node" : "Group" + }, + { + "form" : { + "h" : 358, + "type" : "Rect", + "w" : 478, + "x" : 1, + "y" : 1 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + } + ], + "layout" : { + "scalingMode" : "meet", + "svgSize" : { + "height" : "100.0%", + "width" : "100.0%" + }, + "viewBox" : { + "h" : 360, + "type" : "Rect", + "w" : 480, + "x" : 0, + "y" : 0 + }, + "xAligningMode" : "mid", + "yAligningMode" : "mid" + }, + "node" : "Canvas" +} \ No newline at end of file diff --git a/MacawTests/w3cSVGTests/pservers-pattern-02-f-manual.svg b/MacawTests/w3cSVGTests/pservers-pattern-02-f-manual.svg new file mode 100644 index 00000000..d2e8655a --- /dev/null +++ b/MacawTests/w3cSVGTests/pservers-pattern-02-f-manual.svg @@ -0,0 +1,61 @@ + + + + + + + + + + +

+ Test that the 'patternTransform' attribute has an effect on the 'pattern' element. +

+ + +

+ Run the test. No interaction required. +

+
+ +

+ The test is passed if the testframe is filled with a blue and white + diamond pattern. +

+
+ + $RCSfile: pservers-pattern-02-f.svg,v $ + + + + + + + + + + + + + + + + + + $Revision: 1.3 $ + + + + diff --git a/MacawTests/w3cSVGTests/pservers-pattern-03-f-manual.reference b/MacawTests/w3cSVGTests/pservers-pattern-03-f-manual.reference new file mode 100644 index 00000000..ac3e85ca --- /dev/null +++ b/MacawTests/w3cSVGTests/pservers-pattern-03-f-manual.reference @@ -0,0 +1,342 @@ +{ + "contents" : [ + { + "contents" : [ + { + "contents" : [ + { + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "form" : { + "h" : 100, + "type" : "Rect", + "w" : 100, + "x" : 0, + "y" : 0 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + }, + { + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "form" : { + "h" : 100, + "type" : "Rect", + "w" : 100, + "x" : 100, + "y" : 0 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + }, + { + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "form" : { + "h" : 100, + "type" : "Rect", + "w" : 100, + "x" : 200, + "y" : 0 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + }, + { + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "form" : { + "h" : 100, + "type" : "Rect", + "w" : 100, + "x" : 300, + "y" : 0 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + }, + { + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "form" : { + "h" : 100, + "type" : "Rect", + "w" : 100, + "x" : 0, + "y" : 100 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + }, + { + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "form" : { + "h" : 100, + "type" : "Rect", + "w" : 100, + "x" : 100, + "y" : 100 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + }, + { + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "form" : { + "h" : 100, + "type" : "Rect", + "w" : 100, + "x" : 200, + "y" : 100 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + }, + { + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "form" : { + "h" : 100, + "type" : "Rect", + "w" : 100, + "x" : 300, + "y" : 100 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + } + ], + "node" : "Group", + "place" : "1, 0, 0, 1, 40, 60" + } + ], + "node" : "Group" + }, + { + "contents" : [ + { + "align" : "min", + "baseline" : "bottom", + "fill" : { + "type" : "Color", + "val" : 0 + }, + "font" : { + "name" : "SVGFreeSansASCII,sans-serif", + "size" : 32, + "weight" : "normal" + }, + "node" : "Text", + "place" : "1, 0, 0, 1, 10, 340", + "text" : "$Revision: 1.1 $" + } + ], + "node" : "Group" + }, + { + "form" : { + "h" : 358, + "type" : "Rect", + "w" : 478, + "x" : 1, + "y" : 1 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + }, + { + "contents" : [ + { + "fill" : { + "type" : "Color", + "val" : 16711680 + }, + "form" : { + "h" : 20, + "type" : "Rect", + "w" : 478, + "x" : 1, + "y" : 1 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + }, + { + "align" : "mid", + "baseline" : "bottom", + "fill" : { + "type" : "Color", + "val" : 16777215 + }, + "font" : { + "name" : "SVGFreeSansASCII,sans-serif", + "size" : 20, + "weight" : "bold" + }, + "node" : "Text", + "place" : "1, 0, 0, 1, 240, 18", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 0.5 + }, + "text" : "DRAFT" + } + ], + "node" : "Group" + } + ], + "layout" : { + "scalingMode" : "meet", + "svgSize" : { + "height" : "100.0%", + "width" : "100.0%" + }, + "viewBox" : { + "h" : 360, + "type" : "Rect", + "w" : 480, + "x" : 0, + "y" : 0 + }, + "xAligningMode" : "mid", + "yAligningMode" : "mid" + }, + "node" : "Canvas" +} \ No newline at end of file diff --git a/MacawTests/w3cSVGTests/pservers-pattern-03-f-manual.svg b/MacawTests/w3cSVGTests/pservers-pattern-03-f-manual.svg new file mode 100644 index 00000000..a363e5e0 --- /dev/null +++ b/MacawTests/w3cSVGTests/pservers-pattern-03-f-manual.svg @@ -0,0 +1,91 @@ + + + + + + + + + + +

+ Test that empty patterns are not rendered, and that the fallback color is used instead. +

+ + +

+ Run the test. No interaction required. +

+
+ +

+ The test is passed if there are 8 green rectangles visible, and no red. +

+
+ + $RCSfile: pservers-pattern-03-f.svg,v $ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $Revision: 1.1 $ + + + + + + DRAFT + + diff --git a/MacawTests/w3cSVGTests/pservers-pattern-09-f-manual.reference b/MacawTests/w3cSVGTests/pservers-pattern-09-f-manual.reference new file mode 100644 index 00000000..be104dd1 --- /dev/null +++ b/MacawTests/w3cSVGTests/pservers-pattern-09-f-manual.reference @@ -0,0 +1,166 @@ +{ + "contents" : [ + { + "contents" : [ + { + "form" : { + "h" : 200, + "type" : "Rect", + "w" : 200, + "x" : 1, + "y" : 1 + }, + "node" : "Shape" + }, + { + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "form" : { + "h" : 200, + "type" : "Rect", + "w" : 200, + "x" : 1, + "y" : 1 + }, + "node" : "Shape" + }, + { + "fill" : { + "type" : "Color", + "val" : 65280 + }, + "form" : { + "h" : 200, + "type" : "Rect", + "w" : 200, + "x" : 201, + "y" : 1 + }, + "node" : "Shape" + } + ], + "node" : "Group" + }, + { + "contents" : [ + { + "align" : "min", + "baseline" : "bottom", + "fill" : { + "type" : "Color", + "val" : 0 + }, + "font" : { + "name" : "SVGFreeSansASCII,sans-serif", + "size" : 32, + "weight" : "normal" + }, + "node" : "Text", + "place" : "1, 0, 0, 1, 10, 340", + "text" : "$Revision: 1.1 $" + } + ], + "node" : "Group" + }, + { + "form" : { + "h" : 358, + "type" : "Rect", + "w" : 478, + "x" : 1, + "y" : 1 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + }, + { + "contents" : [ + { + "fill" : { + "type" : "Color", + "val" : 16711680 + }, + "form" : { + "h" : 20, + "type" : "Rect", + "w" : 478, + "x" : 1, + "y" : 1 + }, + "node" : "Shape", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 1 + } + }, + { + "align" : "mid", + "baseline" : "bottom", + "fill" : { + "type" : "Color", + "val" : 16777215 + }, + "font" : { + "name" : "SVGFreeSansASCII,sans-serif", + "size" : 20, + "weight" : "bold" + }, + "node" : "Text", + "place" : "1, 0, 0, 1, 240, 18", + "stroke" : { + "cap" : "butt", + "dashes" : [ + + ], + "fill" : { + "type" : "Color", + "val" : 0 + }, + "join" : "miter", + "width" : 0.5 + }, + "text" : "DRAFT" + } + ], + "node" : "Group" + } + ], + "layout" : { + "scalingMode" : "meet", + "svgSize" : { + "height" : "100.0%", + "width" : "100.0%" + }, + "viewBox" : { + "h" : 360, + "type" : "Rect", + "w" : 480, + "x" : 0, + "y" : 0 + }, + "xAligningMode" : "mid", + "yAligningMode" : "mid" + }, + "node" : "Canvas" +} \ No newline at end of file diff --git a/MacawTests/w3cSVGTests/pservers-pattern-09-f-manual.svg b/MacawTests/w3cSVGTests/pservers-pattern-09-f-manual.svg new file mode 100644 index 00000000..f98cdc29 --- /dev/null +++ b/MacawTests/w3cSVGTests/pservers-pattern-09-f-manual.svg @@ -0,0 +1,70 @@ + + + + + + + + + + +

+ Test that an invalid xlink:href on a 'pattern' element has no effect on the pattern, and that the + pattern isn't rendered since the default 'width' and 'height' is 0. + A subtest that explicitly specifies 'width' and 'height' as 0 is added as a reference. + Both of these cases should result in the fallback color being used. +

+ + +

+ Run the test. No interaction required. +

+
+ +

+ The test is passed if there is a green rectangle visible on the page, and no red. +

+
+ + $RCSfile: pservers-pattern-09-f.svg,v $ + + + + + + + + + + + + + + + + + + + + + + + + + $Revision: 1.1 $ + + + + + + DRAFT + + diff --git a/Source/model/draw/Pattern.swift b/Source/model/draw/Pattern.swift index 20576331..32d250b7 100644 --- a/Source/model/draw/Pattern.swift +++ b/Source/model/draw/Pattern.swift @@ -1,12 +1,16 @@ open class Pattern: Fill { + public let viewBox: Rect public let content: Node public let bounds: Rect public let userSpace: Bool + public let place: Transform - public init(content: Node, bounds: Rect, userSpace: Bool = false) { + public init(content: Node, bounds: Rect, viewBox: Rect, userSpace: Bool = false, place: Transform) { + self.viewBox = viewBox self.content = content self.bounds = bounds self.userSpace = userSpace + self.place = place } } diff --git a/Source/model/scene/Image.swift b/Source/model/scene/Image.swift index 8f9cbfe9..a04ff3d7 100644 --- a/Source/model/scene/Image.swift +++ b/Source/model/scene/Image.swift @@ -150,7 +150,7 @@ open class Image: Node { } // Base64 image - let decodableFormat = ["image/png", "image/jpg", "image/svg+xml"] + let decodableFormat = ["image/png", "image/jpg", "image/jpeg"] for format in decodableFormat { let prefix = "data:\(format);base64," if src.hasPrefix(prefix) { @@ -163,10 +163,6 @@ open class Image: Node { } } - #if os(iOS) - return MImage(named: src) - #elseif os(OSX) - return MImage(contentsOfFile: src) - #endif + return MImage(named: src) ?? MImage(contentsOfFile: src) } } diff --git a/Source/render/ShapeRenderer.swift b/Source/render/ShapeRenderer.swift index 22242651..b997fbe0 100644 --- a/Source/render/ShapeRenderer.swift +++ b/Source/render/ShapeRenderer.swift @@ -186,7 +186,14 @@ class ShapeRenderer: NodeRenderer { let boundsTranform = BoundsUtils.transformForLocusInRespectiveCoords(respectiveLocus: pattern.bounds, absoluteLocus: shape.form) patternBounds = pattern.bounds.applying(boundsTranform) } - let tileImage = renderer.renderToImage(bounds: patternBounds, inset: 0) + + var viewBox = pattern.viewBox + if viewBox == .zero() { + viewBox = patternBounds + } + + let tileImage = renderer.renderToImage(bounds: viewBox, inset: 0) + ctx?.concatenate(pattern.place.toCG()) ctx?.clip() ctx?.draw(tileImage.cgImage!, in: patternBounds.toCG(), byTiling: true) } diff --git a/Source/svg/SVGParser.swift b/Source/svg/SVGParser.swift index 0310c786..88d05856 100644 --- a/Source/svg/SVGParser.swift +++ b/Source/svg/SVGParser.swift @@ -365,12 +365,24 @@ open class SVGParser { parentPattern = defPatterns[id] } + var viewBox: Rect = parentPattern?.viewBox ?? .zero() + if let viewBoxString = element.allAttributes["viewBox"]?.text { + let nums = viewBoxString.components(separatedBy: .whitespaces).map { Double($0) } + if nums.count == 4, let x = nums[0], let y = nums[1], let w = nums[2], let h = nums[3] { + viewBox = Rect(x: x, y: y, w: w, h: h) + } + } + let x = getDoubleValue(element, attribute: "x") ?? parentPattern?.bounds.x ?? 0 let y = getDoubleValue(element, attribute: "y") ?? parentPattern?.bounds.y ?? 0 let w = getDoubleValue(element, attribute: "width") ?? parentPattern?.bounds.w ?? 0 let h = getDoubleValue(element, attribute: "height") ?? parentPattern?.bounds.h ?? 0 let bounds = Rect(x: x, y: y, w: w, h: h) + guard bounds.w > 0 && bounds.h > 0 else { + return .none + } + var userSpace = parentPattern?.userSpace ?? false if let units = element.allAttributes["patternUnits"]?.text, units == "userSpaceOnUse" { userSpace = true @@ -380,6 +392,8 @@ open class SVGParser { contentUserSpace = false } + let place = getPatternPlace(element) + var contentNode: Node? if pattern.children.isEmpty { if let parentPattern = parentPattern { @@ -399,7 +413,10 @@ open class SVGParser { contentNode = Group(contents: shapes) } - return UserSpacePattern(content: contentNode!, bounds: bounds, userSpace: userSpace, contentUserSpace: contentUserSpace) + if let contentNode = contentNode { + return UserSpacePattern(content: contentNode, bounds: bounds, viewBox: viewBox, userSpace: userSpace, contentUserSpace: contentUserSpace, place: place) + } + return .none } fileprivate func parseGroup(_ group: XMLIndexer, style: [String: String]) throws -> Group? { @@ -417,7 +434,14 @@ open class SVGParser { fileprivate func getPosition(_ element: SWXMLHash.XMLElement) -> Transform { guard let transformAttribute = element.allAttributes["transform"]?.text else { - return Transform.identity + return .identity + } + return parseTransformationAttribute(transformAttribute) + } + + fileprivate func getPatternPlace(_ element: SWXMLHash.XMLElement) -> Transform { + guard let transformAttribute = element.allAttributes["patternTransform"]?.text else { + return .identity } return parseTransformationAttribute(transformAttribute) } @@ -653,7 +677,11 @@ open class SVGParser { if let pattern = defPatterns[colorId] { return getPatternFill(pattern: pattern, locus: locus) } + if let fallbackColor = fillColor.split(separator: " ").last { + fillColor = String(fallbackColor) + } } + if fillColor == SVGKeys.currentColor, let currentColor = groupStyle[SVGKeys.color] { fillColor = currentColor } @@ -664,14 +692,14 @@ open class SVGParser { fileprivate func getPatternFill(pattern: UserSpacePattern, locus: Locus?) -> Pattern { if pattern.userSpace == false && pattern.contentUserSpace == true { let tranform = BoundsUtils.transformForLocusInRespectiveCoords(respectiveLocus: pattern.bounds, absoluteLocus: locus!) - return Pattern(content: pattern.content, bounds: pattern.bounds.applying(tranform), userSpace: true) + return Pattern(content: pattern.content, bounds: pattern.bounds.applying(tranform), viewBox: pattern.viewBox, userSpace: true, place: pattern.place) } if pattern.userSpace == true && pattern.contentUserSpace == false { if let patternNode = BoundsUtils.createNodeFromRespectiveCoords(respectiveNode: pattern.content, absoluteLocus: locus!) { - return Pattern(content: patternNode, bounds: pattern.bounds, userSpace: pattern.userSpace) + return Pattern(content: patternNode, bounds: pattern.bounds, viewBox: pattern.viewBox, userSpace: pattern.userSpace, place: pattern.place) } } - return Pattern(content: pattern.content, bounds: pattern.bounds, userSpace: true) + return Pattern(content: pattern.content, bounds: pattern.bounds, viewBox: pattern.viewBox, userSpace: true, place: pattern.place) } fileprivate func getStroke(_ styleParts: [String: String], groupStyle: [String: String] = [:]) -> Stroke? { @@ -1392,7 +1420,7 @@ open class SVGParser { fileprivate func parseIdFromUrl(_ urlString: String) -> String? { if urlString.hasPrefix("url") { - return urlString.substringWithOffset(fromStart: 5, fromEnd: 1) + return urlString.slice(from: "(#", to: ")") } return .none } @@ -1881,6 +1909,14 @@ fileprivate extension String { let end = index(endIndex, offsetBy: -fromEnd) return String(self[start.. String? { + return (range(of: from)?.upperBound).flatMap { substringFrom in + (range(of: to, range: substringFrom..