Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 CommandLine/CommandLine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Options:
--hide-unsupported-filters hide elements with unsupported filters.

Available keys for --format swift:
--api api of generated code: appkit | uikit
--api api of generated code: swiftui | appkit | uikit

Available keys for --format sfsymbol:
--insets alignment of regular variant: top,left,bottom,right | auto
Expand Down
97 changes: 58 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Options:
--hide-unsupported-filters hide elements with unsupported filters.

Available keys for --format swift:
--api api of generated code: appkit | uikit
--api api of generated code: swiftui | appkit | uikit

Available keys for --format sfsymbol:
--insets alignment of regular variant: top,left,bottom,right | auto
Expand Down Expand Up @@ -196,47 +196,66 @@ $ swiftdraw simple.svg --format swift
```

```swift
extension UIImage {
static func svgSimple(size: CGSize = CGSize(width: 160.0, height: 160.0)) -> UIImage {
let f = UIGraphicsImageRendererFormat.preferred()
f.opaque = false
let scale = CGSize(width: size.width / 160.0, height: size.height / 160.0)
return UIGraphicsImageRenderer(size: size, format: f).image {
drawSimple(in: $0.cgContext, scale: scale)
struct SimpleView: View {

var body: some View {
if isResizable {
canvas
.frame(idealWidth: 160.0, idealHeight: 160.0)
} else {
canvas
.frame(width: 160.0, height: 160.0)
}
}

private static func drawSimple(in ctx: CGContext, scale: CGSize) {
ctx.scaleBy(x: scale.width, y: scale.height)
let rgb = CGColorSpaceCreateDeviceRGB()
let color1 = CGColor(colorSpace: rgb, components: [1, 0.98, 0.98, 1])!
ctx.setFillColor(color1)
ctx.fill(CGRect(x: 0, y: 0, width: 160, height: 160))
let color2 = CGColor(colorSpace: rgb, components: [1, 0.753, 0.796, 1])!
ctx.setFillColor(color2)
let path = CGMutablePath()
path.move(to: CGPoint(x: 80, y: 30))
path.addCurve(to: CGPoint(x: 30, y: 80),
control1: CGPoint(x: 52.39, y: 30),
control2: CGPoint(x: 30, y: 52.39))
path.addCurve(to: CGPoint(x: 80, y: 130),
control1: CGPoint(x: 30, y: 107.61),
control2: CGPoint(x: 52.39, y: 130))
path.addCurve(to: CGPoint(x: 130, y: 80),
control1: CGPoint(x: 107.61, y: 130),
control2: CGPoint(x: 130, y: 107.61))
path.addLine(to: CGPoint(x: 80, y: 80))
path.closeSubpath()
ctx.addPath(path)
ctx.fillPath()
ctx.setLineCap(.butt)
ctx.setLineJoin(.miter)
ctx.setLineWidth(2)
ctx.setMiterLimit(4)
let color3 = CGColor(colorSpace: rgb, components: [0, 0, 0, 1])!
ctx.setStrokeColor(color3)
ctx.addPath(path)
ctx.strokePath()
private var isResizable = false

func resizable() -> Self {
var copy = self
copy.isResizable = true
return copy
}

var canvas: some View {
Canvas(
opaque: false,
colorMode: .linear,
rendersAsynchronously: false
) { context, size in
let scale = CGSize(width: size.width / 160.0, height: size.height / 160.0)
context.withCGContext { ctx in
ctx.scaleBy(x: scale.width, y: scale.height)
let rgb = CGColorSpaceCreateDeviceRGB()
let color1 = CGColor(colorSpace: rgb, components: [1, 0.98, 0.98, 1])!
ctx.setFillColor(color1)
ctx.fill(CGRect(x: 0, y: 0, width: 160, height: 160))
let color2 = CGColor(colorSpace: rgb, components: [1, 0.753, 0.796, 1])!
ctx.setFillColor(color2)
let path = CGMutablePath()
path.move(to: CGPoint(x: 80, y: 30))
path.addCurve(to: CGPoint(x: 30, y: 80),
control1: CGPoint(x: 52.39, y: 30),
control2: CGPoint(x: 30, y: 52.39))
path.addCurve(to: CGPoint(x: 80, y: 130),
control1: CGPoint(x: 30, y: 107.61),
control2: CGPoint(x: 52.39, y: 130))
path.addCurve(to: CGPoint(x: 130, y: 80),
control1: CGPoint(x: 107.61, y: 130),
control2: CGPoint(x: 130, y: 107.61))
path.addLine(to: CGPoint(x: 80, y: 80))
path.closeSubpath()
ctx.addPath(path)
ctx.fillPath()
ctx.setLineCap(.butt)
ctx.setLineJoin(.miter)
ctx.setLineWidth(2)
ctx.setMiterLimit(4)
let color3 = CGColor(colorSpace: rgb, components: [0, 0, 0, 1])!
ctx.setStrokeColor(color3)
ctx.addPath(path)
ctx.strokePath()
}
}
}
}
```
Expand Down
3 changes: 2 additions & 1 deletion SwiftDraw/Sources/CommandLine/CommandLine+Process.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,11 @@ public extension CommandLine {
}

static func makeTextAPI(for api: CommandLine.API?) -> CGTextRenderer.API {
guard let api = api else { return .uiKit }
guard let api = api else { return .swiftUI}
switch api {
case .appkit: return .appKit
case .uikit: return .uiKit
case .swiftui: return .swiftUI
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ extension CommandLine {
public enum API: String {
case appkit
case uikit
case swiftui
}

public enum Size: Equatable {
Expand Down
71 changes: 67 additions & 4 deletions SwiftDraw/Sources/Renderer/Renderer.CGText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ public final class CGTextRenderer: Renderer {
public enum API {
case uiKit
case appKit
case swiftUI
}

init(api: API,
Expand Down Expand Up @@ -591,6 +592,42 @@ public final class CGTextRenderer: Renderer {
""")
}

func makeSwiftUI() -> String {
"""
import SwiftUI

struct \(name)View: View {

var body: some View {
if isResizable {
canvas
.frame(idealWidth: \(commandSize.width), idealHeight: \(commandSize.width))
} else {
canvas
.frame(width: \(commandSize.width), height: \(commandSize.width))
}
}

private var isResizable = false

func resizable() -> Self {
var copy = self
copy.isResizable = true
return copy
}

var canvas: some View {
Canvas(
opaque: false,
colorMode: .linear,
rendersAsynchronously: false
) { context, size in
let scale = CGSize(width: size.width / \(commandSize.width), height: size.height / \(commandSize.height))
context.withCGContext { ctx in

"""
}

func makeUIKit() -> String {
"""
import CoreGraphics
Expand Down Expand Up @@ -632,27 +669,53 @@ public final class CGTextRenderer: Renderer {
"""
}

func makeTemplate() -> String {
func makeTemplateStart() -> String {
switch api {
case .appKit:
return makeAppKit()
case .uiKit:
return makeUIKit()
case .swiftUI:
return makeSwiftUI()
}
}

func makeTemplateEnd() -> String {
switch api {
case .appKit, .uiKit:
return "\n }\n}"
case .swiftUI:
return """

}
}
}
}
"""
}
}

func makeIndent() -> String {
switch api {
case .appKit, .uiKit:
return String(repeating: " ", count: 4)
case .swiftUI:
return String(repeating: " ", count: 8)
}
}

func makeText() -> String {
var template = makeTemplate()
var template = makeTemplateStart()

lines.insert("ctx.scaleBy(x: scale.width, y: scale.height)", at: 0)
if !patterns.isEmpty {
lines.insert("let baseCTM = ctx.ctm", at: 0)
}

let indent = String(repeating: " ", count: 4)
let indent = makeIndent()
let lines = self.lines.map { "\(indent)\($0)" }
template.append(lines.joined(separator: "\n"))
template.append("\n }\n}")
template.append(makeTemplateEnd())
return template
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ final class CommandLineConfigurationTests: XCTestCase {
}

func testAPIConversion() {
XCTAssertEqual(CommandLine.makeTextAPI(for: nil), .uiKit)
XCTAssertEqual(CommandLine.makeTextAPI(for: nil), .swiftUI)
XCTAssertEqual(CommandLine.makeTextAPI(for: .appkit), .appKit)
XCTAssertEqual(CommandLine.makeTextAPI(for: .uikit), .uiKit)
}
Expand Down
74 changes: 72 additions & 2 deletions SwiftDraw/Tests/Renderer/Renderer.CGTextTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,76 @@ final class RendererCGTextTests: XCTestCase {
)
}

func testSwiftUICode() throws {
let code = try CGTextRenderer.render(svgNamed: "lines.svg", api: .swiftUI)
XCTAssertEqual(
code,
"""
import SwiftUI

struct ImageView: View {

var body: some View {
if isResizable {
canvas
.frame(idealWidth: 100.0, idealHeight: 100.0)
} else {
canvas
.frame(width: 100.0, height: 100.0)
}
}

private var isResizable = false

func resizable() -> Self {
var copy = self
copy.isResizable = true
return copy
}

var canvas: some View {
Canvas(
opaque: false,
colorMode: .linear,
rendersAsynchronously: false
) { context, size in
let scale = CGSize(width: size.width / 100.0, height: size.height / 100.0)
context.withCGContext { ctx in
ctx.scaleBy(x: scale.width, y: scale.height)
let rgb = CGColorSpaceCreateDeviceRGB()
let color1 = CGColor(colorSpace: rgb, components: [0, 0, 0, 1])!
ctx.setFillColor(color1)
let path = CGMutablePath()
path.addLines(between: [
CGPoint(x: 0, y: 0),
CGPoint(x: 100, y: 100)
])
ctx.addPath(path)
ctx.fillPath()
ctx.setLineCap(.butt)
ctx.setLineJoin(.miter)
ctx.setLineWidth(1)
ctx.setMiterLimit(4)
ctx.setStrokeColor(color1)
ctx.addPath(path)
ctx.strokePath()
let path1 = CGMutablePath()
path1.addLines(between: [
CGPoint(x: 100, y: 0),
CGPoint(x: 0, y: 100)
])
ctx.addPath(path1)
ctx.fillPath()
ctx.addPath(path1)
ctx.strokePath()
}
}
}
}
"""
)
}

func testGradientAppleCode() throws {
let code = try CGTextRenderer.render(svgNamed: "gradient-apple.svg")
XCTAssertEqual(
Expand Down Expand Up @@ -479,10 +549,10 @@ final class RendererCGTextTests: XCTestCase {

private extension CGTextRenderer {

static func render(svgNamed name: String, in bundle: Bundle = .test, precision: Int = 2) throws -> String {
static func render(svgNamed name: String, in bundle: Bundle = .test, api: API = .uiKit, precision: Int = 2) throws -> String {
let url = try bundle.url(forResource: name)
let data = try Data(contentsOf: url)
return try render(data: data, options: .default, api: .uiKit, precision: precision)
return try render(data: data, options: .default, api: api, precision: precision)
}

}
Loading