Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add clip, mask, transformedLocus to serializer #453

Merged
merged 2 commits into from
Aug 17, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MacawTests/svg/textBasicTransform.reference
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><g transform="translate(100,100)" ><text dominant-baseline="text-before-edge" fill="black" transform="matrix(0.707106781186548,-0.707106781186547,0.707106781186547,0.707106781186548,0.0,0.0)" >Point</text></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><g transform="translate(100,100)" ><text dominant-baseline="text-before-edge" fill="black" transform="matrix(0.707106781186548,-0.707106781186547,0.707106781186547,0.707106781186548,0,0)" >Point</text></g></g></svg>
2 changes: 1 addition & 1 deletion MacawTests/svg/transform.reference
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><g transform="matrix(2.0,1.0,1.0,1.0,0.0,0.0)" ><rect height="5" x="0" y="0" width="150" fill="blue"/><rect height="50" x="0" y="0" width="5" fill="red"/><rect height="50" x="150" y="0" width="5" fill="black"/><rect height="5" x="0" y="50" width="150" fill="black"/><ellipse cy="25" ry="15" rx="40" cx="75" fill="purple"/></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><g><g transform="matrix(2,1,1,1,0,0)" ><rect height="5" x="0" y="0" width="150" fill="blue"/><rect height="50" x="0" y="0" width="5" fill="red"/><rect height="50" x="150" y="0" width="5" fill="black"/><rect height="5" x="0" y="50" width="150" fill="black"/><ellipse cy="25" ry="15" rx="40" cx="75" fill="purple"/></g></g></svg>
92 changes: 51 additions & 41 deletions Source/svg/SVGSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ open class SVGSerializer {
fileprivate let width: Int?
fileprivate let height: Int?
fileprivate let id: String?
fileprivate let indent: Int

fileprivate init(width: Int?, height: Int?, id: String?) {
self.width = width
self.height = height
self.id = id
self.indent = 0
}

// header and footer
Expand Down Expand Up @@ -50,24 +48,12 @@ open class SVGSerializer {
fileprivate let SVGGenericCloseTag = "/>"

fileprivate let SVGUndefinedTag = "<UNDEFINED "
fileprivate let indentPrefixSymbol = " "
fileprivate let SVGClipPathName = "clipPath"
fileprivate let SVGMaskName = "mask"

fileprivate let SVGEpsilon: Double = 0.00001
fileprivate let SVGDefaultOpacityValueAsAlpha = 1 * 255

fileprivate func indentTextWithOffset(_ text: String, _ offset: Int) -> String {
if self.indent != 0 {
let prefix = String(repeating: indentPrefixSymbol, count: self.indent)
return "\n\(String(repeating: prefix, count: offset))\(text)"
}
return text
}

fileprivate func att(_ a: Double) -> String {
return String(Int(a))
}

fileprivate func tag(_ tag: String, _ args: [String: String]=[:], close: Bool = false) -> String {
let attrs = args.map { "\($0)=\"\($1)\"" }.joined(separator: " ")
let closeTag = close ? " />" : ""
Expand All @@ -76,7 +62,7 @@ open class SVGSerializer {

fileprivate func arcToSVG(_ arc: Arc) -> String {
if arc.shift == 0.0 && abs(arc.extent - .pi * 2.0) < SVGEpsilon {
return tag(SVGEllipseOpenTag, ["cx": att(arc.ellipse.cx), "cy": att(arc.ellipse.cy), "rx": att(arc.ellipse.rx), "ry": att(arc.ellipse.ry)])
return tag(SVGEllipseOpenTag, ["cx": arc.ellipse.cx.toString(), "cy": arc.ellipse.cy.toString(), "rx": arc.ellipse.rx.toString(), "ry": arc.ellipse.ry.toString()])
} else {
let rx = arc.ellipse.rx
let ry = arc.ellipse.ry
Expand Down Expand Up @@ -115,36 +101,36 @@ open class SVGSerializer {
fileprivate func pathToSVG(_ path: Path) -> String {
var d = ""
for segment in path.segments {
d += "\(segment.type) \(segment.data.compactMap { String(Int($0)) }.joined(separator: " "))"
d += "\(segment.type) \(segment.data.compactMap { $0.toString() }.joined(separator: " "))"
}
return tag(SVGPathOpenTag, ["d": d])
}

fileprivate func lineToSVG(_ line: Line) -> String {
return tag(SVGLineOpenTag, ["x1": String(Int(line.x1)), "y1": att(line.y1), "x2": att(line.x2), "y2": att(line.y2)])
return tag(SVGLineOpenTag, ["x1": line.x1.toString(), "y1": line.y1.toString(), "x2": line.x2.toString(), "y2": line.y2.toString()])
}

fileprivate func ellipseToSVG(_ ellipse: Ellipse) -> String {
return tag(SVGEllipseOpenTag, ["cx": att(ellipse.cx), "cy": att(ellipse.cy), "rx": att(ellipse.rx), "ry": att(ellipse.ry)])
return tag(SVGEllipseOpenTag, ["cx": ellipse.cx.toString(), "cy": ellipse.cy.toString(), "rx": ellipse.rx.toString(), "ry": ellipse.ry.toString()])
}

fileprivate func circleToSVG(_ circle: Circle) -> String {
return tag(SVGCircleOpenTag, ["cx": att(circle.cx), "cy": att(circle.cy), "r": att(circle.r)])
return tag(SVGCircleOpenTag, ["cx": circle.cx.toString(), "cy": circle.cy.toString(), "r": circle.r.toString()])
}

fileprivate func roundRectToSVG(_ roundRect: RoundRect) -> String {
return tag(SVGRectOpenTag, ["x": att(roundRect.rect.x), "y": att(roundRect.rect.y), "width": att(roundRect.rect.w), "height": att(roundRect.rect.h), "rx": att(roundRect.rx), "ry": att(roundRect.ry)])
return tag(SVGRectOpenTag, ["x": roundRect.rect.x.toString(), "y": roundRect.rect.y.toString(), "width": roundRect.rect.w.toString(), "height": roundRect.rect.h.toString(), "rx": roundRect.rx.toString(), "ry": roundRect.ry.toString()])
}

fileprivate func rectToSVG(_ rect: Rect) -> String {
return tag(SVGRectOpenTag, ["x": att(rect.x), "y": att(rect.y), "width": att(rect.w), "height": att(rect.h)])
return tag(SVGRectOpenTag, ["x": rect.x.toString(), "y": rect.y.toString(), "width": rect.w.toString(), "height": rect.h.toString()])
}

fileprivate func imageToSVG(_ image: Image) -> String {
var result = tag(SVGImageOpenTag, close: false)
result += idToSVG(image.tag)
result += clipToSVG(image.clip)
result += transformToSVG(image)
result += transformToSVG(image.place)
if image.src.contains("memory://") {
if let data = image.base64encoded(type: Image.ImageRepresentationType.PNG) {
result += " xlink:href=\"data:image/png;base64,\(data)\""
Expand Down Expand Up @@ -192,7 +178,7 @@ open class SVGSerializer {
result += clipToSVG(text.clip)
result += fillToSVG(text.fillVar.value)
result += strokeToSVG(text.strokeVar.value)
result += transformToSVG(text)
result += transformToSVG(text.place)
result += SVGGenericEndTag
result += text.text
result += "</text>"
Expand Down Expand Up @@ -270,14 +256,14 @@ open class SVGSerializer {
return false
}

fileprivate func transformToSVG(_ shape: Node) -> String {
if [shape.place.m11, shape.place.m12, shape.place.m21, shape.place.m22] == [1.0, 0.0, 0.0, 1.0] {
if ([shape.place.dx, shape.place.dy] == [0.0, 0.0]) {
fileprivate func transformToSVG(_ place: Transform) -> String {
if [place.m11, place.m12, place.m21, place.m22] == [1.0, 0.0, 0.0, 1.0] {
if ([place.dx, place.dy] == [0.0, 0.0]) {
return ""
}
return " transform=\"translate(\(Int(shape.place.dx)),\(Int(shape.place.dy)))\" "
return " transform=\"translate(\(place.dx.toString()),\(place.dy.toString()))\" "
}
let matrixArgs = [shape.place.m11, shape.place.m12, shape.place.m21, shape.place.m22, shape.place.dx, shape.place.dy].map { String($0) }.joined(separator: ",")
let matrixArgs = [place.m11, place.m12, place.m21, place.m22, place.dx, place.dy].map { $0.toString() }.joined(separator: ",")
return " transform=\"matrix(\(matrixArgs))\" "
}

Expand All @@ -301,6 +287,8 @@ open class SVGSerializer {
return roundRectToSVG(roundRect)
case let rect as Rect:
return rectToSVG(rect)
case let transformedLocus as TransformedLocus:
return locusToSVG(transformedLocus.locus) + transformToSVG(transformedLocus.transform)
default:
return "\(SVGUndefinedTag) locus:\(locus)"
}
Expand All @@ -319,7 +307,14 @@ open class SVGSerializer {

fileprivate func addClipToDefs(_ clip: Locus) {
clipPathCount += 1
defs += "<clipPath id=\"\(SVGClipPathName)\(clipPathCount)\">" + locusToSVG(clip) + SVGGenericCloseTag + "</clipPath>"
defs += "<\(SVGClipPathName) id=\"\(SVGClipPathName)\(clipPathCount)\">" + locusToSVG(clip) + SVGGenericCloseTag + "</\(SVGClipPathName)>"
}

fileprivate var maskCount: Int = 0

fileprivate func addMaskToDefs(_ mask: Node) {
maskCount += 1
defs += "<\(SVGMaskName) id=\"\(SVGMaskName)\(maskCount)\">" + serialize(node: mask) + SVGGenericCloseTag + "</\(SVGMaskName)>"
}

fileprivate func idToSVG(_ tag: [String]) -> String {
Expand All @@ -337,32 +332,41 @@ open class SVGSerializer {
return " clip-path=\"url(#\(SVGClipPathName)\(clipPathCount))\" "
}

fileprivate func maskToSVG(_ mask: Node?) -> String {
guard let mask = mask else {
return ""
}
addMaskToDefs(mask)
return " mask=\"url(#\(SVGMaskName)\(maskCount))\" "
}

fileprivate func macawShapeToSvgShape (macawShape: Shape) -> String {
let locus = macawShape.formVar.value
var result = locusToSVG(locus)
result += idToSVG(macawShape.tag)
result += clipToSVG(macawShape.clip)
result += maskToSVG(macawShape.mask)
result += fillToSVG(macawShape.fillVar.value)
result += strokeToSVG(macawShape.strokeVar.value)
result += transformToSVG(macawShape)
result += transformToSVG(macawShape.place)
result += SVGGenericCloseTag
return result
}

fileprivate func serialize(node: Node, offset: Int) -> String {
fileprivate func serialize(node: Node) -> String {
if let shape = node as? Shape {
return indentTextWithOffset(macawShapeToSvgShape(macawShape: shape), offset)
return macawShapeToSvgShape(macawShape: shape)
}
if let group = node as? Group {
var result = indentTextWithOffset(SVGGroupOpenTag, offset)
var result = SVGGroupOpenTag
result += idToSVG(group.tag)
result += clipToSVG(group.clip)
result += transformToSVG(group)
result += transformToSVG(group.place)
result += SVGGenericEndTag
for child in group.contentsVar.value {
result += serialize(node: child, offset: offset + 1)
result += serialize(node: child)
}
result += indentTextWithOffset(SVGGroupCloseTag, offset)
result += SVGGroupCloseTag
return result
}
if let image = node as? Image {
Expand All @@ -374,7 +378,7 @@ open class SVGSerializer {
return "SVGUndefinedTag \(node)"
}

fileprivate func serialize(node: Node) -> String {
fileprivate func serializeRootNode(node: Node) -> String {
var optionalSection = ""
if let w = width {
optionalSection += "width=\"\(w)\""
Expand All @@ -386,14 +390,20 @@ open class SVGSerializer {
optionalSection += " id=\"\(i)\""
}
var result = [SVGDefaultHeader, optionalSection, SVGGenericEndTag].joined(separator: " ")
let body = serialize(node: node, offset: 1)
let body = serialize(node: node)
result += getDefs() + body
result += indentTextWithOffset(SVGFooter, 0)
result += SVGFooter
return result
}

open class func serialize(node: Node, width: Int? = nil, height: Int? = nil, id: String? = nil) -> String {
return SVGSerializer(width: width, height: height, id: id).serialize(node: node)
return SVGSerializer(width: width, height: height, id: id).serializeRootNode(node: node)
}

}

extension Double {
func toString() -> String {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about using not that common name for this method, like toString => serialize? I understand it's internal, but still might reflect some general changes in the Macaw codebase (I was thinking to add toString to all model classes).

return abs(self.remainder(dividingBy: 1)) > 0.00001 ? String(self) : String(Int(self.rounded()))
}
}