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

Better Align implementation

This commit is contained in:
Alisa Mylnikova 2018-04-03 13:17:43 +07:00
parent 26ee61a2c8
commit 49e16e9f52
8 changed files with 164 additions and 188 deletions

View File

@ -1,5 +1,44 @@
public enum Align {
case min
case mid
case max
open class Align {
public static let min: Align = MinAlign()
public static let mid: Align = MidAlign()
public static let max: Align = MaxAlign()
open func align(x: Double, y: Double) -> Double {
return 0
}
open func align(x: CGFloat, y: CGFloat) -> CGFloat {
return CGFloat(align(x: x.doubleValue, y: y.doubleValue))
}
open func align(x: Double) -> Double {
return align(x: x, y: 0)
}
open func align(x: CGFloat) -> CGFloat {
return CGFloat(align(x: x.doubleValue))
}
}
private class MinAlign : Align {
override func align(x: Double, y: Double) -> Double {
return 0
}
}
private class MidAlign : Align {
override func align(x: Double, y: Double) -> Double {
return x / 2 - y / 2
}
}
private class MaxAlign : Align {
override func align(x: Double, y: Double) -> Double {
return x - y
}
}

View File

@ -121,16 +121,7 @@ open class Text: Node {
NSAttributedStringKey.font: font
]
let textSize = NSString(string: text).size(withAttributes: textAttributes)
var alignmentOffset = 0.0
switch align {
case .mid:
alignmentOffset = (textSize.width / 2).doubleValue
case .max:
alignmentOffset = textSize.width.doubleValue
default:
break
}
return -alignmentOffset
return -align.align(x: textSize.width.doubleValue)
}
}

View File

@ -115,8 +115,6 @@ class ImageRenderer: NodeRenderer {
let srcAR = size.width / size.height
var resultW = w
var resultH = h
var destX = CGFloat(0)
var destY = CGFloat(0)
if destAR < srcAR {
// fill all available width and scale height
resultH = size.height * w / size.width
@ -124,24 +122,8 @@ class ImageRenderer: NodeRenderer {
// fill all available height and scale width
resultW = size.width * h / size.height
}
let xalign = image.xAlign
switch xalign {
case Align.min:
destX = 0
case Align.mid:
destX = w / 2 - resultW / 2
case Align.max:
destX = w - resultW
}
let yalign = image.yAlign
switch yalign {
case Align.min:
destY = 0
case Align.mid:
destY = h / 2 - resultH / 2
case Align.max:
destY = h - resultH
}
let destX = image.xAlign.align(x: w, y: resultW)
let destY = image.yAlign.align(x: h, y: resultH)
return CGRect(x: destX, y: destY, width: resultW, height: resultH)
}
@ -159,26 +141,12 @@ class ImageRenderer: NodeRenderer {
// fill all available width and scale height
totalH = size.height * w / size.width
totalW = w
switch image.yAlign {
case Align.min:
srcY = 0
case Align.mid:
srcY = -(totalH / 2 - h / 2)
case Align.max:
srcY = -(totalH - h)
}
srcY = image.yAlign.align(x: h, y: totalH)
} else {
// fill all available height and scale width
totalW = size.width * h / size.height
totalH = h
switch image.xAlign {
case Align.min:
srcX = 0
case Align.mid:
srcX = -(totalW / 2 - w / 2)
case Align.max:
srcX = -(totalW - w)
}
srcX = image.xAlign.align(x: w, y: totalW)
}
return CGRect(x: srcX, y: srcY, width: totalW, height: totalH)
}

View File

