From 9de08c3cbca863f0e4eb40e1481bc78d869409b6 Mon Sep 17 00:00:00 2001 From: Simon Whitty Date: Sat, 12 Oct 2024 09:46:14 +1100 Subject: [PATCH] remove percent encoding from URL fragments --- Examples/Sources/ViewController.swift | 2 +- Samples.bundle/stars.svg | 20 ++++++++ SwiftDraw/LayerTree.Builder.Layer.swift | 2 +- SwiftDraw/LayerTree.Builder.swift | 20 ++++---- SwiftDraw/URL+Fragment.swift | 48 +++++++++++++++++++ .../Parser.GraphicAttributeTests.swift | 8 ++-- SwiftDrawTests/UseTests.swift | 4 +- SwiftDrawTests/ValueParserTests.swift | 11 +++-- 8 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 Samples.bundle/stars.svg create mode 100644 SwiftDraw/URL+Fragment.swift diff --git a/Examples/Sources/ViewController.swift b/Examples/Sources/ViewController.swift index 28c2ed5..d6092e0 100644 --- a/Examples/Sources/ViewController.swift +++ b/Examples/Sources/ViewController.swift @@ -65,7 +65,7 @@ class ViewController: UIViewController { override func loadView() { let imageView = UIImageView(frame: UIScreen.main.bounds) - imageView.image = SVG(named: "rgba.svg", in: .samples)?.rasterize() + imageView.image = SVG(named: "stars.svg", in: .samples)?.rasterize() imageView.contentMode = .scaleAspectFit imageView.backgroundColor = .white self.view = imageView diff --git a/Samples.bundle/stars.svg b/Samples.bundle/stars.svg new file mode 100644 index 0000000..d2e08a5 --- /dev/null +++ b/Samples.bundle/stars.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftDraw/LayerTree.Builder.Layer.swift b/SwiftDraw/LayerTree.Builder.Layer.swift index 1799012..e2d5b13 100644 --- a/SwiftDraw/LayerTree.Builder.Layer.swift +++ b/SwiftDraw/LayerTree.Builder.Layer.swift @@ -41,7 +41,7 @@ extension LayerTree.Builder { func makeUseLayerContents(from use: DOM.Use, with state: State) throws -> LayerTree.Layer.Contents { guard - let id = use.href.fragment, + let id = use.href.fragmentID, let element = svg.firstGraphicsElement(with: id) else { throw LayerTree.Error.invalid("missing referenced element: \(use.href)") } diff --git a/SwiftDraw/LayerTree.Builder.swift b/SwiftDraw/LayerTree.Builder.swift index 013cfa7..0c8b067 100644 --- a/SwiftDraw/LayerTree.Builder.swift +++ b/SwiftDraw/LayerTree.Builder.swift @@ -124,14 +124,14 @@ extension LayerTree { } func createClipShapes(for element: DOM.GraphicsElement) -> [Shape] { - guard let clipId = element.attributes.clipPath?.fragment, + guard let clipId = element.attributes.clipPath?.fragmentID, let clip = svg.defs.clipPaths.first(where: { $0.id == clipId }) else { return [] } return clip.childElements.compactMap{ Builder.makeShape(from: $0) } } func createMaskLayer(for element: DOM.GraphicsElement) -> Layer? { - guard let maskId = element.attributes.mask?.fragment, + guard let maskId = element.attributes.mask?.fragmentID, let mask = svg.defs.masks.first(where: { $0.id == maskId }) else { return nil } let l = Layer() @@ -145,7 +145,7 @@ extension LayerTree { } func makeFilters(for state: State) -> [Filter] { - guard let filterId = state.filter?.fragment, + guard let filterId = state.filter?.fragmentID, let filter = svg.defs.filters.first(where: { $0.id == filterId }) else { return [] } return filter.effects } @@ -191,15 +191,15 @@ extension LayerTree.Builder { .withAlpha(state.fillOpacity).maybeNone() if case .url(let patternId) = state.fill, - let element = svg.defs.patterns.first(where: { $0.id == patternId.fragment }) { + let element = svg.defs.patterns.first(where: { $0.id == patternId.fragmentID }) { let pattern = makePattern(for: element) return LayerTree.FillAttributes(pattern: pattern, rule: state.fillRule, opacity: state.fillOpacity) } else if case .url(let gradientId) = state.fill, - let element = svg.defs.linearGradients.first(where: { $0.id == gradientId.fragment }), + let element = svg.defs.linearGradients.first(where: { $0.id == gradientId.fragmentID }), let gradient = makeGradient(for: element) { return LayerTree.FillAttributes(linear: gradient, rule: state.fillRule, opacity: state.fillOpacity) } else if case .url(let gradientId) = state.fill, - let element = svg.defs.radialGradients.first(where: { $0.id == gradientId.fragment }), + let element = svg.defs.radialGradients.first(where: { $0.id == gradientId.fragmentID }), let gradient = makeGradient(for: element) { return LayerTree.FillAttributes(radial: gradient, rule: state.fillRule, opacity: state.fillOpacity) } else { @@ -208,7 +208,7 @@ extension LayerTree.Builder { } func makeLinearGradient(for gradientId: URL) -> LayerTree.LinearGradient? { - guard let element = svg.defs.linearGradients.first(where: { $0.id == gradientId.fragment }), + guard let element = svg.defs.linearGradients.first(where: { $0.id == gradientId.fragmentID }), let gradient = makeGradient(for: element) else { return nil } @@ -216,7 +216,7 @@ extension LayerTree.Builder { } func makeRadialGradient(for gradientId: URL) -> LayerTree.RadialGradient? { - guard let element = svg.defs.radialGradients.first(where: { $0.id == gradientId.fragment }), + guard let element = svg.defs.radialGradients.first(where: { $0.id == gradientId.fragmentID }), let gradient = makeGradient(for: element) else { return nil } @@ -249,7 +249,7 @@ extension LayerTree.Builder { let y2 = element.y2 ?? 0 var stops = [LayerTree.Gradient.Stop]() - if let id = element.href?.fragment, + if let id = element.href?.fragmentID, let reference = svg.defs.linearGradients.first(where: { $0.id == id }) { stops = makeGradientStops(for: reference) } else { @@ -272,7 +272,7 @@ extension LayerTree.Builder { func makeGradient(for element: DOM.RadialGradient) -> LayerTree.RadialGradient? { var stops = [LayerTree.Gradient.Stop]() - if let id = element.href?.fragment, + if let id = element.href?.fragmentID, let reference = svg.defs.radialGradients.first(where: { $0.id == id }) { stops = makeGradientStops(for: reference) } else { diff --git a/SwiftDraw/URL+Fragment.swift b/SwiftDraw/URL+Fragment.swift new file mode 100644 index 0000000..ffe4748 --- /dev/null +++ b/SwiftDraw/URL+Fragment.swift @@ -0,0 +1,48 @@ +// +// URL+Fragment.swift +// SwiftDraw +// +// Created by Simon Whitty on 12/10/24. +// Copyright 2024 Simon Whitty +// +// Distributed under the permissive zlib license +// Get the latest version from here: +// +// https://github.com/swhitty/SwiftDraw +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +import Foundation + +extension URL { + + var fragmentID: String? { + #if compiler(>=5.7) && canImport(Darwin) + if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) { + return fragment(percentEncoded: false) + } else { + return fragment + } + #else + return fragment + #endif + } + +} diff --git a/SwiftDrawTests/Parser.GraphicAttributeTests.swift b/SwiftDrawTests/Parser.GraphicAttributeTests.swift index a617128..5578497 100644 --- a/SwiftDrawTests/Parser.GraphicAttributeTests.swift +++ b/SwiftDrawTests/Parser.GraphicAttributeTests.swift @@ -82,9 +82,9 @@ final class ParserGraphicAttributeTests: XCTestCase { XCTAssertEqual(parsed.fillOpacity, 0.25) XCTAssertEqual(parsed.fillRule, .evenodd) XCTAssertEqual(parsed.transform!, [.scale(sx: 15, sy: 15)]) - XCTAssertEqual(parsed.clipPath?.fragment, "circlePath") - XCTAssertEqual(parsed.mask?.fragment, "fancyMask") - XCTAssertEqual(parsed.filter?.fragment, "blur") + XCTAssertEqual(parsed.clipPath?.fragmentID, "circlePath") + XCTAssertEqual(parsed.mask?.fragmentID, "fancyMask") + XCTAssertEqual(parsed.filter?.fragmentID, "blur") } func testCircle() throws { @@ -93,7 +93,7 @@ final class ParserGraphicAttributeTests: XCTestCase { let parsed = try XMLParser().parseGraphicsElement(el) let circle = parsed as? DOM.Circle XCTAssertNotNil(circle) - XCTAssertEqual(circle?.style.clipPath?.fragment, "cp1") + XCTAssertEqual(circle?.style.clipPath?.fragmentID, "cp1") XCTAssertEqual(circle?.style.fill, .color(.keyword(.black))) XCTAssertEqual(circle?.style.strokeWidth, 2) } diff --git a/SwiftDrawTests/UseTests.swift b/SwiftDrawTests/UseTests.swift index f77d950..ef04155 100644 --- a/SwiftDrawTests/UseTests.swift +++ b/SwiftDrawTests/UseTests.swift @@ -38,7 +38,7 @@ final class UseTests: XCTestCase { var node = ["xlink:href": "#line2", "href": "#line1"] var parsed = try XMLParser().parseUse(node) - XCTAssertEqual(parsed.href.fragment, "line2") + XCTAssertEqual(parsed.href.fragmentID, "line2") XCTAssertNil(parsed.x) XCTAssertNil(parsed.y) @@ -46,7 +46,7 @@ final class UseTests: XCTestCase { node["y"] = "30" parsed = try XMLParser().parseUse(node) - XCTAssertEqual(parsed.href.fragment, "line2") + XCTAssertEqual(parsed.href.fragmentID, "line2") XCTAssertEqual(parsed.x, 20) XCTAssertEqual(parsed.y, 30) } diff --git a/SwiftDrawTests/ValueParserTests.swift b/SwiftDrawTests/ValueParserTests.swift index bdb699e..385b525 100644 --- a/SwiftDrawTests/ValueParserTests.swift +++ b/SwiftDrawTests/ValueParserTests.swift @@ -126,15 +126,16 @@ final class ValueParserTests: XCTestCase { } func testUrl() { - XCTAssertEqual(try parser.parseUrl("#testingId").fragment, "testingId") +#if compiler(>=5.9) && canImport(Darwin) + XCTAssertEqual(try parser.parseUrl("#testing🐟").fragmentID, "testing🐟") +#else + XCTAssertEqual(try parser.parseUrl("#testing").fragmentID, "testing") +#endif XCTAssertEqual(try parser.parseUrl("http://www.google.com").host, "www.google.com") - - //XCTAssertThrowsError(try parser.parseUrl("www.google.com")) - //XCTAssertThrowsError(try parser.parseUrl("sd")) } func testUrlSelector() { - XCTAssertEqual(try parser.parseUrlSelector("url(#testingId)").fragment, "testingId") + XCTAssertEqual(try parser.parseUrlSelector("url(#testingId)").fragmentID, "testingId") XCTAssertEqual(try parser.parseUrlSelector("url(http://www.google.com)").host, "www.google.com") XCTAssertThrowsError(try parser.parseUrlSelector("url(#testingId) other"))