From 2585d66035bb0e83b173a7806d979376b05bc5eb Mon Sep 17 00:00:00 2001 From: Tomas Linhart Date: Fri, 2 Oct 2020 16:21:20 +1000 Subject: [PATCH] Removing force unwrapping in SVGParser --- Source/svg/SVGParser.swift | 145 +++++++++++++++++++++++++------------ 1 file changed, 97 insertions(+), 48 deletions(-) diff --git a/Source/svg/SVGParser.swift b/Source/svg/SVGParser.swift index dfdf7de9..f9c35a29 100644 --- a/Source/svg/SVGParser.swift +++ b/Source/svg/SVGParser.swift @@ -148,9 +148,9 @@ open class SVGParser { } } } - let layout = svgElement != nil ? try parseViewBox(svgElement!) : nil + let layout = try svgElement.flatMap(parseViewBox) try parseSvg(parsedXml.children) - let root = layout != nil ? SVGCanvas(layout: layout!, contents: nodes) : Group(contents: nodes) + let root = layout.flatMap { SVGCanvas(layout: $0, contents: nodes) } ?? Group(contents: nodes) if let opacity = svgElement?.attribute(by: "opacity") { root.opacity = getOpacity(opacity.text) } @@ -466,25 +466,30 @@ open class SVGParser { contentUserSpace = false } - var contentNode: Node? - if pattern.children.isEmpty { - if let parentPattern = parentPattern { - contentNode = parentPattern.content - } - } else if pattern.children.count == 1, - let shape = try parseNode(pattern.children.first!) as? Shape { - contentNode = shape - } else { - var shapes = [Shape]() - try pattern.children.forEach { indexer in - if let shape = try parseNode(indexer) as? Shape { - shapes.append(shape) + func parseContentNode() throws -> Node? { + if pattern.children.isEmpty { + return parentPattern?.content + } else if pattern.children.count == 1, + let child = pattern.children.first, + let shape = try parseNode(child) as? Shape { + return shape + } else { + var shapes = [Shape]() + try pattern.children.forEach { indexer in + if let shape = try parseNode(indexer) as? Shape { + shapes.append(shape) + } } + return Group(contents: shapes) } - contentNode = Group(contents: shapes) } - return UserSpacePattern(content: contentNode!, + guard let contentNode = try parseContentNode() else { + print("Pattern does not contain any content.") + return .none + } + + return UserSpacePattern(content: contentNode, bounds: bounds, userSpace: userSpace, contentUserSpace: contentUserSpace) @@ -760,14 +765,14 @@ open class SVGParser { } fileprivate func getPatternFill(pattern: UserSpacePattern, locus: Locus?) -> Pattern { - if pattern.userSpace == false && pattern.contentUserSpace == true { + if let locus = locus, pattern.userSpace == false && pattern.contentUserSpace == true { let tranform = BoundsUtils.transformForLocusInRespectiveCoords(respectiveLocus: pattern.bounds, - absoluteLocus: locus!) + absoluteLocus: locus) return Pattern(content: pattern.content, bounds: pattern.bounds.applying(tranform), userSpace: true) } - if pattern.userSpace == true && pattern.contentUserSpace == false { + if let locus = locus, pattern.userSpace == true && pattern.contentUserSpace == false { if let patternNode = BoundsUtils.createNodeFromRespectiveCoords(respectiveNode: pattern.content, - absoluteLocus: locus!) { + absoluteLocus: locus) { return Pattern(content: patternNode, bounds: pattern.bounds, userSpace: pattern.userSpace) } } @@ -889,7 +894,7 @@ open class SVGParser { fileprivate func getTag(_ element: SWXMLHash.XMLElement) -> [String] { let id = element.allAttributes["id"]?.text - return id != nil ? [id!] : [] + return id.map { [$0] } ?? [] } fileprivate func getOpacity(_ styleParts: [String: String]) -> Double { @@ -1200,9 +1205,22 @@ open class SVGParser { let text = shouldAddWhitespace ? " \(string)" : string let attributes = getStyleAttributes([:], element: element) + var fillColor: Fill? { + guard let fillValue = attributes[SVGKeys.fill] else { + return fill + } + + if let fillColor = getFillColor(attributes) { + return fillColor + } + + print("Found invalid fill \(fillValue) in style attributes of \(element.name).") + return fill + } + return Text(text: text, font: getFont(attributes, fontName: fontName, fontWeight: fontWeight, fontSize: fontSize), - fill: (attributes[SVGKeys.fill] != nil) ? getFillColor(attributes)! : fill, + fill: fillColor, stroke: stroke ?? getStroke(attributes), align: anchorToAlign(textAnchor ?? getTextAnchor(attributes)), baseline: .alphabetic, @@ -1298,8 +1316,11 @@ open class SVGParser { return .none } - if clip.children.count == 1 { - let shape = try parseNode(clip.children.first!) as! Shape + if clip.children.count == 1, let child = clip.children.first { + guard let shape = try parseNode(child) as? Shape else { + return .none + } + if shape.place != Transform.identity { let locus = TransformedLocus(locus: shape.form, transform: shape.place) return UserSpaceLocus(locus: locus, userSpace: userSpace) @@ -1324,8 +1345,12 @@ open class SVGParser { } fileprivate func parseMask(_ mask: XMLIndexer) throws -> UserSpaceNode? { + guard let element = mask.element else { + return .none + } + var userSpace = true - let styles = getStyleAttributes([:], element: mask.element!) + let styles = getStyleAttributes([:], element: element) if let units = mask.element?.allAttributes["maskContentUnits"]?.text, units == "objectBoundingBox" { userSpace = false } @@ -1334,14 +1359,20 @@ open class SVGParser { return .none } - if mask.children.count == 1 { - let node = try parseNode(mask.children.first!, groupStyle: styles)! + if mask.children.count == 1, let child = mask.children.first { + guard let node = try parseNode(child, groupStyle: styles) else { + return .none + } + return UserSpaceNode(node: node, userSpace: userSpace) } - var nodes = [Node]() - try mask.children.forEach { indexer in - let position = getPosition(indexer.element!) + let nodes = try mask.children.reduce(into: [Node]()) { nodes, indexer in + guard let element = indexer.element else { + return + } + + let position = getPosition(element) if let useNode = try parseUse(indexer, groupStyle: styles, place: position) { nodes.append(useNode) } else if let contentNode = try parseNode(indexer, groupStyle: styles) { @@ -1367,7 +1398,6 @@ open class SVGParser { } effects.removeValue(forKey: filterIn) - let filterOut = element.allAttributes["result"]?.text var resultingEffect: Effect? = .none switch element.name { @@ -1382,18 +1412,34 @@ open class SVGParser { } case "feColorMatrix": if let type = element.allAttributes["type"]?.text { - var matrix: ColorMatrix? - if type == "saturate" { - matrix = ColorMatrix(saturate: getDoubleValue(element, attribute: "values")!) - } else if type == "hueRotate" { - let degrees = getDoubleValue(element, attribute: "values")! - matrix = ColorMatrix(hueRotate: degrees / 180 * Double.pi) - } else if type == "luminanceToAlpha" { - matrix = .luminanceToAlpha - } else { // "matrix" - matrix = ColorMatrix(values: getMatrix(element, attribute: "values")) + func parseMatrix() -> ColorMatrix? { + if type == "saturate" { + guard let value = getDoubleValue(element, attribute: "values") else { + print("Invalid number value in \(element.name)") + return nil + } + + return ColorMatrix(saturate: value) + } else if type == "hueRotate" { + guard let degrees = getDoubleValue(element, attribute: "values") else { + print("Invalid number value in \(element.name)") + return nil + } + + return ColorMatrix(hueRotate: degrees / 180 * Double.pi) + } else if type == "luminanceToAlpha" { + return .luminanceToAlpha + } else { // "matrix" + return ColorMatrix(values: getMatrix(element, attribute: "values")) + } } - resultingEffect = ColorMatrixEffect(matrix: matrix!, input: currentEffect) + + guard let matrix = parseMatrix() else { + print("Invalid matrix in \(element.name)") + continue + } + + resultingEffect = ColorMatrixEffect(matrix: matrix, input: currentEffect) } case "feBlend": if let filterIn2 = element.allAttributes["in2"]?.text { @@ -1408,10 +1454,11 @@ open class SVGParser { continue } - if filterOut == nil { + guard let filterOut = element.allAttributes["result"]?.text else { return resultingEffect } - effects[filterOut!] = resultingEffect + + effects[filterOut] = resultingEffect } if effects.count == 1 { @@ -1857,6 +1904,8 @@ private class PathDataReader { private var previous: UnicodeScalar? private var iterator: String.UnicodeScalarView.Iterator + private static let spaces: Set = Set("\n\r\t ,".unicodeScalars) + init(input: String) { self.input = input self.iterator = input.unicodeScalars.makeIterator() @@ -1944,9 +1993,9 @@ private class PathDataReader { } private func skipSpaces() { - var ch = current - while ch != nil && "\n\r\t ,".contains(String(ch!)) { - ch = readNext() + var currentCharacter = current + while let character = currentCharacter, Self.spaces.contains(character) { + currentCharacter = readNext() } }