2016-04-21 12:06:16 +03:00
|
|
|
import Foundation
|
|
|
|
import SWXMLHash
|
2016-09-01 11:59:15 +03:00
|
|
|
import CoreGraphics
|
2016-04-21 12:06:16 +03:00
|
|
|
|
2016-08-16 16:45:09 +03:00
|
|
|
///
|
|
|
|
/// This class used to parse SVG file and build corresponding Macaw scene
|
|
|
|
///
|
2016-09-27 16:45:31 +03:00
|
|
|
open class SVGParser {
|
2016-05-18 19:06:10 +03:00
|
|
|
|
2016-11-24 13:45:16 +03:00
|
|
|
/// Parse an SVG file identified by the specified bundle, name and file extension.
|
|
|
|
/// - returns: Root node of the corresponding Macaw scene.
|
|
|
|
open class func parse(bundle: Bundle, path: String, ofType: String = "svg") -> Node {
|
|
|
|
let path = bundle.path(forResource: path, ofType: ofType)
|
|
|
|
let text = try! String(contentsOfFile: path!, encoding: String.Encoding.utf8)
|
|
|
|
return SVGParser.parse(text: text)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse an SVG file identified by the specified name and file extension.
|
|
|
|
/// - returns: Root node of the corresponding Macaw scene.
|
|
|
|
open class func parse(path: String, ofType: String = "svg") -> Node {
|
|
|
|
return SVGParser.parse(bundle: Bundle.main, path: path, ofType: ofType)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse the specified content of an SVG file.
|
|
|
|
/// - returns: Root node of the corresponding Macaw scene.
|
|
|
|
open class func parse(text: String) -> Node {
|
|
|
|
return SVGParser(text).parse()
|
|
|
|
}
|
2016-09-11 21:16:20 +03:00
|
|
|
|
|
|
|
let moveToAbsolute = Character("M")
|
|
|
|
let moveToRelative = Character("m")
|
|
|
|
let lineToAbsolute = Character("L")
|
|
|
|
let lineToRelative = Character("l")
|
|
|
|
let lineHorizontalAbsolute = Character("H")
|
|
|
|
let lineHorizontalRelative = Character("h")
|
|
|
|
let lineVerticalAbsolute = Character("V")
|
|
|
|
let lineVerticalRelative = Character("v")
|
|
|
|
let curveToAbsolute = Character("C")
|
|
|
|
let curveToRelative = Character("c")
|
|
|
|
let smoothCurveToAbsolute = Character("S")
|
|
|
|
let smoothCurveToRelative = Character("s")
|
|
|
|
let closePathAbsolute = Character("Z")
|
|
|
|
let closePathRelative = Character("z")
|
|
|
|
let availableStyleAttributes = ["stroke", "stroke-width", "stroke-opacity", "stroke-dasharray", "stroke-linecap", "stroke-linejoin",
|
|
|
|
"fill", "fill-opacity",
|
|
|
|
"stop-color", "stop-opacity",
|
|
|
|
"font-family", "font-size",
|
|
|
|
"opacity"]
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate let xmlString: String
|
|
|
|
fileprivate let initialPosition: Transform
|
|
|
|
|
|
|
|
fileprivate var nodes = [Node]()
|
|
|
|
fileprivate var defNodes = [String: Node]()
|
|
|
|
fileprivate var defFills = [String: Fill]()
|
|
|
|
|
|
|
|
fileprivate enum PathCommandType {
|
|
|
|
case moveTo
|
|
|
|
case lineTo
|
|
|
|
case lineV
|
|
|
|
case lineH
|
|
|
|
case curveTo
|
|
|
|
case smoothCurveTo
|
|
|
|
case closePath
|
|
|
|
case none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate typealias PathCommand = (type: PathCommandType, expression: String, absolute: Bool)
|
2016-09-11 21:16:20 +03:00
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate init(_ string: String, pos: Transform = Transform()) {
|
2016-09-11 21:16:20 +03:00
|
|
|
self.xmlString = string
|
|
|
|
self.initialPosition = pos
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parse() -> Group {
|
2016-09-11 21:16:20 +03:00
|
|
|
let parsedXml = SWXMLHash.parse(xmlString)
|
|
|
|
iterateThroughXmlTree(parsedXml.children)
|
|
|
|
|
|
|
|
let group = Group(contents: self.nodes, place: initialPosition)
|
|
|
|
return group
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func iterateThroughXmlTree(_ children: [XMLIndexer]) {
|
2016-09-11 21:16:20 +03:00
|
|
|
children.forEach { child in
|
|
|
|
if let element = child.element {
|
|
|
|
if element.name == "svg" {
|
|
|
|
iterateThroughXmlTree(child.children)
|
|
|
|
} else if let node = parseNode(child) {
|
|
|
|
self.nodes.append(node)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseNode(_ node: XMLIndexer, groupStyle: [String: String] = [:]) -> Node? {
|
2016-09-11 21:16:20 +03:00
|
|
|
if let element = node.element {
|
|
|
|
if element.name == "g" {
|
|
|
|
return parseGroup(node, groupStyle: groupStyle)
|
|
|
|
} else if element.name == "defs" {
|
|
|
|
parseDefinitions(node)
|
|
|
|
} else {
|
|
|
|
return parseElement(node, groupStyle: groupStyle)
|
|
|
|
}
|
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseDefinitions(_ defs: XMLIndexer) {
|
2016-09-11 21:16:20 +03:00
|
|
|
for child in defs.children {
|
|
|
|
guard let id = child.element?.attributes["id"] else {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if let node = parseNode(child) {
|
|
|
|
self.defNodes[id] = node
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if let fill = parseFill(child) {
|
|
|
|
self.defFills[id] = fill
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseElement(_ node: XMLIndexer, groupStyle: [String: String] = [:]) -> Node? {
|
2016-09-11 21:16:20 +03:00
|
|
|
if let element = node.element {
|
|
|
|
let styleAttributes = getStyleAttributes(groupStyle, element: element)
|
|
|
|
let position = getPosition(element)
|
|
|
|
switch element.name {
|
|
|
|
case "path":
|
|
|
|
if let path = parsePath(node) {
|
|
|
|
return Shape(form: path, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes))
|
|
|
|
}
|
|
|
|
case "line":
|
|
|
|
if let line = parseLine(node) {
|
|
|
|
return Shape(form: line, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes))
|
|
|
|
}
|
|
|
|
case "rect":
|
|
|
|
if let rect = parseRect(node) {
|
|
|
|
return Shape(form: rect, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes))
|
|
|
|
}
|
|
|
|
case "circle":
|
|
|
|
if let circle = parseCircle(node) {
|
|
|
|
return Shape(form: circle, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes))
|
|
|
|
}
|
|
|
|
case "ellipse":
|
|
|
|
if let ellipse = parseEllipse(node) {
|
|
|
|
return Shape(form: ellipse, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes))
|
|
|
|
}
|
|
|
|
case "polygon":
|
|
|
|
if let polygon = parsePolygon(node) {
|
|
|
|
return Shape(form: polygon, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes))
|
|
|
|
}
|
|
|
|
case "polyline":
|
|
|
|
if let polyline = parsePolyline(node) {
|
|
|
|
return Shape(form: polyline, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes))
|
|
|
|
}
|
|
|
|
case "image":
|
|
|
|
return parseImage(node, opacity: getOpacity(styleAttributes), pos: position)
|
|
|
|
case "text":
|
|
|
|
return parseText(node, fill: getFillColor(styleAttributes), opacity: getOpacity(styleAttributes), fontName: getFontName(styleAttributes), fontSize: getFontSize(styleAttributes), pos: position)
|
|
|
|
case "use":
|
|
|
|
return parseUse(node, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), pos: position, opacity: getOpacity(styleAttributes))
|
|
|
|
default:
|
|
|
|
print("SVG parsing error. Shape \(element.name) not supported")
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseFill(_ fill: XMLIndexer) -> Fill? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let element = fill.element else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
switch element.name {
|
|
|
|
case "linearGradient":
|
|
|
|
return parseLinearGradient(fill)
|
|
|
|
case "radialGradient":
|
|
|
|
return parseRadialGradient(fill)
|
|
|
|
default:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseGroup(_ group: XMLIndexer, groupStyle: [String: String] = [:]) -> Group? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let element = group.element else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
var groupNodes: [Node] = []
|
|
|
|
let style = getStyleAttributes(groupStyle, element: element)
|
|
|
|
let position = getPosition(element)
|
|
|
|
group.children.forEach { child in
|
|
|
|
if let node = parseNode(child, groupStyle: style) {
|
|
|
|
groupNodes.append(node)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Group(contents: groupNodes, place: position)
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getPosition(_ element: XMLElement) -> Transform {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let transformAttribute = element.attributes["transform"] else {
|
|
|
|
return Transform()
|
|
|
|
}
|
|
|
|
return parseTransformationAttribute(transformAttribute)
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseTransformationAttribute(_ attributes: String, transform: Transform = Transform()) -> Transform {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let matcher = SVGParserRegexHelper.getTransformAttributeMatcher() else {
|
|
|
|
return transform
|
|
|
|
}
|
|
|
|
var finalTransform = transform
|
|
|
|
let fullRange = NSRange(location: 0, length: attributes.characters.count)
|
2016-09-27 16:45:31 +03:00
|
|
|
if let matchedAttribute = matcher.firstMatch(in: attributes, options: .reportCompletion, range: fullRange) {
|
|
|
|
let attributeName = (attributes as NSString).substring(with: matchedAttribute.rangeAt(1))
|
|
|
|
let values = parseTransformValues((attributes as NSString).substring(with: matchedAttribute.rangeAt(2)))
|
2016-09-11 21:16:20 +03:00
|
|
|
if values.isEmpty {
|
|
|
|
return transform
|
|
|
|
}
|
|
|
|
switch attributeName {
|
|
|
|
case "translate":
|
|
|
|
if let x = Double(values[0]) {
|
|
|
|
var y: Double = 0
|
|
|
|
if values.indices.contains(1) {
|
|
|
|
y = Double(values[1]) ?? 0
|
|
|
|
}
|
|
|
|
finalTransform = transform.move(dx: x, dy: y)
|
|
|
|
}
|
|
|
|
case "scale":
|
|
|
|
if let x = Double(values[0]) {
|
|
|
|
var y: Double = x
|
|
|
|
if values.indices.contains(1) {
|
|
|
|
y = Double(values[1]) ?? x
|
|
|
|
}
|
|
|
|
finalTransform = transform.scale(sx: x, sy: y)
|
|
|
|
}
|
|
|
|
case "rotate":
|
|
|
|
if let angle = Double(values[0]) {
|
|
|
|
if values.count == 1 {
|
2016-11-30 13:59:58 +03:00
|
|
|
finalTransform = transform.rotate(angle: degreesToRadians(angle))
|
2016-09-11 21:16:20 +03:00
|
|
|
} else if values.count == 3 {
|
2016-09-27 16:45:31 +03:00
|
|
|
if let x = Double(values[1]), let y = Double(values[2]) {
|
2016-11-30 13:59:58 +03:00
|
|
|
finalTransform = transform.move(dx: x, dy: y).rotate(angle: degreesToRadians(angle)).move(dx: -x, dy: -y)
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case "skewX":
|
|
|
|
if let x = Double(values[0]) {
|
|
|
|
finalTransform = transform.shear(shx: x, shy: 0)
|
|
|
|
}
|
|
|
|
case "skewY":
|
|
|
|
if let y = Double(values[0]) {
|
|
|
|
finalTransform = transform.shear(shx: 0, shy: y)
|
|
|
|
}
|
|
|
|
case "matrix":
|
|
|
|
if values.count != 6 {
|
|
|
|
return transform
|
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
if let m11 = Double(values[0]), let m12 = Double(values[1]),
|
|
|
|
let m21 = Double(values[2]), let m22 = Double(values[3]),
|
|
|
|
let dx = Double(values[4]), let dy = Double(values[5]) {
|
2016-09-11 21:16:20 +03:00
|
|
|
|
|
|
|
let transformMatrix = Transform(m11: m11, m12: m12, m21: m21, m22: m22, dx: dx, dy: dy)
|
|
|
|
finalTransform = GeomUtils.concat(t1: transform, t2: transformMatrix)
|
|
|
|
}
|
|
|
|
default: break
|
|
|
|
}
|
|
|
|
let rangeToRemove = NSRange(location: 0, length: matchedAttribute.range.location + matchedAttribute.range.length)
|
2016-09-27 16:45:31 +03:00
|
|
|
let newAttributeString = (attributes as NSString).replacingCharacters(in: rangeToRemove, with: "")
|
2016-09-11 21:16:20 +03:00
|
|
|
return parseTransformationAttribute(newAttributeString, transform: finalTransform)
|
|
|
|
} else {
|
|
|
|
return transform
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseTransformValues(_ values: String, collectedValues: [String] = []) -> [String] {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let matcher = SVGParserRegexHelper.getTransformMatcher() else {
|
|
|
|
return collectedValues
|
|
|
|
}
|
|
|
|
var updatedValues: [String] = collectedValues
|
|
|
|
let fullRange = NSRange(location: 0, length: values.characters.count)
|
2016-09-27 16:45:31 +03:00
|
|
|
if let matchedValue = matcher.firstMatch(in: values, options: .reportCompletion, range: fullRange) {
|
|
|
|
let value = (values as NSString).substring(with: matchedValue.range)
|
2016-09-11 21:16:20 +03:00
|
|
|
updatedValues.append(value)
|
|
|
|
let rangeToRemove = NSRange(location: 0, length: matchedValue.range.location + matchedValue.range.length)
|
2016-09-27 16:45:31 +03:00
|
|
|
let newValues = (values as NSString).replacingCharacters(in: rangeToRemove, with: "")
|
2016-09-11 21:16:20 +03:00
|
|
|
return parseTransformValues(newValues, collectedValues: updatedValues)
|
|
|
|
}
|
|
|
|
return updatedValues
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getStyleAttributes(_ groupAttributes: [String: String], element: XMLElement) -> [String: String] {
|
2016-09-11 21:16:20 +03:00
|
|
|
var styleAttributes: [String: String] = groupAttributes
|
|
|
|
if let style = element.attributes["style"] {
|
2016-09-29 14:17:59 +03:00
|
|
|
|
|
|
|
let styleParts = style.replacingOccurrences(of: " ", with: "").components(separatedBy: ";")
|
2016-09-11 21:16:20 +03:00
|
|
|
styleParts.forEach { styleAttribute in
|
2016-09-29 14:17:59 +03:00
|
|
|
let currentStyle = styleAttribute.components(separatedBy: ":")
|
2016-09-11 21:16:20 +03:00
|
|
|
if currentStyle.count == 2 {
|
|
|
|
styleAttributes.updateValue(currentStyle[1], forKey: currentStyle[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.availableStyleAttributes.forEach { availableAttribute in
|
|
|
|
if let styleAttribute = element.attributes[availableAttribute] {
|
|
|
|
styleAttributes.updateValue(styleAttribute, forKey: availableAttribute)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return styleAttributes
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func createColor(_ hexString: String, opacity: Double = 1) -> Color {
|
2016-09-11 21:16:20 +03:00
|
|
|
var cleanedHexString = hexString
|
|
|
|
if hexString.hasPrefix("#") {
|
2016-09-27 16:45:31 +03:00
|
|
|
cleanedHexString = hexString.replacingOccurrences(of: "#", with: "")
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
var rgbValue: UInt32 = 0
|
2016-09-27 16:45:31 +03:00
|
|
|
Scanner(string: cleanedHexString).scanHexInt32(&rgbValue)
|
2016-09-11 21:16:20 +03:00
|
|
|
|
|
|
|
let red = CGFloat((rgbValue >> 16) & 0xff)
|
|
|
|
let green = CGFloat((rgbValue >> 08) & 0xff)
|
|
|
|
let blue = CGFloat((rgbValue >> 00) & 0xff)
|
|
|
|
|
|
|
|
return Color.rgba(r: Int(red), g: Int(green), b: Int(blue), a: opacity)
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getFillColor(_ styleParts: [String: String]) -> Fill? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let fillColor = styleParts["fill"] else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
if fillColor == "none" {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
var opacity: Double = 1
|
|
|
|
if let fillOpacity = styleParts["fill-opacity"] {
|
2016-09-27 16:45:31 +03:00
|
|
|
opacity = Double(fillOpacity.replacingOccurrences(of: " ", with: "")) ?? 1
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
if fillColor.hasPrefix("url") {
|
2016-09-27 16:45:31 +03:00
|
|
|
let index = fillColor.characters.index(fillColor.startIndex, offsetBy: 4)
|
|
|
|
let id = fillColor.substring(from: index)
|
|
|
|
.replacingOccurrences(of: "(", with: "")
|
|
|
|
.replacingOccurrences(of: ")", with: "")
|
|
|
|
.replacingOccurrences(of: "#", with: "")
|
2016-09-11 21:16:20 +03:00
|
|
|
return defFills[id]
|
|
|
|
} else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return createColor(fillColor.replacingOccurrences(of: " ", with: ""), opacity: opacity)
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getStroke(_ styleParts: [String: String]) -> Stroke? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let strokeColor = styleParts["stroke"] else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
if strokeColor == "none" {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
var opacity: Double = 1
|
|
|
|
if let strokeOpacity = styleParts["stroke-opacity"] {
|
2016-09-27 16:45:31 +03:00
|
|
|
opacity = Double(strokeOpacity.replacingOccurrences(of: " ", with: "")) ?? 1
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
var fill: Fill?
|
|
|
|
if strokeColor.hasPrefix("url") {
|
2016-09-27 16:45:31 +03:00
|
|
|
let index = strokeColor.characters.index(strokeColor.startIndex, offsetBy: 4)
|
|
|
|
let id = strokeColor.substring(from: index)
|
|
|
|
.replacingOccurrences(of: "(", with: "")
|
|
|
|
.replacingOccurrences(of: ")", with: "")
|
|
|
|
.replacingOccurrences(of: "#", with: "")
|
2016-09-11 21:16:20 +03:00
|
|
|
fill = defFills[id]
|
|
|
|
} else {
|
2016-09-27 16:45:31 +03:00
|
|
|
fill = createColor(strokeColor.replacingOccurrences(of: " ", with: ""), opacity: opacity)
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if let strokeFill = fill {
|
|
|
|
return Stroke(fill: strokeFill,
|
|
|
|
width: getStrokeWidth(styleParts),
|
|
|
|
cap: getStrokeCap(styleParts),
|
|
|
|
join: getStrokeJoin(styleParts),
|
|
|
|
dashes: getStrokeDashes(styleParts))
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getStrokeWidth(_ styleParts: [String: String]) -> Double {
|
2016-09-11 21:16:20 +03:00
|
|
|
var width: Double = 1
|
|
|
|
if let strokeWidth = styleParts["stroke-width"] {
|
2016-09-27 16:45:31 +03:00
|
|
|
let strokeWidth = strokeWidth.replacingOccurrences(of: " ", with: "")
|
2016-09-11 21:16:20 +03:00
|
|
|
width = Double(strokeWidth)!
|
|
|
|
}
|
|
|
|
return width
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getStrokeCap(_ styleParts: [String: String]) -> LineCap {
|
2016-09-11 21:16:20 +03:00
|
|
|
var cap = LineCap.square
|
|
|
|
if let strokeCap = styleParts["stroke-linecap"] {
|
|
|
|
switch strokeCap {
|
|
|
|
case "butt":
|
|
|
|
cap = .butt
|
|
|
|
case "square":
|
|
|
|
cap = .square
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cap
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getStrokeJoin(_ styleParts: [String: String]) -> LineJoin {
|
2016-09-11 21:16:20 +03:00
|
|
|
var join = LineJoin.miter
|
|
|
|
if let strokeJoin = styleParts["stroke-linejoin"] {
|
|
|
|
switch strokeJoin {
|
|
|
|
case "miter":
|
|
|
|
join = .miter
|
|
|
|
case "bevel":
|
|
|
|
join = .bevel
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return join
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getStrokeDashes(_ styleParts: [String: String]) -> [Double] {
|
2016-09-11 21:16:20 +03:00
|
|
|
var dashes = [Double]()
|
|
|
|
if let strokeDashes = styleParts["stroke-dasharray"] {
|
2016-09-29 14:17:59 +03:00
|
|
|
var characterSet = CharacterSet()
|
|
|
|
characterSet.insert(" ")
|
|
|
|
characterSet.insert(",")
|
2016-09-27 16:45:31 +03:00
|
|
|
let separatedValues = strokeDashes.components(separatedBy: characterSet)
|
2016-09-11 21:16:20 +03:00
|
|
|
separatedValues.forEach { value in
|
|
|
|
if let doubleValue = Double(value) {
|
|
|
|
dashes.append(doubleValue)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return dashes
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getOpacity(_ styleParts: [String: String]) -> Double {
|
2016-09-11 21:16:20 +03:00
|
|
|
if let opacityAttr = styleParts["opacity"] {
|
2016-09-27 16:45:31 +03:00
|
|
|
return Double(opacityAttr.replacingOccurrences(of: " ", with: "")) ?? 1
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseLine(_ line: XMLIndexer) -> Line? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let element = line.element else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return Line(x1: getDoubleValue(element, attribute: "x1") ?? 0,
|
|
|
|
y1: getDoubleValue(element, attribute: "y1") ?? 0,
|
|
|
|
x2: getDoubleValue(element, attribute: "x2") ?? 0,
|
|
|
|
y2: getDoubleValue(element, attribute: "y2") ?? 0)
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseRect(_ rect: XMLIndexer) -> Locus? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let element = rect.element,
|
2016-09-27 16:45:31 +03:00
|
|
|
let width = getDoubleValue(element, attribute: "width"),
|
|
|
|
let height = getDoubleValue(element, attribute: "height")
|
|
|
|
, width > 0 && height > 0 else {
|
2016-09-11 21:16:20 +03:00
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
let resultRect = Rect(x: getDoubleValue(element, attribute: "x") ?? 0, y: getDoubleValue(element, attribute: "y") ?? 0, w: width, h: height)
|
|
|
|
|
|
|
|
let rxOpt = getDoubleValue(element, attribute: "rx")
|
|
|
|
let ryOpt = getDoubleValue(element, attribute: "ry")
|
2016-09-27 16:45:31 +03:00
|
|
|
if let rx = rxOpt, let ry = ryOpt {
|
2016-09-11 21:16:20 +03:00
|
|
|
return RoundRect(rect: resultRect, rx: rx, ry: ry)
|
|
|
|
}
|
|
|
|
let rOpt = rxOpt ?? ryOpt
|
2016-09-27 16:45:31 +03:00
|
|
|
if let r = rOpt , r >= 0 {
|
2016-09-11 21:16:20 +03:00
|
|
|
return RoundRect(rect: resultRect, rx: r, ry: r)
|
|
|
|
}
|
|
|
|
return resultRect
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseCircle(_ circle: XMLIndexer) -> Circle? {
|
|
|
|
guard let element = circle.element, let r = getDoubleValue(element, attribute: "r") , r > 0 else {
|
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return Circle(cx: getDoubleValue(element, attribute: "cx") ?? 0, cy: getDoubleValue(element, attribute: "cy") ?? 0, r: r)
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseEllipse(_ ellipse: XMLIndexer) -> Ellipse? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let element = ellipse.element,
|
2016-09-27 16:45:31 +03:00
|
|
|
let rx = getDoubleValue(element, attribute: "rx"),
|
|
|
|
let ry = getDoubleValue(element, attribute: "ry")
|
|
|
|
, rx > 0 && ry > 0 else {
|
2016-09-11 21:16:20 +03:00
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return Ellipse(cx: getDoubleValue(element, attribute: "cx") ?? 0, cy: getDoubleValue(element, attribute: "cy") ?? 0, rx: rx, ry: ry)
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parsePolygon(_ polygon: XMLIndexer) -> Polygon? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let element = polygon.element else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if let points = element.attributes["points"] {
|
|
|
|
return Polygon(points: parsePoints(points))
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parsePolyline(_ polyline: XMLIndexer) -> Polyline? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let element = polyline.element else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if let points = element.attributes["points"] {
|
|
|
|
return Polyline(points: parsePoints(points))
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parsePoints(_ pointsString: String) -> [Double] {
|
2016-09-11 21:16:20 +03:00
|
|
|
var resultPoints: [Double] = []
|
2016-09-27 16:45:31 +03:00
|
|
|
let pointPairs = pointsString.components(separatedBy: " ")
|
2016-09-11 21:16:20 +03:00
|
|
|
|
|
|
|
pointPairs.forEach { pointPair in
|
2016-09-27 16:45:31 +03:00
|
|
|
let points = pointPair.components(separatedBy: ",")
|
2016-09-11 21:16:20 +03:00
|
|
|
points.forEach { point in
|
|
|
|
if let resultPoint = Double(point) {
|
|
|
|
resultPoints.append(resultPoint)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return resultPoints
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseImage(_ image: XMLIndexer, opacity: Double, pos: Transform = Transform()) -> Image? {
|
|
|
|
guard let element = image.element, let link = element.attributes["xlink:href"] else {
|
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
let position = pos.move(dx: getDoubleValue(element, attribute: "x") ?? 0, dy: getDoubleValue(element, attribute: "y") ?? 0)
|
|
|
|
return Image(src: link, w: getIntValue(element, attribute: "width") ?? 0, h: getIntValue(element, attribute: "height") ?? 0, place: position)
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseText(_ text: XMLIndexer, fill: Fill?, opacity: Double, fontName: String?, fontSize: Int?,
|
2016-09-11 21:16:20 +03:00
|
|
|
pos: Transform = Transform()) -> Node? {
|
|
|
|
guard let element = text.element else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
if text.children.isEmpty {
|
|
|
|
return parseSimpleText(element, fill: fill, opacity: opacity, fontName: fontName, fontSize: fontSize)
|
|
|
|
} else {
|
|
|
|
guard let matcher = SVGParserRegexHelper.getTextElementMatcher() else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
let elementString = element.description
|
|
|
|
let fullRange = NSMakeRange(0, elementString.characters.count)
|
2016-09-27 16:45:31 +03:00
|
|
|
if let match = matcher.firstMatch(in: elementString, options: .reportCompletion, range: fullRange) {
|
|
|
|
let tspans = (elementString as NSString).substring(with: match.rangeAt(1))
|
2016-09-11 21:16:20 +03:00
|
|
|
return Group(contents: collectTspans(tspans, fill: fill, opacity: opacity, fontName: fontName, fontSize: fontSize,
|
|
|
|
bounds: Rect(x: getDoubleValue(element, attribute: "x") ?? 0, y: getDoubleValue(element, attribute: "y") ?? 0)),
|
|
|
|
place: pos)
|
|
|
|
}
|
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseSimpleText(_ text: XMLElement, fill: Fill?, opacity: Double, fontName: String?, fontSize: Int?, pos: Transform = Transform()) -> Text? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let string = text.text else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
let position = pos.move(dx: getDoubleValue(text, attribute: "x") ?? 0, dy: getDoubleValue(text, attribute: "y") ?? 0)
|
2016-09-27 16:45:31 +03:00
|
|
|
return Text(text: string, font: getFont(fontName: fontName, fontSize: fontSize), fill: fill ?? Color.black, place: position, opacity: opacity)
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// REFACTOR
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func collectTspans(_ tspan: String, collectedTspans: [Node] = [], withWhitespace: Bool = false, fill: Fill?, opacity: Double, fontName: String?, fontSize: Int?, bounds: Rect) -> [Node] {
|
|
|
|
let fullString = tspan.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) as NSString
|
2016-09-11 21:16:20 +03:00
|
|
|
// exit recursion
|
2016-09-27 16:45:31 +03:00
|
|
|
if fullString.isEqual(to: "") {
|
2016-09-11 21:16:20 +03:00
|
|
|
return collectedTspans
|
|
|
|
}
|
|
|
|
var collection = collectedTspans
|
2016-09-27 16:45:31 +03:00
|
|
|
let tagRange = fullString.range(of: "<tspan".lowercased())
|
2016-09-11 21:16:20 +03:00
|
|
|
if tagRange.location == 0 {
|
|
|
|
// parse as <tspan> element
|
2016-09-27 16:45:31 +03:00
|
|
|
let closingTagRange = fullString.range(of: "</tspan>".lowercased())
|
|
|
|
let tspanString = fullString.substring(to: closingTagRange.location + closingTagRange.length)
|
2016-09-11 21:16:20 +03:00
|
|
|
let tspanXml = SWXMLHash.parse(tspanString)
|
|
|
|
guard let indexer = tspanXml.children.first,
|
2016-09-27 16:45:31 +03:00
|
|
|
let text = parseTspan(indexer, withWhitespace: withWhitespace, fill: fill, opacity: opacity, fontName: fontName, fontSize: fontSize, bounds: bounds) else {
|
2016-09-11 21:16:20 +03:00
|
|
|
|
|
|
|
// skip this element if it can't be parsed
|
2016-09-27 16:45:31 +03:00
|
|
|
return collectTspans(fullString.substring(from: closingTagRange.location + closingTagRange.length), collectedTspans: collectedTspans, fill: fill, opacity: opacity,
|
2016-09-11 21:16:20 +03:00
|
|
|
fontName: fontName, fontSize: fontSize, bounds: bounds)
|
|
|
|
}
|
|
|
|
collection.append(text)
|
2016-09-27 16:45:31 +03:00
|
|
|
let nextString = fullString.substring(from: closingTagRange.location + closingTagRange.length) as NSString
|
2016-09-11 21:16:20 +03:00
|
|
|
var withWhitespace = false
|
2016-09-27 16:45:31 +03:00
|
|
|
if nextString.rangeOfCharacter(from: CharacterSet.whitespacesAndNewlines).location == 0 {
|
2016-09-11 21:16:20 +03:00
|
|
|
withWhitespace = true
|
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
return collectTspans(fullString.substring(from: closingTagRange.location + closingTagRange.length), collectedTspans: collection, withWhitespace: withWhitespace, fill: fill, opacity: opacity, fontName: fontName, fontSize: fontSize, bounds: text.bounds())
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
// parse as regular text element
|
|
|
|
var textString: NSString
|
|
|
|
if tagRange.location >= fullString.length {
|
|
|
|
textString = fullString
|
|
|
|
} else {
|
2016-09-27 16:45:31 +03:00
|
|
|
textString = fullString.substring(to: tagRange.location) as NSString
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
var nextStringWhitespace = false
|
2016-09-27 16:45:31 +03:00
|
|
|
var trimmedString = textString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
2016-09-11 21:16:20 +03:00
|
|
|
if trimmedString.characters.count != textString.length {
|
|
|
|
nextStringWhitespace = true
|
|
|
|
}
|
|
|
|
trimmedString = withWhitespace ? " \(trimmedString)" : trimmedString
|
|
|
|
let text = Text(text: trimmedString, font: getFont(fontName: fontName, fontSize: fontSize),
|
|
|
|
fill: fill ?? Color.black, baseline: .alphabetic,
|
|
|
|
place: Transform().move(dx: bounds.x + bounds.w, dy: bounds.y), opacity: opacity)
|
|
|
|
collection.append(text)
|
2016-09-27 16:45:31 +03:00
|
|
|
return collectTspans(fullString.substring(from: tagRange.location), collectedTspans: collection, withWhitespace: nextStringWhitespace, fill: fill, opacity: opacity,
|
2016-09-11 21:16:20 +03:00
|
|
|
fontName: fontName, fontSize: fontSize, bounds: text.bounds())
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseTspan(_ tspan: XMLIndexer, withWhitespace: Bool = false, fill: Fill?, opacity: Double, fontName: String?,
|
2016-09-11 21:16:20 +03:00
|
|
|
fontSize: Int?, bounds: Rect) -> Text? {
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
guard let element = tspan.element, let string = element.text else {
|
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
var shouldAddWhitespace = withWhitespace
|
|
|
|
let pos = getTspanPosition(element, bounds: bounds, withWhitespace: &shouldAddWhitespace)
|
|
|
|
let text = shouldAddWhitespace ? " \(string)" : string
|
|
|
|
let attributes = getStyleAttributes([:], element: element)
|
|
|
|
|
|
|
|
return Text(text: text, font: getFont(attributes, fontName: fontName, fontSize: fontSize),
|
|
|
|
fill: getFillColor(attributes) ?? fill ?? Color.black, baseline: .alphabetic,
|
2016-09-27 16:45:31 +03:00
|
|
|
place: pos, opacity: getOpacity(attributes) ?? opacity)
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getFont(_ attributes: [String: String] = [:], fontName: String?, fontSize: Int?) -> Font {
|
2016-09-11 21:16:20 +03:00
|
|
|
return Font(
|
|
|
|
name: getFontName(attributes) ?? fontName ?? "Serif",
|
|
|
|
size: getFontSize(attributes) ?? fontSize ?? 12)
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getTspanPosition(_ element: XMLElement, bounds: Rect, withWhitespace: inout Bool) -> Transform {
|
2016-09-11 21:16:20 +03:00
|
|
|
var xPos: Double
|
|
|
|
var yPos: Double
|
|
|
|
|
|
|
|
if let absX = getDoubleValue(element, attribute: "x") {
|
|
|
|
xPos = absX
|
|
|
|
withWhitespace = false
|
|
|
|
} else if let relX = getDoubleValue(element, attribute: "dx") {
|
|
|
|
xPos = bounds.x + bounds.w + relX
|
|
|
|
} else {
|
|
|
|
xPos = bounds.x + bounds.w
|
|
|
|
}
|
|
|
|
|
|
|
|
if let absY = getDoubleValue(element, attribute: "y") {
|
|
|
|
yPos = absY
|
|
|
|
} else if let relY = getDoubleValue(element, attribute: "dy") {
|
|
|
|
yPos = bounds.y + relY
|
|
|
|
} else {
|
|
|
|
yPos = bounds.y
|
|
|
|
}
|
|
|
|
return Transform().move(dx: xPos, dy: yPos)
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseUse(_ use: XMLIndexer, fill: Fill?, stroke: Stroke?, pos: Transform, opacity: Double) -> Node? {
|
|
|
|
guard let element = use.element, let link = element.attributes["xlink:href"] else {
|
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
var id = link
|
|
|
|
if id.hasPrefix("#") {
|
2016-09-29 14:17:59 +03:00
|
|
|
id = id.replacingOccurrences(of: "#", with: "")
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
guard let referenceNode = self.defNodes[id], let node = copyNode(referenceNode) else {
|
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
node.place = pos.move(dx: getDoubleValue(element, attribute: "x") ?? 0, dy: getDoubleValue(element, attribute: "y") ?? 0)
|
|
|
|
node.opacity = opacity
|
|
|
|
if let shape = node as? Shape {
|
|
|
|
if let color = fill {
|
|
|
|
shape.fill = color
|
|
|
|
}
|
|
|
|
if let line = stroke {
|
|
|
|
shape.stroke = line
|
|
|
|
}
|
|
|
|
return shape
|
|
|
|
}
|
|
|
|
if let text = node as? Text {
|
|
|
|
if let color = fill {
|
|
|
|
text.fill = color
|
|
|
|
}
|
|
|
|
return text
|
|
|
|
}
|
|
|
|
return node
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseLinearGradient(_ gradient: XMLIndexer) -> Fill? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let element = gradient.element else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
var parentGradient: LinearGradient?
|
2016-09-29 14:17:59 +03:00
|
|
|
if let link = element.attributes["xlink:href"]?.replacingOccurrences(of: " ", with: "")
|
2016-09-27 16:45:31 +03:00
|
|
|
, link.hasPrefix("#") {
|
2016-09-11 21:16:20 +03:00
|
|
|
|
2016-09-29 14:17:59 +03:00
|
|
|
let id = link.replacingOccurrences(of: "#", with: "")
|
2016-09-11 21:16:20 +03:00
|
|
|
parentGradient = defFills[id] as? LinearGradient
|
|
|
|
}
|
|
|
|
|
|
|
|
var stopsArray: [Stop]?
|
|
|
|
if gradient.children.isEmpty {
|
|
|
|
stopsArray = parentGradient?.stops
|
|
|
|
} else {
|
|
|
|
stopsArray = parseStops(gradient.children)
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let stops = stopsArray else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
switch stops.count {
|
|
|
|
case 0:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
case 1:
|
|
|
|
return stops.first?.color
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
let x1 = getDoubleValueFromPercentage(element, attribute: "x1") ?? parentGradient?.x1 ?? 0
|
|
|
|
let y1 = getDoubleValueFromPercentage(element, attribute: "y1") ?? parentGradient?.y1 ?? 0
|
|
|
|
let x2 = getDoubleValueFromPercentage(element, attribute: "x2") ?? parentGradient?.x2 ?? 1
|
|
|
|
let y2 = getDoubleValueFromPercentage(element, attribute: "y2") ?? parentGradient?.y2 ?? 0
|
|
|
|
var userSpace = false
|
2016-09-27 16:45:31 +03:00
|
|
|
if let gradientUnits = element.attributes["gradientUnits"] , gradientUnits == "userSpaceOnUse" {
|
2016-09-11 21:16:20 +03:00
|
|
|
userSpace = true
|
|
|
|
} else if let parent = parentGradient {
|
|
|
|
userSpace = parent.userSpace
|
|
|
|
}
|
|
|
|
return LinearGradient(x1: x1, y1: y1, x2: x2, y2: y2, userSpace: userSpace, stops: stops)
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseRadialGradient(_ gradient: XMLIndexer) -> Fill? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let element = gradient.element else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
var parentGradient: RadialGradient?
|
2016-09-29 14:17:59 +03:00
|
|
|
if let link = element.attributes["xlink:href"]?.replacingOccurrences(of: " ", with: "")
|
2016-09-27 16:45:31 +03:00
|
|
|
, link.hasPrefix("#") {
|
2016-09-11 21:16:20 +03:00
|
|
|
|
2016-09-29 14:17:59 +03:00
|
|
|
let id = link.replacingOccurrences(of: "#", with: "")
|
2016-09-11 21:16:20 +03:00
|
|
|
parentGradient = defFills[id] as? RadialGradient
|
|
|
|
}
|
|
|
|
|
|
|
|
var stopsArray: [Stop]?
|
|
|
|
if gradient.children.isEmpty {
|
|
|
|
stopsArray = parentGradient?.stops
|
|
|
|
} else {
|
|
|
|
stopsArray = parseStops(gradient.children)
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let stops = stopsArray else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
switch stops.count {
|
|
|
|
case 0:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
case 1:
|
|
|
|
return stops.first?.color
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
let cx = getDoubleValueFromPercentage(element, attribute: "cx") ?? parentGradient?.cx ?? 0.5
|
|
|
|
let cy = getDoubleValueFromPercentage(element, attribute: "cy") ?? parentGradient?.cy ?? 0.5
|
|
|
|
let fx = getDoubleValueFromPercentage(element, attribute: "fx") ?? parentGradient?.fx ?? cx
|
|
|
|
let fy = getDoubleValueFromPercentage(element, attribute: "fy") ?? parentGradient?.fy ?? cy
|
|
|
|
let r = getDoubleValueFromPercentage(element, attribute: "r") ?? parentGradient?.r ?? 0.5
|
|
|
|
var userSpace = false
|
2016-09-27 16:45:31 +03:00
|
|
|
if let gradientUnits = element.attributes["gradientUnits"] , gradientUnits == "userSpaceOnUse" {
|
2016-09-11 21:16:20 +03:00
|
|
|
userSpace = true
|
|
|
|
} else if let parent = parentGradient {
|
|
|
|
userSpace = parent.userSpace
|
|
|
|
}
|
|
|
|
return RadialGradient(cx: cx, cy: cy, fx: fx, fy: fy, r: r, userSpace: userSpace, stops: stops)
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseStops(_ stops: [XMLIndexer]) -> [Stop] {
|
2016-09-11 21:16:20 +03:00
|
|
|
var result = [Stop]()
|
|
|
|
stops.forEach { stopXML in
|
|
|
|
if let stop = parseStop(stopXML) {
|
|
|
|
result.append(stop)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseStop(_ stop: XMLIndexer) -> Stop? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let element = stop.element else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-29 14:17:59 +03:00
|
|
|
guard var offset = getDoubleValueFromPercentage(element, attribute: "offset") else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
2016-09-29 14:17:59 +03:00
|
|
|
|
2016-09-11 21:16:20 +03:00
|
|
|
if offset < 0 {
|
|
|
|
offset = 0
|
|
|
|
} else if offset > 1 {
|
|
|
|
offset = 1
|
|
|
|
}
|
|
|
|
var opacity: Double = 1
|
2016-09-27 16:45:31 +03:00
|
|
|
if let stopOpacity = getStyleAttributes([:], element: element)["stop-opacity"], let doubleValue = Double(stopOpacity) {
|
2016-09-11 21:16:20 +03:00
|
|
|
opacity = doubleValue
|
|
|
|
}
|
|
|
|
var color = Color.black
|
|
|
|
if let stopColor = getStyleAttributes([:], element: element)["stop-color"] {
|
2016-09-29 14:17:59 +03:00
|
|
|
color = createColor(stopColor.replacingOccurrences(of: " ", with: ""), opacity: opacity)
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-29 14:17:59 +03:00
|
|
|
return Stop(offset: offset, color: color)
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parsePath(_ path: XMLIndexer) -> Path? {
|
2016-09-11 21:16:20 +03:00
|
|
|
if let dAttr = path.element!.attributes["d"] {
|
|
|
|
let pathSegments = parseCommands(dAttr)
|
|
|
|
return Path(segments: pathSegments)
|
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseCommands(_ d: String) -> [PathSegment] {
|
2016-09-11 21:16:20 +03:00
|
|
|
var pathCommands = [PathCommand]()
|
|
|
|
var commandChar = Character(" ")
|
|
|
|
var commandString = ""
|
|
|
|
|
|
|
|
d.characters.forEach { character in
|
|
|
|
if isCommandCharacter(character) {
|
|
|
|
if !commandString.isEmpty {
|
|
|
|
pathCommands.append(
|
|
|
|
PathCommand(
|
|
|
|
type: getCommandType(commandChar),
|
|
|
|
expression: commandString,
|
|
|
|
absolute: isAbsolute(commandChar)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if character == closePathAbsolute || character == closePathRelative {
|
|
|
|
pathCommands.append(
|
|
|
|
PathCommand(
|
|
|
|
type: getCommandType(character),
|
|
|
|
expression: commandString,
|
|
|
|
absolute: true
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
commandString = ""
|
|
|
|
commandChar = character
|
|
|
|
} else {
|
|
|
|
commandString.append(character)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !commandString.isEmpty && !(commandChar == " ") {
|
|
|
|
pathCommands.append(
|
|
|
|
PathCommand(type: getCommandType(commandChar),
|
|
|
|
expression: commandString,
|
|
|
|
absolute: isAbsolute(commandChar)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
var commands = [PathSegment]()
|
|
|
|
|
|
|
|
pathCommands.forEach { command in
|
|
|
|
if let parsedCommand = parseCommand(command) {
|
|
|
|
commands.append(parsedCommand)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return commands
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func parseCommand(_ command: PathCommand) -> PathSegment? {
|
2016-09-29 14:17:59 +03:00
|
|
|
var characterSet = CharacterSet()
|
|
|
|
characterSet.insert(" ")
|
|
|
|
characterSet.insert(",")
|
2016-09-27 16:45:31 +03:00
|
|
|
let commandParams = command.expression.components(separatedBy: characterSet)
|
2016-09-11 21:16:20 +03:00
|
|
|
var separatedValues = [String]()
|
|
|
|
commandParams.forEach { param in
|
2016-09-27 16:45:31 +03:00
|
|
|
separatedValues.append(contentsOf: separateNegativeValuesIfNeeded(param))
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
switch command.type {
|
2016-09-27 16:45:31 +03:00
|
|
|
case .moveTo:
|
2016-09-13 16:00:39 +03:00
|
|
|
var data = [Double]()
|
|
|
|
separatedValues.forEach { value in
|
|
|
|
if let double = Double(value) {
|
|
|
|
data.append(double)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if data.count < 2 {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-13 16:00:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return PathSegment(type: command.absolute ? .M : .m, data: data)
|
2016-09-11 21:16:20 +03:00
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
case .lineTo:
|
2016-09-13 16:00:39 +03:00
|
|
|
var data = [Double]()
|
|
|
|
separatedValues.forEach { value in
|
|
|
|
if let double = Double(value) {
|
|
|
|
data.append(double)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if data.count < 2 {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-13 16:00:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return PathSegment(type: command.absolute ? .L : .l, data: data)
|
2016-09-11 21:16:20 +03:00
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
case .lineH:
|
2016-09-11 21:16:20 +03:00
|
|
|
if separatedValues.count < 1 {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
guard let x = Double(separatedValues[0]) else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return PathSegment(type: command.absolute ? .H : .h, data: [x])
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
case .lineV:
|
2016-09-11 21:16:20 +03:00
|
|
|
if separatedValues.count < 1 {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
guard let y = Double(separatedValues[0]) else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return PathSegment(type: command.absolute ? .V : .v, data: [y])
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
case .curveTo:
|
2016-09-13 16:00:39 +03:00
|
|
|
var data = [Double]()
|
|
|
|
separatedValues.forEach { value in
|
|
|
|
if let double = Double(value) {
|
|
|
|
data.append(double)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if data.count < 6 {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-13 16:00:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return PathSegment(type: command.absolute ? .C : .c, data: data)
|
2016-09-11 21:16:20 +03:00
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
case .smoothCurveTo:
|
2016-09-13 16:00:39 +03:00
|
|
|
var data = [Double]()
|
|
|
|
separatedValues.forEach { value in
|
|
|
|
if let double = Double(value) {
|
|
|
|
data.append(double)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if data.count < 4 {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-13 16:00:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return PathSegment(type: command.absolute ? .S : .s, data: data)
|
2016-09-11 21:16:20 +03:00
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
case .closePath:
|
|
|
|
return PathSegment(type: .z)
|
2016-09-11 21:16:20 +03:00
|
|
|
default:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func separateNegativeValuesIfNeeded(_ expression: String) -> [String] {
|
2016-09-11 21:16:20 +03:00
|
|
|
var values = [String]()
|
|
|
|
var value = String()
|
|
|
|
var e = false
|
|
|
|
|
|
|
|
expression.characters.forEach { c in
|
|
|
|
if c == "e" {
|
|
|
|
e = true
|
|
|
|
}
|
|
|
|
if c == "-" && !e {
|
|
|
|
if value.characters.count != 0 {
|
|
|
|
values.append(value)
|
|
|
|
value = String()
|
|
|
|
}
|
|
|
|
e = false
|
|
|
|
}
|
|
|
|
|
|
|
|
value.append(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
if value.characters.count != 0 {
|
|
|
|
values.append(value)
|
|
|
|
}
|
|
|
|
|
|
|
|
return values
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func isCommandCharacter(_ character: Character) -> Bool {
|
2016-09-11 21:16:20 +03:00
|
|
|
switch character {
|
|
|
|
case moveToAbsolute:
|
|
|
|
return true
|
|
|
|
case moveToRelative:
|
|
|
|
return true
|
|
|
|
case lineToAbsolute:
|
|
|
|
return true
|
|
|
|
case lineToRelative:
|
|
|
|
return true
|
|
|
|
case lineHorizontalAbsolute:
|
|
|
|
return true
|
|
|
|
case lineHorizontalRelative:
|
|
|
|
return true
|
|
|
|
case lineVerticalAbsolute:
|
|
|
|
return true
|
|
|
|
case lineVerticalRelative:
|
|
|
|
return true
|
|
|
|
case curveToAbsolute:
|
|
|
|
return true
|
|
|
|
case curveToRelative:
|
|
|
|
return true
|
|
|
|
case smoothCurveToAbsolute:
|
|
|
|
return true
|
|
|
|
case smoothCurveToRelative:
|
|
|
|
return true
|
|
|
|
case closePathAbsolute:
|
|
|
|
return true
|
|
|
|
case closePathRelative:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func isAbsolute(_ character: Character) -> Bool {
|
2016-09-11 21:16:20 +03:00
|
|
|
switch character {
|
|
|
|
case moveToAbsolute:
|
|
|
|
return true
|
|
|
|
case moveToRelative:
|
|
|
|
return false
|
|
|
|
case lineToAbsolute:
|
|
|
|
return true
|
|
|
|
case lineToRelative:
|
|
|
|
return false
|
|
|
|
case lineHorizontalAbsolute:
|
|
|
|
return true
|
|
|
|
case lineHorizontalRelative:
|
|
|
|
return false
|
|
|
|
case lineVerticalAbsolute:
|
|
|
|
return true
|
|
|
|
case lineVerticalRelative:
|
|
|
|
return false
|
|
|
|
case curveToAbsolute:
|
|
|
|
return true
|
|
|
|
case curveToRelative:
|
|
|
|
return false
|
|
|
|
case smoothCurveToAbsolute:
|
|
|
|
return true
|
|
|
|
case smoothCurveToRelative:
|
|
|
|
return false
|
|
|
|
case closePathAbsolute:
|
|
|
|
return true
|
|
|
|
case closePathRelative:
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getCommandType(_ character: Character) -> PathCommandType {
|
2016-09-11 21:16:20 +03:00
|
|
|
switch character {
|
|
|
|
case moveToAbsolute:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .moveTo
|
2016-09-11 21:16:20 +03:00
|
|
|
case moveToRelative:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .moveTo
|
2016-09-11 21:16:20 +03:00
|
|
|
case lineToAbsolute:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .lineTo
|
2016-09-11 21:16:20 +03:00
|
|
|
case lineToRelative:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .lineTo
|
2016-09-11 21:16:20 +03:00
|
|
|
case lineVerticalAbsolute:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .lineV
|
2016-09-11 21:16:20 +03:00
|
|
|
case lineVerticalRelative:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .lineV
|
2016-09-11 21:16:20 +03:00
|
|
|
case lineHorizontalAbsolute:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .lineH
|
2016-09-11 21:16:20 +03:00
|
|
|
case lineHorizontalRelative:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .lineH
|
2016-09-11 21:16:20 +03:00
|
|
|
case curveToAbsolute:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .curveTo
|
2016-09-11 21:16:20 +03:00
|
|
|
case curveToRelative:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .curveTo
|
2016-09-11 21:16:20 +03:00
|
|
|
case smoothCurveToAbsolute:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .smoothCurveTo
|
2016-09-11 21:16:20 +03:00
|
|
|
case smoothCurveToRelative:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .smoothCurveTo
|
2016-09-11 21:16:20 +03:00
|
|
|
case closePathAbsolute:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .closePath
|
2016-09-11 21:16:20 +03:00
|
|
|
case closePathRelative:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .closePath
|
2016-09-11 21:16:20 +03:00
|
|
|
default:
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getDoubleValue(_ element: XMLElement, attribute: String) -> Double? {
|
|
|
|
guard let attributeValue = element.attributes[attribute], let doubleValue = Double(attributeValue) else {
|
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
return doubleValue
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getDoubleValueFromPercentage(_ element: XMLElement, attribute: String) -> Double? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let attributeValue = element.attributes[attribute] else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
2016-09-29 14:17:59 +03:00
|
|
|
if !attributeValue.contains("%") {
|
2016-09-11 21:16:20 +03:00
|
|
|
return self.getDoubleValue(element, attribute: attribute)
|
|
|
|
} else {
|
2016-09-29 14:17:59 +03:00
|
|
|
let value = attributeValue.replacingOccurrences(of: "%", with: "")
|
2016-09-11 21:16:20 +03:00
|
|
|
if let doubleValue = Double(value) {
|
|
|
|
return doubleValue / 100
|
|
|
|
}
|
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getIntValue(_ element: XMLElement, attribute: String) -> Int? {
|
|
|
|
guard let attributeValue = element.attributes[attribute], let intValue = Int(attributeValue) else {
|
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
return intValue
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getFontName(_ attributes: [String: String]) -> String? {
|
2016-09-11 21:16:20 +03:00
|
|
|
return attributes["font-family"]
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getFontSize(_ attributes: [String: String]) -> Int? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let fontSize = attributes["font-size"] else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
if let size = Double(fontSize) {
|
|
|
|
return (Int(round(size)))
|
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getFontStyle(_ attributes: [String: String], style: String) -> Bool? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let fontStyle = attributes["font-style"] else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
if fontStyle.lowercased() == style {
|
2016-09-11 21:16:20 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getFontWeight(_ attributes: [String: String], style: String) -> Bool? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let fontWeight = attributes["font-weight"] else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
if fontWeight.lowercased() == style {
|
2016-09-11 21:16:20 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func getTextDecoration(_ attributes: [String: String], decoration: String) -> Bool? {
|
2016-09-11 21:16:20 +03:00
|
|
|
guard let textDecoration = attributes["text-decoration"] else {
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
if textDecoration.contains(decoration) {
|
2016-09-11 21:16:20 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:45:31 +03:00
|
|
|
fileprivate func copyNode(_ referenceNode: Node) -> Node? {
|
2016-09-11 21:16:20 +03:00
|
|
|
|
|
|
|
let pos = referenceNode.place
|
|
|
|
let opaque = referenceNode.opaque
|
|
|
|
let visible = referenceNode.visible
|
|
|
|
let clip = referenceNode.clip
|
|
|
|
let tag = referenceNode.tag
|
|
|
|
|
|
|
|
if let shape = referenceNode as? Shape {
|
2016-09-27 16:45:31 +03:00
|
|
|
return Shape(form: shape.form, fill: shape.fill, stroke: shape.stroke, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
if let text = referenceNode as? Text {
|
2016-09-27 16:45:31 +03:00
|
|
|
return Text(text: text.text, font: text.font, fill: text.fill, align: text.align, baseline: text.baseline, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
if let image = referenceNode as? Image {
|
2016-09-27 16:45:31 +03:00
|
|
|
return Image(src: image.src, xAlign: image.xAlign, yAlign: image.yAlign, aspectRatio: image.aspectRatio, w: image.w, h: image.h, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
|
|
|
if let group = referenceNode as? Group {
|
|
|
|
var contents = [Node]()
|
|
|
|
group.contents.forEach { node in
|
|
|
|
if let copy = copyNode(node) {
|
|
|
|
contents.append(copy)
|
|
|
|
}
|
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
return Group(contents: contents, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
2016-09-27 16:45:31 +03:00
|
|
|
return .none
|
2016-09-11 21:16:20 +03:00
|
|
|
}
|
2016-11-30 13:59:58 +03:00
|
|
|
|
|
|
|
fileprivate func degreesToRadians(_ degrees: Double) -> Double {
|
|
|
|
return degrees * .pi / 180
|
|
|
|
}
|
|
|
|
|
2016-08-03 12:16:30 +03:00
|
|
|
}
|