diff --git a/Macaw.xcodeproj/project.pbxproj b/Macaw.xcodeproj/project.pbxproj index 4ae70bc2..b9b14122 100644 --- a/Macaw.xcodeproj/project.pbxproj +++ b/Macaw.xcodeproj/project.pbxproj @@ -253,6 +253,7 @@ 57F1087C1F53CA7E00DC365B /* MDisplayLink_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F1087B1F53CA7E00DC365B /* MDisplayLink_iOS.swift */; }; 57FCD2771D76EA4600CC0FB6 /* Macaw.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57FCD26C1D76EA4600CC0FB6 /* Macaw.framework */; }; 57FCD27C1D76EA4600CC0FB6 /* MacawTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FCD27B1D76EA4600CC0FB6 /* MacawTests.swift */; }; + 602C561D2081C984003AD452 /* rounded.svg in Resources */ = {isa = PBXBuildFile; fileRef = 602C561C2081C984003AD452 /* rounded.svg */; }; 5B1FFD7A207E083600716A46 /* SvgContentLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAA56A7207C73FF0055BC5B /* SvgContentLayout.swift */; }; 5BAA56A8207C73FF0055BC5B /* SvgContentLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAA56A7207C73FF0055BC5B /* SvgContentLayout.swift */; }; 5BAEA9C9206CEAA20049AAAE /* viewBox.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5BAEA9C8206CEAA20049AAAE /* viewBox.svg */; }; @@ -453,6 +454,7 @@ 57FCD2761D76EA4600CC0FB6 /* MacawTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MacawTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57FCD27B1D76EA4600CC0FB6 /* MacawTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacawTests.swift; sourceTree = ""; }; 57FCD27D1D76EA4600CC0FB6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 602C561C2081C984003AD452 /* rounded.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = rounded.svg; sourceTree = ""; }; 5BAA56A7207C73FF0055BC5B /* SvgContentLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SvgContentLayout.swift; sourceTree = ""; }; 5BAEA9C8206CEAA20049AAAE /* viewBox.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = viewBox.svg; sourceTree = ""; }; 5BAEA9CA206CEB7D0049AAAE /* viewBox.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = viewBox.reference; sourceTree = ""; }; @@ -563,6 +565,7 @@ 57CAB1241D7832E000FD8E47 /* svg */ = { isa = PBXGroup; children = ( + 602C561C2081C984003AD452 /* rounded.svg */, 5BAEA9C8206CEAA20049AAAE /* viewBox.svg */, C46E83541F94B20E00208037 /* transform.svg */, C43B064C1F9738EF00787A35 /* clip.svg */, @@ -1092,6 +1095,7 @@ 5BAEA9CB206CEB7D0049AAAE /* viewBox.reference in Resources */, 57CAB1311D7832E000FD8E47 /* line.svg in Resources */, 57B7A4DF1EE70D17009D78D7 /* logo.png in Resources */, + 602C561D2081C984003AD452 /* rounded.svg in Resources */, 57CAB1321D7832E000FD8E47 /* polygon.svg in Resources */, 57CAB12F1D7832E000FD8E47 /* ellipse.svg in Resources */, 57CAB1341D7832E000FD8E47 /* rect.svg in Resources */, diff --git a/MacawTests/svg/rounded.svg b/MacawTests/svg/rounded.svg new file mode 100644 index 00000000..2441657e --- /dev/null +++ b/MacawTests/svg/rounded.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/Source/model/draw/Stop.swift b/Source/model/draw/Stop.swift index b5b8a38c..0c717aa2 100644 --- a/Source/model/draw/Stop.swift +++ b/Source/model/draw/Stop.swift @@ -6,7 +6,14 @@ open class Stop { open let color: Color public init(offset: Double = 0, color: Color) { - self.offset = offset self.color = color + + if offset < 0 { + self.offset = 0 + } else if offset > 1 { + self.offset = 1 + } else { + self.offset = offset + } } } diff --git a/Source/model/draw/Stroke.swift b/Source/model/draw/Stroke.swift index 162cf49e..e09c5f77 100644 --- a/Source/model/draw/Stroke.swift +++ b/Source/model/draw/Stroke.swift @@ -7,12 +7,14 @@ open class Stroke { open let cap: LineCap open let join: LineJoin open let dashes: [Double] + open let offset: Double - public init(fill: Fill = Color.black, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, dashes: [Double] = []) { + public init(fill: Fill = Color.black, width: Double = 1, cap: LineCap = .butt, join: LineJoin = .miter, dashes: [Double] = [], offset: Double = 0.0) { self.fill = fill self.width = width self.cap = cap self.join = join self.dashes = dashes + self.offset = offset } } diff --git a/Source/render/ShapeRenderer.swift b/Source/render/ShapeRenderer.swift index 05ac7e16..f519df4f 100644 --- a/Source/render/ShapeRenderer.swift +++ b/Source/render/ShapeRenderer.swift @@ -167,14 +167,9 @@ class ShapeRenderer: NodeRenderer { ctx!.setLineWidth(CGFloat(stroke.width)) ctx!.setLineJoin(RenderUtils.mapLineJoin(stroke.join)) ctx!.setLineCap(RenderUtils.mapLineCap(stroke.cap)) - let dashes = stroke.dashes - if !dashes.isEmpty { - var floatDashes = [CGFloat]() - dashes.forEach { dash in - floatDashes.append(CGFloat(dash)) - } - - ctx?.setLineDash(phase: 0.0, lengths: floatDashes) + if !stroke.dashes.isEmpty { + ctx?.setLineDash(phase: CGFloat(stroke.offset), + lengths: stroke.dashes.map{ CGFloat($0) }) } } diff --git a/Source/svg/SVGParser.swift b/Source/svg/SVGParser.swift index cc6555da..666398f3 100644 --- a/Source/svg/SVGParser.swift +++ b/Source/svg/SVGParser.swift @@ -15,8 +15,8 @@ open class SVGParser { var svgSize: Size? var viewBox: Rect? var scalingMode: AspectRatio? - var xAligningMode: Align? - var yAligningMode: Align? + var xAligningMode: Align = .mid + var yAligningMode: Align = .mid } /// Parse an SVG file identified by the specified bundle, name and file extension. @@ -41,11 +41,11 @@ open class SVGParser { return SVGParser(text).parse() } - let availableStyleAttributes = ["stroke", "stroke-width", "stroke-opacity", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", + let availableStyleAttributes = ["stroke", "stroke-width", "stroke-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "fill", "text-anchor", "clip-path", "fill-opacity", "stop-color", "stop-opacity", - "font-family", "font-size", "font-weight", - "opacity", "visibility"] + "font-family", "font-size", + "font-weight", "opacity", "color", "visibility"] fileprivate let xmlString: String fileprivate let initialPosition: Transform @@ -139,8 +139,8 @@ open class SVGParser { if scalingMode === AspectRatio.slice { // setup new clipping to slice extra bits let newSize = AspectRatio.meet.fit(size: svgSize, into: viewBox) - let newX = viewBox.x + viewBox.w / 2 - newSize.w / 2 - let newY = viewBox.y + viewBox.h / 2 - newSize.h / 2 + let newX = viewBox.x + params.xAligningMode.align(outer: viewBox.w, inner: newSize.w) + let newY = viewBox.y + params.yAligningMode.align(outer: viewBox.h, inner: newSize.h) node.clip = Rect(x: newX, y: newY, w: newSize.w, h: newSize.h) } @@ -228,7 +228,7 @@ open class SVGParser { } } - fileprivate func parseDefinitions(_ defs: XMLIndexer) { + fileprivate func parseDefinitions(_ defs: XMLIndexer, groupStyle: [String: String] = [:]) { for child in defs.children { guard let id = child.element?.allAttributes["id"]?.text else { continue @@ -266,41 +266,42 @@ open class SVGParser { return .none } + 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), clip: getClipPath(styleAttributes), tag: getTag(element)) + return Shape(form: path, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) } case "line": if let line = parseLine(node) { - return Shape(form: line, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) + return Shape(form: line, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) } case "rect": if let rect = parseRect(node) { - return Shape(form: rect, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) + return Shape(form: rect, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) } case "circle": if let circle = parseCircle(node) { - return Shape(form: circle, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) + return Shape(form: circle, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) } case "ellipse": if let ellipse = parseEllipse(node) { - return Shape(form: ellipse, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) + return Shape(form: ellipse, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) } case "polygon": if let polygon = parsePolygon(node) { - return Shape(form: polygon, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) + return Shape(form: polygon, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) } case "polyline": if let polyline = parsePolyline(node) { - return Shape(form: polyline, fill: getFillColor(styleAttributes), stroke: getStroke(styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) + return Shape(form: polyline, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes), tag: getTag(element)) } case "image": return parseImage(node, opacity: getOpacity(styleAttributes), pos: position, clip: getClipPath(styleAttributes)) case "text": - return parseText(node, textAnchor: getTextAnchor(styleAttributes), fill: getFillColor(styleAttributes), - stroke: getStroke(styleAttributes), opacity: getOpacity(styleAttributes), fontName: getFontName(styleAttributes), fontSize: getFontSize(styleAttributes), fontWeight: getFontWeight(styleAttributes), pos: position) + return parseText(node, textAnchor: getTextAnchor(styleAttributes), fill: getFillColor(styleAttributes, groupStyle: styleAttributes), + stroke: getStroke(styleAttributes, groupStyle: styleAttributes), opacity: getOpacity(styleAttributes), fontName: getFontName(styleAttributes), fontSize: getFontSize(styleAttributes), fontWeight: getFontWeight(styleAttributes), pos: position) case "use": return parseUse(node, groupStyle: styleAttributes, place: position) case "mask": @@ -317,11 +318,12 @@ open class SVGParser { guard let element = fill.element else { return .none } + let style = getStyleAttributes([:], element: element) switch element.name { case "linearGradient": - return parseLinearGradient(fill) + return parseLinearGradient(fill, groupStyle: style) case "radialGradient": - return parseRadialGradient(fill) + return parseRadialGradient(fill, groupStyle: style) default: return .none } @@ -385,6 +387,8 @@ open class SVGParser { guard let matcher = SVGParserRegexHelper.getTransformAttributeMatcher() else { return transform } + + let attributes = attributes.replacingOccurrences(of: "\n", with: "") var finalTransform = transform let fullRange = NSRange(location: 0, length: attributes.count) @@ -522,7 +526,7 @@ open class SVGParser { } self.availableStyleAttributes.forEach { availableAttribute in - if let styleAttribute = element.allAttributes[availableAttribute]?.text { + if let styleAttribute = element.allAttributes[availableAttribute]?.text, styleAttribute != "inherit" { styleAttributes.updateValue(styleAttribute, forKey: availableAttribute) } } @@ -531,6 +535,7 @@ open class SVGParser { } fileprivate func createColor(_ hexString: String, opacity: Double = 1) -> Color { + let opacity = min(max(opacity, 0), 1) var cleanedHexString = hexString if hexString.hasPrefix("#") { cleanedHexString = hexString.replacingOccurrences(of: "#", with: "") @@ -549,13 +554,16 @@ open class SVGParser { return Color.rgba(r: Int(red), g: Int(green), b: Int(blue), a: opacity) } - fileprivate func getFillColor(_ styleParts: [String: String]) -> Fill? { - guard let fillColor = styleParts["fill"] else { + fileprivate func getFillColor(_ styleParts: [String: String], groupStyle: [String: String] = [:]) -> Fill? { + guard var fillColor = styleParts["fill"] else { return Color.black } if fillColor == "none" || fillColor == "transparent" { return .none } + if fillColor == "currentColor", let currentColor = groupStyle["color"] { + fillColor = currentColor + } var opacity: Double = 1 var hasFillOpacity = false if let fillOpacity = styleParts["fill-opacity"] { @@ -581,13 +589,16 @@ open class SVGParser { } } - fileprivate func getStroke(_ styleParts: [String: String]) -> Stroke? { - guard let strokeColor = styleParts["stroke"] else { + fileprivate func getStroke(_ styleParts: [String: String], groupStyle: [String: String] = [:]) -> Stroke? { + guard var strokeColor = styleParts["stroke"] else { return .none } if strokeColor == "none" { return .none } + if strokeColor == "currentColor", let currentColor = groupStyle["color"] { + strokeColor = currentColor + } var opacity: Double = 1 if let strokeOpacity = styleParts["stroke-opacity"] { opacity = Double(strokeOpacity.replacingOccurrences(of: " ", with: "")) ?? 1 @@ -614,7 +625,8 @@ open class SVGParser { width: getStrokeWidth(styleParts), cap: getStrokeCap(styleParts), join: getStrokeJoin(styleParts), - dashes: getStrokeDashes(styleParts)) + dashes: getStrokeDashes(styleParts), + offset: getStrokeOffset(styleParts)) } return .none @@ -625,8 +637,8 @@ open class SVGParser { let characterSet = NSCharacterSet.decimalDigits.union(NSCharacterSet.punctuationCharacters).inverted let digitsArray = strokeWidth.components(separatedBy: characterSet) let digits = digitsArray.joined() - if let value = NumberFormatter().number(from: digits) { - return value.doubleValue + if let value = Double(digits) { + return value } } return 1 @@ -636,6 +648,8 @@ open class SVGParser { var cap = LineCap.butt if let strokeCap = styleParts["stroke-linecap"] { switch strokeCap { + case "round": + cap = .round case "butt": cap = .butt case "square": @@ -651,6 +665,8 @@ open class SVGParser { var join = LineJoin.miter if let strokeJoin = styleParts["stroke-linejoin"] { switch strokeJoin { + case "round": + join = .round case "miter": join = .miter case "bevel": @@ -670,13 +686,20 @@ open class SVGParser { characterSet.insert(",") let separatedValues = strokeDashes.components(separatedBy: characterSet) separatedValues.forEach { value in - if let doubleValue = Double(value) { + if let doubleValue = doubleFromString(value) { dashes.append(doubleValue) } } } return dashes } + + fileprivate func getStrokeOffset(_ styleParts: [String: String]) -> Double { + if let strokeOffset = styleParts["stroke-dashoffset"], let offset = Double(strokeOffset) { // TODO use doubleFromString once it's merged + return offset + } + return 0 + } fileprivate func getTag(_ element: SWXMLHash.XMLElement) -> [String] { let id = element.allAttributes["id"]?.text @@ -1020,7 +1043,7 @@ open class SVGParser { return maskShape } - fileprivate func parseLinearGradient(_ gradient: XMLIndexer) -> Fill? { + fileprivate func parseLinearGradient(_ gradient: XMLIndexer, groupStyle: [String: String] = [:]) -> Fill? { guard let element = gradient.element else { return .none } @@ -1036,7 +1059,7 @@ open class SVGParser { if gradient.children.isEmpty { stopsArray = parentGradient?.stops } else { - stopsArray = parseStops(gradient.children) + stopsArray = parseStops(gradient.children, groupStyle: groupStyle) } guard let stops = stopsArray else { @@ -1081,7 +1104,7 @@ open class SVGParser { return LinearGradient(x1: x1, y1: y1, x2: x2, y2: y2, userSpace: userSpace, stops: stops) } - fileprivate func parseRadialGradient(_ gradient: XMLIndexer) -> Fill? { + fileprivate func parseRadialGradient(_ gradient: XMLIndexer, groupStyle: [String: String] = [:]) -> Fill? { guard let element = gradient.element else { return .none } @@ -1097,7 +1120,7 @@ open class SVGParser { if gradient.children.isEmpty { stopsArray = parentGradient?.stops } else { - stopsArray = parseStops(gradient.children) + stopsArray = parseStops(gradient.children, groupStyle: groupStyle) } guard let stops = stopsArray else { @@ -1143,36 +1166,34 @@ open class SVGParser { return RadialGradient(cx: cx, cy: cy, fx: fx, fy: fy, r: r, userSpace: userSpace, stops: stops) } - fileprivate func parseStops(_ stops: [XMLIndexer]) -> [Stop] { + fileprivate func parseStops(_ stops: [XMLIndexer], groupStyle: [String: String] = [:]) -> [Stop] { var result = [Stop]() stops.forEach { stopXML in - if let stop = parseStop(stopXML) { + if let stop = parseStop(stopXML, groupStyle: groupStyle) { result.append(stop) } } return result } - fileprivate func parseStop(_ stop: XMLIndexer) -> Stop? { + fileprivate func parseStop(_ stop: XMLIndexer, groupStyle: [String: String] = [:]) -> Stop? { guard let element = stop.element else { return .none } - guard var offset = getDoubleValueFromPercentage(element, attribute: "offset") else { + guard let offset = getDoubleValueFromPercentage(element, attribute: "offset") else { return .none } - if offset < 0 { - offset = 0 - } else if offset > 1 { - offset = 1 - } var opacity: Double = 1 if let stopOpacity = getStyleAttributes([:], element: element)["stop-opacity"], let doubleValue = Double(stopOpacity) { opacity = doubleValue } var color = Color.black - if let stopColor = getStyleAttributes([:], element: element)["stop-color"] { + if var stopColor = getStyleAttributes([:], element: element)["stop-color"] { + if stopColor == "currentColor", let currentColor = groupStyle["color"] { + stopColor = currentColor + } if let defaultColor = SVGConstants.colorList[stopColor] { color = Color(val: defaultColor).with(a: opacity) } else { @@ -1192,10 +1213,31 @@ open class SVGParser { } fileprivate func getDoubleValue(_ element: SWXMLHash.XMLElement, attribute: String) -> Double? { - guard let attributeValue = element.allAttributes[attribute]?.text, let doubleValue = Double(attributeValue) else { + guard let attributeValue = element.allAttributes[attribute]?.text else { return .none } - return doubleValue + return doubleFromString(attributeValue) + } + + fileprivate func doubleFromString(_ string: String) -> Double? { + if let doubleValue = Double(string) { + return doubleValue + } + guard let matcher = SVGParserRegexHelper.getUnitsIdenitifierMatcher() else { return .none } + let fullRange = NSRange(location: 0, length: string.count) + if let match = matcher.firstMatch(in: string, options: .reportCompletion, range: fullRange) { + + let unitString = (string as NSString).substring(with: match.range(at: 1)) + let numberString = String(string.dropLast(unitString.count)) + switch unitString { + case "px" : + return Double(numberString) + default: + print("SVG parsing error. Unit \(unitString) not supported") + return Double(numberString) + } + } + return .none } fileprivate func getDoubleValueFromPercentage(_ element: SWXMLHash.XMLElement, attribute: String) -> Double? { diff --git a/Source/svg/SVGParserRegexHelper.swift b/Source/svg/SVGParserRegexHelper.swift index d88d8bf0..72e1e3c0 100644 --- a/Source/svg/SVGParserRegexHelper.swift +++ b/Source/svg/SVGParserRegexHelper.swift @@ -6,11 +6,13 @@ class SVGParserRegexHelper { fileprivate static let transformPattern = "\\-?\\d+\\.?\\d*e?\\-?\\d*" fileprivate static let textElementPattern = "((?s:.*))<\\/text>" fileprivate static let maskIdenitifierPattern = "url\\(#((?s:.*))\\)" + fileprivate static let unitsIdenitifierPattern = "([a-zA-Z]+)$" fileprivate static var transformMatcher: NSRegularExpression? fileprivate static var transformAttributeMatcher: NSRegularExpression? fileprivate static var textElementMatcher: NSRegularExpression? fileprivate static var maskIdenitifierMatcher: NSRegularExpression? + fileprivate static var unitsMatcher: NSRegularExpression? class func getTransformAttributeMatcher() -> NSRegularExpression? { if self.transformAttributeMatcher == nil { @@ -55,5 +57,16 @@ class SVGParserRegexHelper { } return self.maskIdenitifierMatcher } + + class func getUnitsIdenitifierMatcher() -> NSRegularExpression? { + if unitsMatcher == nil { + do { + unitsMatcher = try NSRegularExpression(pattern: unitsIdenitifierPattern, options: .caseInsensitive) + } catch { + + } + } + return unitsMatcher + } } diff --git a/Source/svg/SVGSerializer.swift b/Source/svg/SVGSerializer.swift index 6856a59d..5b5555a0 100644 --- a/Source/svg/SVGSerializer.swift +++ b/Source/svg/SVGSerializer.swift @@ -247,6 +247,15 @@ open class SVGSerializer { result += " stroke-linejoin=\"\(strokeJoin)\"" } } + if let strokeDashes = stroke?.dashes, strokeDashes.count > 0 { + let dashes = strokeDashes.map{ String($0) }.joined(separator: ",") + result += " stroke-dasharray=\"\(dashes)\"" + } + if let strokeOffset = stroke?.offset { + if strokeOffset != 0 { + result += " stroke-dashoffset=\"\(strokeOffset)\"" + } + } return result } diff --git a/Source/utils/SvgContentLayout.swift b/Source/utils/SvgContentLayout.swift index f9a5cf23..960502c1 100644 --- a/Source/utils/SvgContentLayout.swift +++ b/Source/utils/SvgContentLayout.swift @@ -11,10 +11,10 @@ open class SvgContentLayout: ContentLayout { public let xAligningMode: Align public let yAligningMode: Align - public init(scalingMode: AspectRatio, xAligningMode: Align? = Align.min, yAligningMode: Align? = Align.min) { + public init(scalingMode: AspectRatio, xAligningMode: Align = Align.min, yAligningMode: Align = Align.min) { self.scalingMode = scalingMode - self.xAligningMode = xAligningMode ?? Align.min - self.yAligningMode = yAligningMode ?? Align.min + self.xAligningMode = xAligningMode + self.yAligningMode = yAligningMode } public static var standard: ContentLayout {