@ -145,16 +145,7 @@ class TextRenderer: NodeRenderer {
NSAttributedStringKey.font: font
]
let textSize = NSString(string: text.text).size(withAttributes: textAttributes)
var alignmentOffset = CGFloat(0)
switch text.align {
case Align.mid:
alignmentOffset = textSize.width / 2
case Align.max:
alignmentOffset = textSize.width
default:
break
}
return -alignmentOffset
return -text.align.align(x: textSize.width)
}
fileprivate func getTextColor(_ fill: Fill) -> MColor {

View File

@ -10,27 +10,35 @@ import CoreGraphics
///
open class SVGParser {
fileprivate class ViewBoxParams {
var svgSize: Size?
var viewBox: Rect?
var scalingMode: AspectRatio?
var xAligningMode: Align?
var yAligningMode: Align?
}
/// 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", transformHelper: TransformHelperProtocol = TransformHelper()) throws -> Node {
open class func parse(bundle: Bundle, path: String, ofType: String = "svg") 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, transformHelper: transformHelper)
return try 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", transformHelper: TransformHelperProtocol = TransformHelper()) throws -> Node {
return try SVGParser.parse(bundle: Bundle.main, path: path, ofType: ofType, transformHelper: transformHelper)
open class func parse(path: String, ofType: String = "svg") throws -> Node {
return try 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, transformHelper: TransformHelperProtocol = TransformHelper()) throws -> Node {
return SVGParser(text, transformHelper: transformHelper).parse()
open class func parse(text: String) throws -> Node {
return SVGParser(text).parse()
}
let availableStyleAttributes = ["stroke", "stroke-width", "stroke-opacity", "stroke-dasharray", "stroke-linecap", "stroke-linejoin",
@ -41,13 +49,6 @@ open class SVGParser {
fileprivate let xmlString: String
fileprivate let initialPosition: Transform
fileprivate var svgSize: Size?
fileprivate var viewBox: Rect?
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]()
@ -68,32 +69,36 @@ open class SVGParser {
fileprivate typealias PathCommand = (type: PathCommandType, expression: String, absolute: Bool)
fileprivate init(_ string: String, pos: Transform = Transform(), transformHelper: TransformHelperProtocol = TransformHelper()) {
fileprivate init(_ string: String, pos: Transform = Transform()) {
self.xmlString = string
self.initialPosition = pos
self.transformHelper = transformHelper
}
fileprivate func parse() -> Group {
let parsedXml = SWXMLHash.parse(xmlString)
prepareSvg(parsedXml.children)
var viewBoxParams: ViewBoxParams?
for child in parsedXml.children {
if let element = child.element {
if element.name == "svg" {
viewBoxParams = parseViewBox(element)
prepareSvg(child.children)
break
}
}
}
parseSvg(parsedXml.children)
let group = Group(contents: self.nodes, place: initialPosition)
addViewBoxClip(toNode: group)
if let viewBoxParams = viewBoxParams {
addViewBoxClip(toNode: group, viewBoxParams: viewBoxParams)
}
return group
}
fileprivate func prepareSvg(_ children: [XMLIndexer]) {
children.forEach { child in
if let element = child.element {
if element.name == "svg" {
parseViewBox(element)
prepareSvg(child.children)
} else {
prepareSvg(child)
}
}
prepareSvg(child)
}
}
@ -123,57 +128,59 @@ open class SVGParser {
}
}
fileprivate func addViewBoxClip(toNode node: Node) {
fileprivate func addViewBoxClip(toNode node: Node, viewBoxParams params: ViewBoxParams) {
guard let viewBox = viewBox else { return }
guard let viewBox = params.viewBox else { return }
node.clip = viewBox
guard let scalingMode = scalingMode else { return }
guard let svgSize = svgSize else { return }
guard let scalingMode = params.scalingMode else { return }
guard let svgSize = params.svgSize else { return }
if scalingMode == .aspectFill {
if scalingMode == .slice {
// setup new clipping to slice extra bits
node.clip = svgSize.aspectFit(viewBox)
}
transformHelper.scalingMode = scalingMode
transformHelper.xAligningMode = xAligningMode
transformHelper.yAligningMode = yAligningMode
let transformHelper = TransformHelper(scalingMode: scalingMode, xAligningMode: params.xAligningMode, yAligningMode: params.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)
}
fileprivate func parseViewBox(_ element: SWXMLHash.XMLElement) {
fileprivate func parseViewBox(_ element: SWXMLHash.XMLElement) -> ViewBoxParams {
let params = ViewBoxParams()
if let w = getDoubleValue(element, attribute: "width"), let h = getDoubleValue(element, attribute: "height") {
svgSize = Size(w: w, h: h)
params.svgSize = Size(w: w, h: h)
}
if let viewBoxString = element.allAttributes["viewBox"]?.text {
let nums = viewBoxString.components(separatedBy: .whitespaces).map{ Double($0) }
if nums.count == 4, let x = nums[0], let y = nums[1], let w = nums[2], let h = nums[3] {
viewBox = Rect(x: x, y: y, w: w, h: h)
params.viewBox = Rect(x: x, y: y, w: w, h: h)
}
}
if let contentModeString = element.allAttributes["preserveAspectRatio"]?.text {
let strings = contentModeString.components(separatedBy: CharacterSet(charactersIn: " "))
if strings.count == 1 { // none
scalingMode = ScaleMode(rawValue: strings[0])
return
params.scalingMode = parseAspectRatio(strings[0])
return params
}
guard strings.count == 2 else { return }
guard strings.count == 2 else { return params }
let alignString = strings[0]
var xAlign = alignString.prefix(4).lowercased()
xAlign.remove(at: xAlign.startIndex)
xAligningMode = AlignMode(rawValue: xAlign)
params.xAligningMode = parseAlign(xAlign)
var yAlign = alignString.suffix(4).lowercased()
yAlign.remove(at: yAlign.startIndex)
yAligningMode = AlignMode(rawValue: yAlign)
params.yAligningMode = parseAlign(yAlign)
scalingMode = ScaleMode(rawValue: strings[1])
params.scalingMode = parseAspectRatio(strings[1])
}
return params
}
fileprivate func parseNode(_ node: XMLIndexer, groupStyle: [String: String] = [:]) -> Node? {
@ -340,6 +347,26 @@ open class SVGParser {
}
return parseTransformationAttribute(transformAttribute)
}
fileprivate func parseAlign(_ string: String) -> Align {
if string == "min" {
return .min
}
if string == "mid" {
return .mid
}
return .max
}
fileprivate func parseAspectRatio(_ string: String) -> AspectRatio {
if string == "meet" {
return .meet
}
if string == "slice" {
return .slice
}
return .none
}
var count = 0

View File

@ -157,16 +157,15 @@ open class SVGSerializer {
result += SVGGenericCloseTag
return result
}
fileprivate func alignToSVG(_ align: Align) -> String {
switch align {
case .mid:
if align === Align.mid {
return " text-anchor=\"middle\" "
case .max:
return " text-anchor=\"end "
default:
return ""
}
if align === Align.max {
return " text-anchor=\"end "
}
return ""
}
fileprivate func baselineToSVG(_ baseline: Baseline) -> String {

View File

@ -56,44 +56,44 @@ open class SVGView: MacawView {
let svgWidth = nodeBounds.width
let svgHeight = nodeBounds.height
let transformHelper = TransformHelper()
transformHelper.scalingMode = .noScaling
transformHelper.xAligningMode = .mid
transformHelper.yAligningMode = .mid
var scalingMode = AspectRatio.none
var xAligningMode = Align.mid
var yAligningMode = Align.mid
switch self.contentMode {
switch contentMode {
case .scaleToFill:
transformHelper.scalingMode = .scaleToFill
scalingMode = .none
case .scaleAspectFill:
transformHelper.scalingMode = .aspectFill
scalingMode = .slice
case .scaleAspectFit:
transformHelper.scalingMode = .aspectFit
scalingMode = .meet
case .center:
break
case .top:
transformHelper.yAligningMode = .min
yAligningMode = .min
case .bottom:
transformHelper.yAligningMode = .max
yAligningMode = .max
case .left:
transformHelper.xAligningMode = .min
xAligningMode = .min
case .right:
transformHelper.xAligningMode = .max
xAligningMode = .max
case .topLeft:
transformHelper.xAligningMode = .min
transformHelper.yAligningMode = .min
xAligningMode = .min
yAligningMode = .min
case .topRight:
transformHelper.xAligningMode = .max
transformHelper.yAligningMode = .min
xAligningMode = .max
yAligningMode = .min
case .bottomLeft:
transformHelper.xAligningMode = .min
transformHelper.yAligningMode = .max
xAligningMode = .min
yAligningMode = .max
case .bottomRight:
transformHelper.xAligningMode = .max
transformHelper.yAligningMode = .max
xAligningMode = .max
yAligningMode = .max
case .redraw:
break
}
let transformHelper = TransformHelper(scalingMode: scalingMode, xAligningMode: xAligningMode, yAligningMode: yAligningMode)
svgNode.place = transformHelper.getTransformOf(Rect(x: 0, y: 0, w: Double(svgWidth), h: Double(svgHeight)), into: Rect(cgRect: viewBounds))
rootNode.contents = [svgNode]

View File

@ -1,33 +1,25 @@
public enum AlignMode : String {
case max
case mid
case min
}
public enum ScaleMode : String {
case aspectFit = "meet"
case aspectFill = "slice"
case scaleToFill = "none"
case noScaling = "noScaling"
}
public protocol TransformHelperProtocol {
var scalingMode: ScaleMode? { get set }
var xAligningMode: AlignMode? { get set }
var yAligningMode: AlignMode? { get set }
static var standard: TransformHelperProtocol { get }
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 let scalingMode: AspectRatio!
public let xAligningMode: Align!
public let yAligningMode: Align!
public init() { }
public init(scalingMode: AspectRatio, xAligningMode: Align? = Align.min, yAligningMode: Align? = Align.min) {
self.scalingMode = scalingMode
self.xAligningMode = xAligningMode
self.yAligningMode = yAligningMode
}
public static var standard: TransformHelperProtocol {
return TransformHelper(scalingMode: .none)
}
public func getTransformOf(_ rect: Rect, into rectToFitIn: Rect) -> Transform {
@ -41,7 +33,7 @@ open class TransformHelper: TransformHelperProtocol {
var newHeight = rectToFitIn.h
switch scalingMode {
case .aspectFit:
case .meet:
if heightRatio < widthRatio {
newWidth = rect.w * heightRatio
} else {
@ -51,7 +43,7 @@ open class TransformHelper: TransformHelperProtocol {
sx: newWidth / rect.w,
sy: newHeight / rect.h
)
case .aspectFill:
case .slice:
if heightRatio > widthRatio {
newWidth = rect.w * heightRatio
} else {
@ -61,47 +53,16 @@ open class TransformHelper: TransformHelperProtocol {
sx: newWidth / rect.w,
sy: newHeight / rect.h
)
case .scaleToFill:
case .none:
result = result.scale(
sx: Double(widthRatio),
sy: Double(heightRatio)
)
case .noScaling:
newWidth = rect.w
newHeight = rect.h
}
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)
)
}
let dx = xAligningMode.align(x: rectToFitIn.w, y: newWidth) / (newWidth / rect.w)
let dy = yAligningMode.align(x: rectToFitIn.h, y: newHeight) / (newHeight / rect.h)
result = result.move(dx: dx, dy: dy)
return result
}