Skip to content

Commit eb1c022

Browse files
committed
Fix SVG fill import and round-trip export
1 parent b716c38 commit eb1c022

File tree

4 files changed

+53
-6
lines changed

4 files changed

+53
-6
lines changed

Sources/Cadova/Abstract Layer/Import/2D/GeometrySVGConsumer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ internal final class ShapeExtractionRenderer: SVGRenderer {
107107
let fillRule = FillRule(from: rule)
108108
var polygons: [SimplePolygon] = []
109109

110-
for subpath in finishedPath.subpaths where subpath.isClosed {
110+
for subpath in finishedPath.subpaths {
111111
let points = subpath.bezierPath.points(segmentation: segmentation)
112112
if points.count >= 3 {
113113
polygons.append(SimplePolygon(points))

Sources/Cadova/Concrete Layer/Output Providers/SVGDataProvider.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ struct SVGDataProvider: OutputDataProvider {
1515

1616
let document = Document()
1717
let svg = document.makeDocumentElement(name: "svg", defaultNamespace: "http://www.w3.org/2000/svg")
18-
svg[attribute: "viewBox"] = String(format: "%g %g %g %g",
19-
bounds.minimum.x, bounds.minimum.y,
20-
bounds.size.x, bounds.size.y)
18+
svg[attribute: "width"] = String(format: "%g", bounds.size.x)
19+
svg[attribute: "height"] = String(format: "%g", bounds.size.y)
20+
svg[attribute: "viewBox"] = String(format: "%g %g %g %g", 0.0, 0.0, bounds.size.x, bounds.size.y)
2121

2222
let metadata = options[Metadata.self]
2323
if let title = metadata.title {
@@ -29,10 +29,11 @@ struct SVGDataProvider: OutputDataProvider {
2929

3030
let path = svg.addElement("path")
3131
path[attribute: "fill"] = "black"
32+
path[attribute: "fill-rule"] = "nonzero"
3233
path[attribute: "d"] = shapePoints.map {
3334
"M " + $0.vertices.map {
34-
String(format: "%g,%g", $0.x, $0.y)
35-
}.joined(separator: " ")
35+
String(format: "%g,%g", $0.x - bounds.minimum.x, $0.y - bounds.minimum.y)
36+
}.joined(separator: " ") + " Z"
3637
}.joined(separator: " ")
3738

3839
return try document.xmlData()

Tests/Tests/Import.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,32 @@ struct ImportTests {
100100
Issue.record("Unexpected error type: \(type(of: error)) - \(error)")
101101
}
102102
}
103+
104+
@Test func `SVG export and import preserves geometry`() async throws {
105+
let geometry: any Geometry2D = Rectangle(x: 20, y: 10)
106+
.subtracting {
107+
Rectangle(x: 5, y: 4)
108+
.translated(x: 10, y: 3)
109+
}
110+
111+
let originalMeasurements = try await geometry.measurements
112+
113+
let tempURL = FileManager.default.temporaryDirectory
114+
.appendingPathComponent("cadova-test-\(UUID().uuidString).svg")
115+
defer { try? FileManager.default.removeItem(at: tempURL) }
116+
117+
let context = EvaluationContext()
118+
let result = try await context.buildResult(for: geometry.withDefaultSegmentation(), in: .defaultEnvironment)
119+
let provider = SVGDataProvider(result: result, options: [])
120+
try await provider.writeOutput(to: tempURL, context: context)
121+
122+
let importedGeometry = Import(svg: tempURL, scale: .pixels)
123+
let importedMeasurements = try await importedGeometry.measurements
124+
let symmetricDifferenceArea = try await importedGeometry.symmetricDifferenceArea(with: geometry)
125+
126+
#expect(importedMeasurements.area originalMeasurements.area)
127+
#expect(importedMeasurements.contourCount == originalMeasurements.contourCount)
128+
#expect(importedMeasurements.boundingBox originalMeasurements.boundingBox)
129+
#expect(symmetricDifferenceArea.equals(0, within: 1e-6))
130+
}
103131
}

Tests/Tests/SVGImport.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ struct SVGImportTests {
2525
#expect(bounds!.maximum [11, 21])
2626
}
2727

28+
@Test func `SVG import fills open paths by implicitly closing them`() async throws {
29+
let svg = """
30+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
31+
<path d="M0 0 L10 0 L10 10" fill="black" />
32+
</svg>
33+
"""
34+
35+
let geometry = Import(svg: Data(svg.utf8), scale: .pixels)
36+
let measurements = try await geometry.measurements
37+
let bounds = try await geometry.bounds
38+
39+
#expect(measurements.area.equals(50, within: 0.05))
40+
#expect(measurements.contourCount == 1)
41+
#expect(bounds != nil)
42+
#expect(bounds!.minimum [0, 10])
43+
#expect(bounds!.maximum [10, 20])
44+
}
45+
2846
@Test func `SVG import converts text`() async throws {
2947
let url = Bundle.module.url(forResource: "svg_text", withExtension: "svg", subdirectory: "resources")!
3048
let geometry = Import(svg: url, scale: .pixels)

0 commit comments

Comments
 (0)