diff --git a/DOM/Sources/Parser.XML.Attributes.swift b/DOM/Sources/Parser.XML.Attributes.swift index 6834d63..8505b88 100644 --- a/DOM/Sources/Parser.XML.Attributes.swift +++ b/DOM/Sources/Parser.XML.Attributes.swift @@ -112,7 +112,12 @@ extension XMLParser { var scanner = XMLParser.Scanner(text: value) return try scanner.scanCoordinate() } - + + func parseCoordinateOrPercentage(_ value: String) throws -> DOM.Coordinate { + var scanner = XMLParser.Scanner(text: value) + return try scanner.scanCoordinateOrPercentage() + } + func parseLength(_ value: String) throws -> DOM.Length { var scanner = XMLParser.Scanner(text: value) return try scanner.scanLength() diff --git a/DOM/Sources/Parser.XML.LinearGradient.swift b/DOM/Sources/Parser.XML.LinearGradient.swift index b0b8a5f..a716798 100644 --- a/DOM/Sources/Parser.XML.LinearGradient.swift +++ b/DOM/Sources/Parser.XML.LinearGradient.swift @@ -51,10 +51,10 @@ extension XMLParser { let nodeAtt: any AttributeParser = try parseAttributes(e) let node = DOM.LinearGradient(id: try nodeAtt.parseString("id")) - node.x1 = try nodeAtt.parseCoordinate("x1") - node.y1 = try nodeAtt.parseCoordinate("y1") - node.x2 = try nodeAtt.parseCoordinate("x2") - node.y2 = try nodeAtt.parseCoordinate("y2") + node.x1 = try nodeAtt.parseCoordinateOrPercentage("x1") + node.y1 = try nodeAtt.parseCoordinateOrPercentage("y1") + node.x2 = try nodeAtt.parseCoordinateOrPercentage("x2") + node.y2 = try nodeAtt.parseCoordinateOrPercentage("y2") for n in e.children where n.name == "stop" { let att: any AttributeParser = try parseAttributes(n) diff --git a/DOM/Sources/Parser.XML.RadialGradient.swift b/DOM/Sources/Parser.XML.RadialGradient.swift index 92a370b..46b1265 100644 --- a/DOM/Sources/Parser.XML.RadialGradient.swift +++ b/DOM/Sources/Parser.XML.RadialGradient.swift @@ -51,12 +51,12 @@ extension XMLParser { let nodeAtt: any AttributeParser = try parseAttributes(e) let node = DOM.RadialGradient(id: try nodeAtt.parseString("id")) - node.r = try? nodeAtt.parseCoordinate("r") - node.cx = try? nodeAtt.parseCoordinate("cx") - node.cy = try? nodeAtt.parseCoordinate("cy") - node.fr = try? nodeAtt.parseCoordinate("fr") - node.fx = try? nodeAtt.parseCoordinate("fx") - node.fy = try? nodeAtt.parseCoordinate("fy") + node.r = try? nodeAtt.parseCoordinateOrPercentage("r") + node.cx = try? nodeAtt.parseCoordinateOrPercentage("cx") + node.cy = try? nodeAtt.parseCoordinateOrPercentage("cy") + node.fr = try? nodeAtt.parseCoordinateOrPercentage("fr") + node.fx = try? nodeAtt.parseCoordinateOrPercentage("fx") + node.fy = try? nodeAtt.parseCoordinateOrPercentage("fy") for n in e.children where n.name == "stop" { let att: any AttributeParser = try parseAttributes(n) diff --git a/DOM/Sources/Parser.XML.Scanner.swift b/DOM/Sources/Parser.XML.Scanner.swift index f27eb4d..ad4e3ba 100644 --- a/DOM/Sources/Parser.XML.Scanner.swift +++ b/DOM/Sources/Parser.XML.Scanner.swift @@ -311,6 +311,17 @@ package extension XMLParser { let unit = scanUnit() ?? .pixel return DOM.Coordinate(double.apply(unit: unit)) } + + package mutating func scanCoordinateOrPercentage() throws -> DOM.Coordinate { + let double = try scanDouble() + scanner.currentIndex = currentIndex + if scanner.scanString("%") != nil { + currentIndex = scanner.currentIndex + return DOM.Coordinate(double / 100.0) + } + let unit = scanUnit() ?? .pixel + return DOM.Coordinate(double.apply(unit: unit)) + } package mutating func scanPercentageFloat() throws -> Float { scanner.currentIndex = currentIndex diff --git a/DOM/Sources/Parser.XML.swift b/DOM/Sources/Parser.XML.swift index 029271c..5ea7438 100644 --- a/DOM/Sources/Parser.XML.swift +++ b/DOM/Sources/Parser.XML.swift @@ -65,6 +65,7 @@ package protocol AttributeValueParser { func parseFloats(_ value: String) throws -> [DOM.Float] func parsePercentage(_ value: String) throws -> DOM.Float func parseCoordinate(_ value: String) throws -> DOM.Coordinate + func parseCoordinateOrPercentage(_ value: String) throws -> DOM.Coordinate func parseLength(_ value: String) throws -> DOM.Length func parseBool(_ value: String) throws -> DOM.Bool func parseFill(_ value: String) throws -> DOM.Fill @@ -108,6 +109,10 @@ package extension AttributeParser { return try parse(key) { return try parser.parseCoordinate($0) } } + func parseCoordinateOrPercentage(_ key: String) throws -> DOM.Coordinate { + return try parse(key) { return try parser.parseCoordinateOrPercentage($0) } + } + func parseLength(_ key: String) throws -> DOM.Length { return try parse(key) { return try parser.parseLength($0) } } @@ -187,6 +192,10 @@ package extension AttributeParser { return try parse(key) { return try parser.parseCoordinate($0) } } + func parseCoordinateOrPercentage(_ key: String) throws -> DOM.Coordinate? { + return try parse(key) { return try parser.parseCoordinateOrPercentage($0) } + } + func parseLength(_ key: String) throws -> DOM.Length? { return try parse(key) { return try parser.parseLength($0) } } diff --git a/DOM/Tests/Parser.XML.LinearGradientTests.swift b/DOM/Tests/Parser.XML.LinearGradientTests.swift index 4ec14fe..47cf95d 100644 --- a/DOM/Tests/Parser.XML.LinearGradientTests.swift +++ b/DOM/Tests/Parser.XML.LinearGradientTests.swift @@ -49,6 +49,38 @@ struct ParserXMLLinearGradientTests { #expect(try XMLParser().parseLinearGradients(parent).count == 3) } + @Test + func parseCoordinatesWithPercentage() throws { + let element = XML.Element(name: "linearGradient", attributes: [ + "id": "a", + "x1": "3.309%", + "y1": "28.41%", + "x2": "103.216%", + "y2": "64.511%" + ]) + let gradient = try XMLParser().parseLinearGradient(element) + #expect(abs((gradient.x1 ?? 0) - 0.03309) < 0.0001) + #expect(abs((gradient.y1 ?? 0) - 0.2841) < 0.0001) + #expect(abs((gradient.x2 ?? 0) - 1.03216) < 0.0001) + #expect(abs((gradient.y2 ?? 0) - 0.64511) < 0.0001) + } + + @Test + func parseCoordinatesWithoutPercentage() throws { + let element = XML.Element(name: "linearGradient", attributes: [ + "id": "b", + "x1": "0", + "y1": "0.5", + "x2": "1", + "y2": "0.5" + ]) + let gradient = try XMLParser().parseLinearGradient(element) + #expect(gradient.x1 == 0) + #expect(gradient.y1 == 0.5) + #expect(gradient.x2 == 1) + #expect(gradient.y2 == 0.5) + } + @Test func parseFile() throws { let dom = try DOM.SVG.parse(fileNamed: "linearGradient.svg", in: .test) diff --git a/DOM/Tests/Parser.XML.RadialGradientTests.swift b/DOM/Tests/Parser.XML.RadialGradientTests.swift index 4700588..6a47d09 100644 --- a/DOM/Tests/Parser.XML.RadialGradientTests.swift +++ b/DOM/Tests/Parser.XML.RadialGradientTests.swift @@ -50,6 +50,23 @@ struct ParserXMLRadialGradientTests { #expect(try XMLParser().parseRadialGradients(parent).count == 3) } + @Test + func parseCoordinatesWithPercentage() throws { + let element = XML.Element.makeMockGradient() + element.attributes["r"] = "10%" + element.attributes["cx"] = "50%" + element.attributes["cy"] = "25%" + element.attributes["fx"] = "50%" + element.attributes["fy"] = "25%" + + let gradient = try XMLParser().parseRadialGradient(element) + #expect(abs((gradient.r ?? 0) - 0.1) < 0.0001) + #expect(abs((gradient.cx ?? 0) - 0.5) < 0.0001) + #expect(abs((gradient.cy ?? 0) - 0.25) < 0.0001) + #expect(abs((gradient.fx ?? 0) - 0.5) < 0.0001) + #expect(abs((gradient.fy ?? 0) - 0.25) < 0.0001) + } + @Test func parseCoordinates() throws { let element = XML.Element.makeMockGradient()