Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions DOM/Sources/DOM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ extension DOM {
case millimeter
case point
case pica
case em
case ex
}

package enum Error: Swift.Error {
Expand All @@ -143,6 +145,10 @@ package extension DOM.Unit {
return "pt"
case .pica:
return "pc"
case .em:
return "em"
case .ex:
return "ex"
}
}
}
Expand All @@ -162,6 +168,8 @@ package extension Double {
return self * 1.3333
case .pica:
return self * 16
case .em, .ex:
return self
}
}
}
17 changes: 14 additions & 3 deletions DOM/Sources/Parser.XML.SVG.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,26 @@ package extension XMLParser {
func resolveRootDimension(_ raw: String?, viewport: DOM.Coordinate?, attribute: String) throws -> DOM.Coordinate? {
guard let raw, !raw.isEmpty else { return nil }
var scanner = XMLParser.Scanner(text: raw)
let value = try scanner.scanCoordinate()
scanner.scanner.currentIndex = scanner.currentIndex
guard let number = scanner.scanner.scanDoubleCompatibly() else {
throw Error.invalidAttribute(name: attribute, value: raw)
}
scanner.currentIndex = scanner.scanner.currentIndex

if scanner.scanStringIfPossible("%") {
guard let viewport else { return nil }
return value / 100 * viewport
return DOM.Coordinate(number / 100) * viewport
}
if let unit = scanner.scanUnit() {
guard scanner.isEOF else {
throw Error.invalidAttribute(name: attribute, value: raw)
}
return DOM.Coordinate(number.apply(unit: unit))
}
guard scanner.isEOF else {
throw Error.invalidAttribute(name: attribute, value: raw)
}
return value
return DOM.Coordinate(number)
}

func makeUnresolvedReason(attribute: String, raw: String?, hasViewBox: Bool) -> String {
Expand Down
43 changes: 40 additions & 3 deletions DOM/Sources/Parser.XML.Scanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ package extension XMLParser {

struct Scanner {

private let scanner: Foundation.Scanner
package let scanner: Foundation.Scanner
package var currentIndex: String.Index

package init(text: String) {
Expand Down Expand Up @@ -256,7 +256,7 @@ package extension XMLParser {

package mutating func scanDouble() throws -> Double {
scanner.currentIndex = currentIndex
guard let val = scanner.scanDouble() else {
guard let val = scanner.scanDoubleCompatibly() else {
throw Error.invalid
}
currentIndex = scanner.currentIndex
Expand Down Expand Up @@ -285,6 +285,10 @@ package extension XMLParser {
return .point
} else if scanUnit(.pica) {
return .pica
} else if scanUnit(.em) {
return .em
} else if scanUnit(.ex) {
return .ex
} else {
return nil
}
Expand Down Expand Up @@ -404,9 +408,42 @@ package extension Scanner {
_ = scanCharacter()
return scalar
}

func scanDoubleCompatibly() -> Double? {
#if canImport(Darwin)
return scanDouble()
#else
let start = currentIndex
if let value = scanDouble() {
return value
}

currentIndex = start
let numeric = Foundation.CharacterSet(charactersIn: "+-0123456789.Ee")
guard let scannedNumber = scanCharacters(from: numeric), !scannedNumber.isEmpty else {
currentIndex = start
return nil
}

var numericEnd = currentIndex
var numericPrefix = scannedNumber
while Double(numericPrefix) == nil, !numericPrefix.isEmpty {
numericEnd = string.index(before: numericEnd)
numericPrefix.removeLast()
}

guard let value = Double(numericPrefix), !numericPrefix.isEmpty else {
currentIndex = start
return nil
}

currentIndex = numericEnd
return value
#endif
}

func scanCoordinate() throws -> DOM.Coordinate {
guard let val = scanDouble() else { throw XMLParser.Error.invalid }
guard let val = scanDoubleCompatibly() else { throw XMLParser.Error.invalid }
return DOM.Coordinate(val)
}

Expand Down
23 changes: 23 additions & 0 deletions DOM/Tests/Parser.SVGTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,29 @@ struct ParserSVGTests {
#expect(parsed.height == 192)
}

@Test
func root_em_ex_dimensions_fall_back_to_raw_values() throws {
let em = XML.Element(name: "svg", attributes: ["width": "2em", "height": "1em"])
let ex = XML.Element(name: "svg", attributes: ["width": "2ex", "height": "1ex"])
let parser = DOMXMLParser()

let parsedEm = try parser.parseSVG(em)
#expect(parsedEm.width == 2)
#expect(parsedEm.height == 1)

let parsedEx = try parser.parseSVG(ex)
#expect(parsedEx.width == 2)
#expect(parsedEx.height == 1)
}

@Test
func unknown_root_units_still_throw() {
let node = XML.Element(name: "svg", attributes: ["width": "2abc", "height": "1abc"])
#expect(throws: XMLParser.Error.self) {
try XMLParser().parseSVG(node)
}
}

@Test
func parseSVGInvalidNode() {
let node = XML.Element(name: "svg2", attributes: ["width": "100", "height": "200"])
Expand Down
11 changes: 11 additions & 0 deletions DOM/Tests/Parser.XML.PathTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ struct ParserXMLPathTests {
#expect(throws: (any Error).self) { _ = try scanner.scanCoordinate() }
}

@Test
func scanCoordinate_FontUnitsFallBackToRawValues() throws {
let emScanner = XMLParser.PathScanner(string: "2em")
let exScanner = XMLParser.PathScanner(string: "3ex")

#expect(try emScanner.scanCoordinate() == 2.0)
#expect(emScanner.isAtEnd == false)
#expect(try exScanner.scanCoordinate() == 3.0)
#expect(exScanner.isAtEnd == false)
}

@Test
func equality() {
#expect(Segment.move(x: 10, y: 20, space: .relative) == move(10, 20, .relative))
Expand Down
47 changes: 47 additions & 0 deletions DOM/Tests/XML.Parser.ScannerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,38 @@ struct ScannerTests {
#expect(scanDouble("124") == 124)
#expect(scanDouble(" 045") == 45)
#expect(scanDouble("-29") == -29)
#expect(scanDouble("2em") == 2)
#expect(scanDouble(".5em") == 0.5)
#expect(scanDouble("ab24") == nil)
}

@Test
func foundationScanDouble_WithTrailingEmEx_IsPlatformDependent() {
let emScanner = Foundation.Scanner(string: "2em")
emScanner.charactersToBeSkipped = Foundation.CharacterSet.whitespacesAndNewlines

let exScanner = Foundation.Scanner(string: "2ex")
exScanner.charactersToBeSkipped = Foundation.CharacterSet.whitespacesAndNewlines

#if canImport(Darwin)
#expect(emScanner.scanDouble() == 2)
#expect(exScanner.scanDouble() == 2)
#else
#expect(emScanner.scanDouble() == nil)
#expect(exScanner.scanDouble() == nil)
#endif
}

@Test
func scanDoubleCompatibly_FallsBackForTrailingFontUnits() {
#expect(scanDoubleCompatibly("2em") == 2)
#expect(scanDoubleCompatibly("2ex") == 2)
#expect(scanDoubleCompatibly("2e") == 2)
#expect(scanDoubleCompatibly("2e+") == 2)
#expect(scanDoubleCompatibly(".5em") == 0.5)
#expect(scanDoubleCompatibly("ab24") == nil)
}

@Test
func scan_Length() {
#expect(scanLength("0") == 0)
Expand Down Expand Up @@ -217,6 +246,13 @@ struct ScannerTests {
#expect(try scanner.scanCoordinate() == 5 * 16)
}

@Test
func scanCoordinate_FontUnitsFallBackToRawValues() {
#expect(parseCoordinate("2em") == 2)
#expect(parseCoordinate("2ex") == 2)
#expect(parseCoordinate(".5em") == 0.5)
}

@Test
func scanUpToPreservesStrings() throws {
var scanner = XMLParser.Scanner(text: #"foo: bar; baz: "bing;bob" zab;"#)
Expand Down Expand Up @@ -289,6 +325,17 @@ private func scanDouble(_ text: String) -> Double? {
return try? scanner.scanDouble()
}

private func scanDoubleCompatibly(_ text: String) -> Double? {
let scanner = Foundation.Scanner(string: text)
scanner.charactersToBeSkipped = Foundation.CharacterSet.whitespacesAndNewlines
return scanner.scanDoubleCompatibly()
}

private func parseCoordinate(_ text: String) -> DOM.Coordinate? {
var scanner = XMLParser.Scanner(text: text)
return try? scanner.scanCoordinate()
}

private func scanLength(_ text: String) -> DOM.Length? {
var scanner = XMLParser.Scanner(text: text)
return try? scanner.scanLength()
Expand Down
1 change: 1 addition & 0 deletions Examples/Sources/GalleryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ struct GalleryView: View {
"alert.svg",
"effigy.svg",
"d10.svg",
"mathjax-latex.svg",
"stylesheet-multiple.svg"
]

Expand Down
51 changes: 51 additions & 0 deletions Samples.bundle/mathjax-latex.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions SwiftDraw/Tests/SVGTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ struct SVGTests {
#expect(SVG(named: "missing.svg", in: .test) == nil)
}

@Test
func root_em_ex_dimensions_parse_as_raw_values() {
let ex = #"<svg xmlns="http://www.w3.org/2000/svg" width="2ex" height="1ex"><rect width="5" height="5"/></svg>"#
let em = #"<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="1em"><rect width="5" height="5"/></svg>"#
let px = #"<svg xmlns="http://www.w3.org/2000/svg" width="2px" height="1px"><rect width="5" height="5"/></svg>"#

let exSVG = SVG(data: Data(ex.utf8))
let emSVG = SVG(data: Data(em.utf8))
let pxSVG = SVG(data: Data(px.utf8))

#expect(exSVG?.size == CGSize(width: 2, height: 1))
#expect(emSVG?.size == CGSize(width: 2, height: 1))
#expect(pxSVG?.size == CGSize(width: 2, height: 1))
}

@Test
func imageRasterizes() {
let image = SVG.makeLines()
Expand Down
4 changes: 2 additions & 2 deletions docker-test.sh → container-run-tests.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
docker run -it \
container run -it \
--rm \
--mount src="$(pwd)",target=/SwiftDraw,type=bind \
swift \
swift:6.3 \
/usr/bin/swift test --package-path /SwiftDraw
Loading