mirror of
https://github.com/exyte/Macaw.git
synced 2024-09-21 01:47:44 +03:00
Merge branch 'master' into task/visibilityAttribute
# Conflicts: # Source/svg/SVGParser.swift
This commit is contained in:
commit
26a3e7bee1
@ -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 = "<group>"; };
|
||||
57FCD27D1D76EA4600CC0FB6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
602C561C2081C984003AD452 /* rounded.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = rounded.svg; sourceTree = "<group>"; };
|
||||
5BAA56A7207C73FF0055BC5B /* SvgContentLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SvgContentLayout.swift; sourceTree = "<group>"; };
|
||||
5BAEA9C8206CEAA20049AAAE /* viewBox.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = viewBox.svg; sourceTree = "<group>"; };
|
||||
5BAEA9CA206CEB7D0049AAAE /* viewBox.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = viewBox.reference; sourceTree = "<group>"; };
|
||||
@ -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 */,
|
||||
|
7
MacawTests/svg/rounded.svg
Normal file
7
MacawTests/svg/rounded.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="30px" height="30px" viewBox="0 0 30 30" enable-background="new 0 0 30 30" xml:space="preserve">
|
||||
<polyline fill="none" stroke="#000000" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
24.27,6.35 12.54,23.65 5.73,16.59 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 551 B |
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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) })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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? {
|
||||
|
@ -6,11 +6,13 @@ class SVGParserRegexHelper {
|
||||
fileprivate static let transformPattern = "\\-?\\d+\\.?\\d*e?\\-?\\d*"
|
||||
fileprivate static let textElementPattern = "<text.*?>((?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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user