From 22c77a3cf1dabf213953477f3853d880bc30e3f4 Mon Sep 17 00:00:00 2001 From: Alisa Mylnikova Date: Thu, 29 Mar 2018 15:40:08 +0700 Subject: [PATCH] Add helper for aspectFill/Fit transformations --- Source/svg/SVGParser.swift | 103 +++++----------------------- Source/utils/TransformHelper.swift | 104 +++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 87 deletions(-) create mode 100644 Source/utils/TransformHelper.swift diff --git a/Source/svg/SVGParser.swift b/Source/svg/SVGParser.swift index 89ef79ad..63212efe 100644 --- a/Source/svg/SVGParser.swift +++ b/Source/svg/SVGParser.swift @@ -9,40 +9,28 @@ import CoreGraphics /// This class used to parse SVG file and build corresponding Macaw scene /// -public enum AlignMode : String { - case max - case mid - case min -} - -public enum ScaleMode : String { - case aspectFit = "meet" - case aspectFill = "slice" - case scaleToFill = "none" -} - open class SVGParser { /// 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") throws -> Node { + open class func parse(bundle: Bundle, path: String, ofType: String = "svg", transformHelper: TransformHelperProtocol = TransformHelper()) throws -> Node { guard let fullPath = bundle.path(forResource: path, ofType: ofType) else { throw SVGParserError.noSuchFile(path: "\(path).\(ofType)") } let text = try String(contentsOfFile: fullPath, encoding: String.Encoding.utf8) - return try SVGParser.parse(text: text) + return try SVGParser.parse(text: text, transformHelper: transformHelper) } /// 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") throws -> Node { - return try SVGParser.parse(bundle: Bundle.main, path: path, ofType: ofType) + open class func parse(path: String, ofType: String = "svg", transformHelper: TransformHelperProtocol = TransformHelper()) throws -> Node { + return try SVGParser.parse(bundle: Bundle.main, path: path, ofType: ofType, transformHelper: transformHelper) } /// Parse the specified content of an SVG file. /// - returns: Root node of the corresponding Macaw scene. - open class func parse(text: String) throws -> Node { - return SVGParser(text).parse() + open class func parse(text: String, transformHelper: TransformHelperProtocol = TransformHelper()) throws -> Node { + return SVGParser(text, transformHelper: transformHelper).parse() } let availableStyleAttributes = ["stroke", "stroke-width", "stroke-opacity", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", @@ -59,6 +47,7 @@ open class SVGParser { fileprivate var scalingMode: ScaleMode? fileprivate var xAligningMode: AlignMode? fileprivate var yAligningMode: AlignMode? + fileprivate var transformHelper: TransformHelperProtocol! fileprivate var nodes = [Node]() fileprivate var defNodes = [String: XMLIndexer]() @@ -79,9 +68,10 @@ open class SVGParser { fileprivate typealias PathCommand = (type: PathCommandType, expression: String, absolute: Bool) - fileprivate init(_ string: String, pos: Transform = Transform()) { + fileprivate init(_ string: String, pos: Transform = Transform(), transformHelper: TransformHelperProtocol = TransformHelper()) { self.xmlString = string self.initialPosition = pos + self.transformHelper = transformHelper } fileprivate func parse() -> Group { @@ -111,82 +101,21 @@ open class SVGParser { guard let viewBox = viewBox else { return } node.clip = viewBox - guard let svgSize = svgSize else { return } guard let scalingMode = scalingMode else { return } + guard let svgSize = svgSize else { return } - let scaleX = svgSize.w / viewBox.w - let scaleY = svgSize.h / viewBox.h - - let widthRatio = svgSize.w / viewBox.w - let heightRatio = svgSize.h / viewBox.h - - var newWidth = svgSize.w - var newHeight = svgSize.h - - switch scalingMode { - case .aspectFit: - if heightRatio < widthRatio { - newWidth = viewBox.w * heightRatio - } else { - newHeight = viewBox.h * widthRatio - } - node.place = Transform.scale( - sx: newWidth / viewBox.w, - sy: newHeight / viewBox.h - ) - case .aspectFill: - if heightRatio > widthRatio { - newWidth = viewBox.w * heightRatio - } else { - newHeight = viewBox.h * widthRatio - } - node.place = Transform.scale( - sx: newWidth / viewBox.w, - sy: newHeight / viewBox.h - ) + if scalingMode == .aspectFill { // setup new clipping to slice extra bits node.clip = svgSize.aspectFit(viewBox) - case .scaleToFill: - node.place = Transform.scale( - sx: Double(scaleX), - sy: Double(scaleY) - ) } + transformHelper.scalingMode = scalingMode + transformHelper.xAligningMode = xAligningMode + transformHelper.yAligningMode = yAligningMode + node.place = transformHelper.getTransformOf(viewBox, into: Rect(x: 0, y: 0, w: svgSize.w, h: svgSize.h)) + // move to (0, 0) node.place = node.place.move(dx: -viewBox.x, dy: -viewBox.y) - - guard let xAligningMode = xAligningMode else { return } - switch xAligningMode { - case .min: - break - case .mid: - node.place = node.place.move( - dx: (svgSize.w / 2 - newWidth / 2) / (newWidth / viewBox.w), - dy: 0 - ) - case .max: - node.place = node.place.move( - dx: (svgSize.w - newWidth) / (newWidth / viewBox.w), - dy: 0 - ) - } - - guard let yAligningMode = yAligningMode else { return } - switch yAligningMode { - case .min: - break - case .mid: - node.place = node.place.move( - dx: 0, - dy: (svgSize.h / 2 - newHeight / 2) / (newHeight / viewBox.h) - ) - case .max: - node.place = node.place.move( - dx: 0, - dy: (svgSize.h - newHeight) / (newHeight / viewBox.h) - ) - } } fileprivate func parseViewBox(_ element: SWXMLHash.XMLElement) { diff --git a/Source/utils/TransformHelper.swift b/Source/utils/TransformHelper.swift new file mode 100644 index 00000000..2263845a --- /dev/null +++ b/Source/utils/TransformHelper.swift @@ -0,0 +1,104 @@ + +public enum AlignMode : String { + case max + case mid + case min +} + +public enum ScaleMode : String { + case aspectFit = "meet" + case aspectFill = "slice" + case scaleToFill = "none" +} + +public protocol TransformHelperProtocol { + + var scalingMode: ScaleMode? { get set } + var xAligningMode: AlignMode? { get set } + var yAligningMode: AlignMode? { get set } + + func getTransformOf(_ rect: Rect, into rectToFitIn: Rect) -> Transform +} + +open class TransformHelper: TransformHelperProtocol { + + public var scalingMode: ScaleMode? + public var xAligningMode: AlignMode? + public var yAligningMode: AlignMode? + + public init() { } + + public func getTransformOf(_ rect: Rect, into rectToFitIn: Rect) -> Transform { + + var result = Transform() + guard let scalingMode = scalingMode else { return result } + + let widthRatio = rectToFitIn.w / rect.w + let heightRatio = rectToFitIn.h / rect.h + + var newWidth = rectToFitIn.w + var newHeight = rectToFitIn.h + + switch scalingMode { + case .aspectFit: + if heightRatio < widthRatio { + newWidth = rect.w * heightRatio + } else { + newHeight = rect.h * widthRatio + } + result = result.scale( + sx: newWidth / rect.w, + sy: newHeight / rect.h + ) + case .aspectFill: + if heightRatio > widthRatio { + newWidth = rect.w * heightRatio + } else { + newHeight = rect.h * widthRatio + } + result = result.scale( + sx: newWidth / rect.w, + sy: newHeight / rect.h + ) + case .scaleToFill: + result = result.scale( + sx: Double(widthRatio), + sy: Double(heightRatio) + ) + } + + guard let xAligningMode = xAligningMode else { return result } + switch xAligningMode { + case .min: + break + case .mid: + result = result.move( + dx: (rectToFitIn.w / 2 - newWidth / 2) / (newWidth / rect.w), + dy: 0 + ) + case .max: + result = result.move( + dx: (rectToFitIn.w - newWidth) / (newWidth / rect.w), + dy: 0 + ) + } + + guard let yAligningMode = yAligningMode else { return result } + switch yAligningMode { + case .min: + break + case .mid: + result = result.move( + dx: 0, + dy: (rectToFitIn.h / 2 - newHeight / 2) / (newHeight / rect.h) + ) + case .max: + result = result.move( + dx: 0, + dy: (rectToFitIn.h - newHeight) / (newHeight / rect.h) + ) + } + + return result + } +}