1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-09-21 09:59:10 +03:00

Add helper for aspectFill/Fit transformations

This commit is contained in:
Alisa Mylnikova 2018-03-29 15:40:08 +07:00
parent 858dfddd38
commit 22c77a3cf1
2 changed files with 120 additions and 87 deletions

View File

@ -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) {

View File

@ -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
}
}