diff --git a/DOM/Sources/DOM.SVG.swift b/DOM/Sources/DOM.SVG.swift
index 917ea2f..c551ca9 100644
--- a/DOM/Sources/DOM.SVG.swift
+++ b/DOM/Sources/DOM.SVG.swift
@@ -77,7 +77,13 @@ package extension DOM {
struct ClipPath: ContainerElement {
package var id: String
+ package var clipPathUnits: Units?
package var childElements = [GraphicsElement]()
+
+ package enum Units: String {
+ case userSpaceOnUse
+ case objectBoundingBox
+ }
}
final class Mask: GraphicsElement, ContainerElement {
diff --git a/DOM/Sources/Parser.XML.SVG.swift b/DOM/Sources/Parser.XML.SVG.swift
index 2a57a36..901cbab 100644
--- a/DOM/Sources/Parser.XML.SVG.swift
+++ b/DOM/Sources/Parser.XML.SVG.swift
@@ -152,9 +152,12 @@ package extension XMLParser {
let att = try parseAttributes(e)
let id: String = try att.parseString("id")
+ let units: DOM.ClipPath.Units? = try att.parseRaw("clipPathUnits")
let children = try parseGraphicsElements(e.children)
- return DOM.ClipPath(id: id, childElements: children)
+ var clip = DOM.ClipPath(id: id, childElements: children)
+ clip.clipPathUnits = units
+ return clip
}
func parseMasks(_ e: XML.Element) throws -> [DOM.Mask] {
diff --git a/SwiftDraw/Sources/LayerTree/LayerTree.Builder.swift b/SwiftDraw/Sources/LayerTree/LayerTree.Builder.swift
index f5d4322..ca9ae1c 100644
--- a/SwiftDraw/Sources/LayerTree/LayerTree.Builder.swift
+++ b/SwiftDraw/Sources/LayerTree/LayerTree.Builder.swift
@@ -142,6 +142,7 @@ extension LayerTree {
l.transform = Builder.createTransforms(from: attributes.transform ?? [])
l.clip = makeClipShapes(for: element)
l.clipRule = attributes.clipRule
+ l.clipUnits = makeClipUnits(for: element)
l.mask = createMaskLayer(for: element)
l.opacity = state.opacity
l.filters = makeFilters(for: state)
@@ -173,6 +174,16 @@ extension LayerTree {
return clip.childElements.compactMap(makeClipShape)
}
+ func makeClipUnits(for element: DOM.GraphicsElement) -> ClipUnits {
+ let attributes = DOM.presentationAttributes(for: element, styles: svg.styles)
+ guard let clipID = attributes.clipPath?.fragmentID,
+ let clip = svg.defs.clipPaths.first(where: { $0.id == clipID }) else { return .userSpaceOnUse }
+ switch clip.clipPathUnits {
+ case .objectBoundingBox: return .objectBoundingBox
+ case .userSpaceOnUse, nil: return .userSpaceOnUse
+ }
+ }
+
func makeClipShape(for element: DOM.GraphicsElement) -> ClipShape? {
guard let shape = Builder.makeShape(from: element) else {
return nil
diff --git a/SwiftDraw/Sources/LayerTree/LayerTree.Layer.swift b/SwiftDraw/Sources/LayerTree/LayerTree.Layer.swift
index cd85607..5f3057c 100644
--- a/SwiftDraw/Sources/LayerTree/LayerTree.Layer.swift
+++ b/SwiftDraw/Sources/LayerTree/LayerTree.Layer.swift
@@ -37,6 +37,7 @@ extension LayerTree {
var transform: [Transform] = []
var clip: [ClipShape] = []
var clipRule: FillRule?
+ var clipUnits: ClipUnits = .userSpaceOnUse
var mask: Layer?
var filters: [Filter] = []
@@ -79,6 +80,7 @@ extension LayerTree {
opacity.hash(into: &hasher)
transform.hash(into: &hasher)
clip.hash(into: &hasher)
+ clipUnits.hash(into: &hasher)
mask.hash(into: &hasher)
filters.hash(into: &hasher)
}
@@ -90,6 +92,7 @@ extension LayerTree {
lhs.transform == rhs.transform &&
lhs.clip == rhs.clip &&
lhs.clipRule == rhs.clipRule &&
+ lhs.clipUnits == rhs.clipUnits &&
lhs.mask == rhs.mask &&
lhs.filters == rhs.filters
}
diff --git a/SwiftDraw/Sources/LayerTree/LayerTree.Shape.swift b/SwiftDraw/Sources/LayerTree/LayerTree.Shape.swift
index 8f6fafa..df3d696 100644
--- a/SwiftDraw/Sources/LayerTree/LayerTree.Shape.swift
+++ b/SwiftDraw/Sources/LayerTree/LayerTree.Shape.swift
@@ -43,6 +43,11 @@ extension LayerTree {
var shape: Shape
var transform: Transform.Matrix
}
+
+ enum ClipUnits: Hashable {
+ case userSpaceOnUse
+ case objectBoundingBox
+ }
}
extension LayerTree.Shape {
diff --git a/SwiftDraw/Sources/Renderer/Renderer.SFSymbol+ClipPath.swift b/SwiftDraw/Sources/Renderer/Renderer.SFSymbol+ClipPath.swift
new file mode 100644
index 0000000..9e97d85
--- /dev/null
+++ b/SwiftDraw/Sources/Renderer/Renderer.SFSymbol+ClipPath.swift
@@ -0,0 +1,111 @@
+//
+// Renderer.SFSymbol+ClipPath.swift
+// SwiftDraw
+//
+// Created by SwiftDraw contributors
+// Copyright 2026 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.
+//
+
+#if canImport(CoreGraphics)
+import Foundation
+import CoreGraphics
+import SwiftDrawDOM
+
+extension SFSymbolRenderer {
+
+ /// Bake the intersection of `path` with the union of `clipShapes` into a single LayerTree.Path.
+ /// Returns `nil` when the result is empty (path lies entirely outside the clip region).
+ /// Returns the original `path` unchanged when the running platform predates CGPath boolean ops.
+ /// `clipCTM` is applied to each clip shape so it sits in the same coordinate space as `path`.
+ static func intersect(path: LayerTree.Path,
+ with clipShapes: [LayerTree.ClipShape],
+ clipRule: LayerTree.FillRule?,
+ clipUnits: LayerTree.ClipUnits,
+ clipCTM: LayerTree.Transform.Matrix) -> LayerTree.Path? {
+ guard !clipShapes.isEmpty else { return path }
+
+ let pathCG = CGProvider().createPath(from: .path(path))
+ guard !pathCG.isEmpty else { return nil }
+
+ let unitsTransform: LayerTree.Transform.Matrix
+ switch clipUnits {
+ case .userSpaceOnUse:
+ unitsTransform = .identity
+ case .objectBoundingBox:
+ let bounds = path.bounds
+ guard bounds.width > 0, bounds.height > 0 else { return nil }
+ unitsTransform = LayerTree.Transform.Matrix(
+ a: bounds.width, b: 0,
+ c: 0, d: bounds.height,
+ tx: bounds.minX, ty: bounds.minY
+ )
+ }
+
+ let clipCG = makeUnionedCGPath(
+ from: clipShapes,
+ preCTM: unitsTransform,
+ postCTM: clipCTM
+ )
+ guard !clipCG.isEmpty else { return nil }
+
+ let rule: CGPathFillRule = (clipRule == .evenodd) ? .evenOdd : .winding
+
+ if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) {
+ let intersected = pathCG.intersection(clipCG, using: rule)
+ if intersected.isEmpty { return nil }
+ return intersected.makePath()
+ } else {
+ // Fall back for older deployment targets: keep the path when its bbox lies
+ // entirely inside the clip bbox; drop it when entirely outside; otherwise
+ // pass it through unchanged so existing behavior is preserved.
+ return fallbackIntersect(pathCG: pathCG, clipCG: clipCG, original: path)
+ }
+ }
+
+ private static func makeUnionedCGPath(from clipShapes: [LayerTree.ClipShape],
+ preCTM: LayerTree.Transform.Matrix,
+ postCTM: LayerTree.Transform.Matrix) -> CGPath {
+ let provider = CGProvider()
+ let union = CGMutablePath()
+ for clipShape in clipShapes {
+ let combined = preCTM
+ .concatenated(clipShape.transform)
+ .concatenated(postCTM)
+ let transform = provider.createTransform(from: combined)
+ let shapePath = provider.createPath(from: clipShape.shape)
+ union.addPath(shapePath, transform: transform)
+ }
+ return union
+ }
+
+ private static func fallbackIntersect(pathCG: CGPath, clipCG: CGPath, original: LayerTree.Path) -> LayerTree.Path? {
+ let pathBounds = pathCG.boundingBoxOfPath
+ let clipBounds = clipCG.boundingBoxOfPath
+ if !pathBounds.intersects(clipBounds) { return nil }
+ return original
+ }
+}
+#endif
diff --git a/SwiftDraw/Sources/Renderer/Renderer.SFSymbol.swift b/SwiftDraw/Sources/Renderer/Renderer.SFSymbol.swift
index 0b5abf4..f582091 100644
--- a/SwiftDraw/Sources/Renderer/Renderer.SFSymbol.swift
+++ b/SwiftDraw/Sources/Renderer/Renderer.SFSymbol.swift
@@ -198,10 +198,6 @@ extension SFSymbolRenderer {
let isSFSymbolLayer = containsAcceptedName(layer.class)
guard isSFSymbolLayer || layer.opacity > 0 else { return [] }
- guard layer.clip.isEmpty else {
- print("Warning:", "clip-path unsupported in SF Symbols.", to: &.standardError)
- return []
- }
guard layer.mask == nil else {
print("Warning:", "mask unsupported in SF Symbols.", to: &.standardError)
return []
@@ -237,9 +233,41 @@ extension SFSymbolRenderer {
}
}
+ if !layer.clip.isEmpty {
+ paths = applyClip(to: paths,
+ clipShapes: layer.clip,
+ clipRule: layer.clipRule,
+ clipUnits: layer.clipUnits,
+ ctm: ctm)
+ }
+
return paths
}
+ static func applyClip(to paths: [SymbolPath],
+ clipShapes: [LayerTree.ClipShape],
+ clipRule: LayerTree.FillRule?,
+ clipUnits: LayerTree.ClipUnits,
+ ctm: LayerTree.Transform.Matrix) -> [SymbolPath] {
+#if canImport(CoreGraphics)
+ var result = [SymbolPath]()
+ result.reserveCapacity(paths.count)
+ for symbolPath in paths {
+ if let clipped = intersect(path: symbolPath.path,
+ with: clipShapes,
+ clipRule: clipRule,
+ clipUnits: clipUnits,
+ clipCTM: ctm) {
+ result.append(SymbolPath(class: symbolPath.class, path: clipped))
+ }
+ }
+ return result
+#else
+ print("Warning:", "clip-path requires CoreGraphics.", to: &.standardError)
+ return paths
+#endif
+ }
+
static func makeFillPath(for shape: LayerTree.Shape,
fill: LayerTree.FillAttributes,
preserve: Bool) -> LayerTree.Path? {
diff --git a/SwiftDraw/Tests/Renderer/Renderer.SFSymbolClipPathTests.swift b/SwiftDraw/Tests/Renderer/Renderer.SFSymbolClipPathTests.swift
new file mode 100644
index 0000000..c84f0fd
--- /dev/null
+++ b/SwiftDraw/Tests/Renderer/Renderer.SFSymbolClipPathTests.swift
@@ -0,0 +1,299 @@
+//
+// Renderer.SFSymbolClipPathTests.swift
+// SwiftDraw
+//
+// Created by SwiftDraw contributors
+// Copyright 2026 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 SwiftDrawDOM
+import XCTest
+@testable import SwiftDraw
+
+#if canImport(CoreGraphics)
+final class RendererSFSymbolClipPathTests: XCTestCase {
+
+ // MARK: - Issue #37 reproduction
+
+ /// The exact SVG from the GitHub issue used as the reproducer for the missing clip-path
+ /// support warning. Pre-fix this rendered nothing and emitted "clip-path unsupported".
+ /// Post-fix it must produce the baked intersection (the clipping circle).
+ func testIssue37_minimalRectClippedByCircle_producesIntersectedPath() throws {
+ let svg = try DOM.SVG.parse(#"""
+
+ """#)
+
+ let template = try SFSymbolTemplate.parse(SFSymbolRenderer.render(svg: svg))
+
+ XCTAssertFalse(template.regular.contents.paths.isEmpty)
+ XCTAssertFalse(template.ultralight.contents.paths.isEmpty)
+ XCTAssertFalse(template.black.contents.paths.isEmpty)
+ }
+
+ // MARK: - Clip shape coverage (asserted on raw symbol paths in user space)
+
+ func testClipRect_intersectionIsClippingRect() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-rect.svg")
+ XCTAssertEqual(paths.count, 1)
+ // 100x100 rect ∩ rect(20,20,60,60) = rect(20,20,60,60)
+ assertBounds(paths[0].path.bounds,
+ equals: .init(x: 20, y: 20, width: 60, height: 60))
+ }
+
+ func testClipCircle_intersectionIsClippingCircle() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-circle.svg")
+ XCTAssertEqual(paths.count, 1)
+ // 100x100 rect ∩ circle(50,50,r=30) = circle(50,50,r=30) → bounds(20,20,60,60)
+ assertBounds(paths[0].path.bounds,
+ equals: .init(x: 20, y: 20, width: 60, height: 60),
+ accuracy: 0.5)
+ }
+
+ func testClipEllipse_intersectionIsClippingEllipse() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-ellipse.svg")
+ XCTAssertEqual(paths.count, 1)
+ // 100x100 rect ∩ ellipse(cx=50,cy=50,rx=40,ry=20) = ellipse → bounds(10,30,80,40)
+ assertBounds(paths[0].path.bounds,
+ equals: .init(x: 10, y: 30, width: 80, height: 40),
+ accuracy: 0.5)
+ }
+
+ func testClipPolygon_intersectionMatchesPolygon() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-polygon.svg")
+ XCTAssertEqual(paths.count, 1)
+ // Triangle(50,10)-(90,90)-(10,90) → bounds(10,10,80,80)
+ assertBounds(paths[0].path.bounds,
+ equals: .init(x: 10, y: 10, width: 80, height: 80),
+ accuracy: 0.5)
+ }
+
+ func testClipPathElement_intersectionMatchesPath() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-path-element.svg")
+ XCTAssertEqual(paths.count, 1)
+ assertBounds(paths[0].path.bounds,
+ equals: .init(x: 20, y: 20, width: 60, height: 60),
+ accuracy: 0.5)
+ }
+
+ func testClipMultiShape_unionsAllChildren() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-multi-shape.svg")
+ XCTAssertEqual(paths.count, 1)
+ // Two overlapping circles centered at (35,50) and (65,50) with r=20.
+ // Union extents: x ∈ [15,85], y ∈ [30,70]
+ assertBounds(paths[0].path.bounds,
+ equals: .init(x: 15, y: 30, width: 70, height: 40),
+ accuracy: 0.5)
+ }
+
+ func testClipOnGroup_appliesToAllChildren() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-group.svg")
+ // Two rects in a clipped group; each gets independently intersected with the rect clip.
+ // Each result fits inside (20,20,60,60).
+ XCTAssertGreaterThan(paths.count, 0)
+ for sp in paths {
+ let b = sp.path.bounds
+ XCTAssertGreaterThanOrEqual(b.minX, 19.5)
+ XCTAssertLessThanOrEqual(b.maxX, 80.5)
+ XCTAssertGreaterThanOrEqual(b.minY, 19.5)
+ XCTAssertLessThanOrEqual(b.maxY, 80.5)
+ }
+ }
+
+ func testClipRuleEvenOdd_producesAnnulusBounds() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-rule-evenodd.svg")
+ // Outer 80x80 with inner 40x40 hole using evenodd → bounds remain 10,10,80,80
+ XCTAssertEqual(paths.count, 1)
+ assertBounds(paths[0].path.bounds,
+ equals: .init(x: 10, y: 10, width: 80, height: 80),
+ accuracy: 0.5)
+ }
+
+ func testClipUnitsObjectBoundingBox_appliesShapeRelativeCoords() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-units-bbox.svg")
+ XCTAssertEqual(paths.count, 1)
+ // rect(20,20,60,60) clipped by bbox-relative rect (0.25,0.25,0.5,0.5)
+ // → user-space rect (35,35,30,30)
+ assertBounds(paths[0].path.bounds,
+ equals: .init(x: 35, y: 35, width: 30, height: 30),
+ accuracy: 0.5)
+ }
+
+ func testClipFullyContainsShape_keepsOriginalBounds() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-contains.svg")
+ XCTAssertEqual(paths.count, 1)
+ assertBounds(paths[0].path.bounds,
+ equals: .init(x: 20, y: 20, width: 60, height: 60),
+ accuracy: 0.5)
+ }
+
+ func testClipFullyOutsideShape_dropsPath() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-outside.svg")
+ XCTAssertEqual(paths.count, 0)
+ }
+
+ func testClipChildTransform_isApplied() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-transform-child.svg")
+ XCTAssertEqual(paths.count, 1)
+ // Clip child rect(0,0,40,40) with transform translate(30,30) → effective (30,30,40,40)
+ assertBounds(paths[0].path.bounds,
+ equals: .init(x: 30, y: 30, width: 40, height: 40),
+ accuracy: 0.5)
+ }
+
+ func testClipPropagatesThroughElementTransform() throws {
+ let paths = try makeSymbolPaths(forResource: "clip-transformed-element.svg")
+ XCTAssertEqual(paths.count, 1)
+ // 100x100 rect drawn with translate(50,50), clipped by circle(0,0,r=30) in local space.
+ // Post-transform clip is a circle at (50,50) with r=30 → bounds (20,20,60,60)
+ assertBounds(paths[0].path.bounds,
+ equals: .init(x: 20, y: 20, width: 60, height: 60),
+ accuracy: 0.5)
+ }
+
+ // MARK: - Full pipeline smoke (renders to SF Symbol template)
+
+ func testFullPipeline_clipCircle_emitsThreeWeightVariants() throws {
+ let url = try Bundle.test.url(forResource: "clip-circle.svg")
+ let template = try SFSymbolTemplate.parse(SFSymbolRenderer.render(fileURL: url))
+ XCTAssertEqual(template.regular.contents.paths.count, 1)
+ XCTAssertEqual(template.ultralight.contents.paths.count, 1)
+ XCTAssertEqual(template.black.contents.paths.count, 1)
+ }
+
+ func testFullPipeline_clipFullyOutside_throwsNoValidContent() throws {
+ let url = try Bundle.test.url(forResource: "clip-outside.svg")
+ XCTAssertThrowsError(try SFSymbolRenderer.render(fileURL: url))
+ }
+
+ // MARK: - Layer-level coverage
+
+ /// Independent smoke test: the layer's clipUnits propagates through Builder.
+ func testBuilder_setsClipUnits_objectBoundingBox() throws {
+ let svg = try DOM.SVG.parse(#"""
+
+ """#)
+ let layer = LayerTree.Builder(svg: svg).makeLayer()
+ let clipped = firstClippedLayer(in: layer)
+ XCTAssertNotNil(clipped)
+ XCTAssertEqual(clipped?.clipUnits, .objectBoundingBox)
+ }
+
+ func testBuilder_setsClipUnits_userSpaceOnUseByDefault() throws {
+ let svg = try DOM.SVG.parse(#"""
+
+ """#)
+ let layer = LayerTree.Builder(svg: svg).makeLayer()
+ let clipped = firstClippedLayer(in: layer)
+ XCTAssertNotNil(clipped)
+ XCTAssertEqual(clipped?.clipUnits, .userSpaceOnUse)
+ }
+
+ // MARK: - Helpers
+
+ private func makeSymbolPaths(forResource named: String) throws -> [SFSymbolRenderer.SymbolPath] {
+ let url = try Bundle.test.url(forResource: named)
+ let svg = try DOM.SVG.parse(fileURL: url)
+ return SFSymbolRenderer.getPaths(for: svg) ?? []
+ }
+
+ private func assertBounds(_ actual: LayerTree.Rect,
+ equals expected: LayerTree.Rect,
+ accuracy: LayerTree.Float = 0.01,
+ file: StaticString = #file,
+ line: UInt = #line) {
+ XCTAssertEqual(actual.minX, expected.minX, accuracy: accuracy, "minX", file: file, line: line)
+ XCTAssertEqual(actual.minY, expected.minY, accuracy: accuracy, "minY", file: file, line: line)
+ XCTAssertEqual(actual.width, expected.width, accuracy: accuracy, "width", file: file, line: line)
+ XCTAssertEqual(actual.height, expected.height, accuracy: accuracy, "height", file: file, line: line)
+ }
+
+ private func firstClippedLayer(in layer: LayerTree.Layer) -> LayerTree.Layer? {
+ if !layer.clip.isEmpty { return layer }
+ for c in layer.contents {
+ if case .layer(let inner) = c, let hit = firstClippedLayer(in: inner) {
+ return hit
+ }
+ }
+ return nil
+ }
+}
+#endif
+
+private extension DOM.SVG {
+ static func parse(_ text: String, filename: String = #file) throws -> DOM.SVG {
+ let element = try XML.SAXParser.parse(data: text.data(using: .utf8)!)
+ let parser = XMLParser(options: [], filename: filename)
+ return try parser.parseSVG(element)
+ }
+}
+
+private extension SFSymbolRenderer {
+
+ static func render(fileURL: URL) throws -> String {
+ let renderer = SFSymbolRenderer(
+ size: .small,
+ options: [],
+ insets: .init(),
+ insetsUltralight: .init(),
+ insetsBlack: .init(),
+ precision: 3,
+ isLegacyInsets: false
+ )
+ return try renderer.render(regular: fileURL, ultralight: nil, black: nil)
+ }
+
+ static func render(svg: DOM.SVG) throws -> String {
+ let renderer = SFSymbolRenderer(
+ size: .small,
+ options: [],
+ insets: .init(),
+ insetsUltralight: .init(),
+ insetsBlack: .init(),
+ precision: 3,
+ isLegacyInsets: false
+ )
+ return try renderer.render(default: svg, ultralight: nil, black: nil)
+ }
+}
diff --git a/SwiftDraw/Tests/Test.bundle/clip-circle.svg b/SwiftDraw/Tests/Test.bundle/clip-circle.svg
new file mode 100644
index 0000000..6d871e9
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-circle.svg
@@ -0,0 +1,6 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-contains.svg b/SwiftDraw/Tests/Test.bundle/clip-contains.svg
new file mode 100644
index 0000000..ffa15e1
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-contains.svg
@@ -0,0 +1,6 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-ellipse.svg b/SwiftDraw/Tests/Test.bundle/clip-ellipse.svg
new file mode 100644
index 0000000..a46aaa8
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-ellipse.svg
@@ -0,0 +1,6 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-group.svg b/SwiftDraw/Tests/Test.bundle/clip-group.svg
new file mode 100644
index 0000000..92cc364
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-group.svg
@@ -0,0 +1,9 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-multi-shape.svg b/SwiftDraw/Tests/Test.bundle/clip-multi-shape.svg
new file mode 100644
index 0000000..5708aff
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-multi-shape.svg
@@ -0,0 +1,9 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-outside.svg b/SwiftDraw/Tests/Test.bundle/clip-outside.svg
new file mode 100644
index 0000000..e704ad5
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-outside.svg
@@ -0,0 +1,6 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-path-element.svg b/SwiftDraw/Tests/Test.bundle/clip-path-element.svg
new file mode 100644
index 0000000..a3edca7
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-path-element.svg
@@ -0,0 +1,6 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-polygon.svg b/SwiftDraw/Tests/Test.bundle/clip-polygon.svg
new file mode 100644
index 0000000..b28d9ff
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-polygon.svg
@@ -0,0 +1,6 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-rect.svg b/SwiftDraw/Tests/Test.bundle/clip-rect.svg
new file mode 100644
index 0000000..f456257
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-rect.svg
@@ -0,0 +1,6 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-rule-evenodd.svg b/SwiftDraw/Tests/Test.bundle/clip-rule-evenodd.svg
new file mode 100644
index 0000000..2c48fdf
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-rule-evenodd.svg
@@ -0,0 +1,8 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-transform-child.svg b/SwiftDraw/Tests/Test.bundle/clip-transform-child.svg
new file mode 100644
index 0000000..c12e408
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-transform-child.svg
@@ -0,0 +1,8 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-transformed-element.svg b/SwiftDraw/Tests/Test.bundle/clip-transformed-element.svg
new file mode 100644
index 0000000..7c6caaa
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-transformed-element.svg
@@ -0,0 +1,8 @@
+
diff --git a/SwiftDraw/Tests/Test.bundle/clip-units-bbox.svg b/SwiftDraw/Tests/Test.bundle/clip-units-bbox.svg
new file mode 100644
index 0000000..0b91379
--- /dev/null
+++ b/SwiftDraw/Tests/Test.bundle/clip-units-bbox.svg
@@ -0,0 +1,8 @@
+