Skip to content

Commit 9bbc820

Browse files
authored
Merge pull request #71 from swhitty/SVG+Transform
Transform SVG instances
2 parents 172c636 + dbeb76f commit 9bbc820

File tree

11 files changed

+341
-277
lines changed

11 files changed

+341
-277
lines changed

Examples/Sources/GalleryView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ struct GalleryView: View {
5858
ScrollView {
5959
LazyVStack(spacing: 20) {
6060
ForEach(images, id: \.self) { image in
61-
SVGView(svg: image)
61+
SVGView(svg: image.scale(x: 3, y: 1))
6262
.aspectRatio(contentMode: .fit)
6363
.padding([.leading, .trailing], 10)
6464
}

SwiftDraw/CommandLine+Process.swift

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,20 @@ public extension CommandLine {
9595
#if canImport(CoreGraphics)
9696
switch config.format {
9797
case .jpeg:
98-
let insets = try makeImageInsets(for: config.insets)
99-
return try image.jpegData(size: config.size.cgValue, scale: config.scale.cgValue, insets: insets)
98+
return try image
99+
.inset(makeImageInsets(for: config.insets))
100+
.size(config.size.cgValue)
101+
.jpegData(scale: config.scale.cgValue)
100102
case .pdf:
101-
let insets = try makeImageInsets(for: config.insets)
102-
return try image.pdfData(size: config.size.cgValue, insets: insets)
103+
return try image
104+
.inset(makeImageInsets(for: config.insets))
105+
.size(config.size.cgValue)
106+
.pdfData()
103107
case .png:
104-
let insets = try makeImageInsets(for: config.insets)
105-
return try image.pngData(size: config.size.cgValue, scale: config.scale.cgValue, insets: insets)
108+
return try image
109+
.inset(makeImageInsets(for: config.insets))
110+
.size(config.size.cgValue)
111+
.pngData(scale: config.scale.cgValue)
106112
case .swift, .sfsymbol:
107113
throw Error.unsupported
108114
}
@@ -129,6 +135,18 @@ public extension CommandLine {
129135
}
130136
}
131137

138+
private extension SVG {
139+
140+
func size(_ s: CGSize?) -> SVG {
141+
guard let s else { return self }
142+
return size(s)
143+
}
144+
145+
func inset(_ insets: Insets) -> SVG {
146+
expand(top: -insets.top, left: -insets.left, bottom: -insets.bottom, right: -insets.right)
147+
}
148+
}
149+
132150
#if canImport(CoreGraphics)
133151
private extension CommandLine.Scale {
134152
var cgValue: CGFloat {

SwiftDraw/NSImage+Image.swift renamed to SwiftDraw/NSImage+SVG.swift

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// NSImage+Image.swift
2+
// NSImage+SVG.swift
33
// SwiftDraw
44
//
55
// Created by Simon Whitty on 24/5/17.
@@ -72,6 +72,7 @@ public extension NSImage {
7272
}
7373

7474
public extension SVG {
75+
7576
func rasterize() -> NSImage {
7677
return rasterize(with: size)
7778
}
@@ -91,8 +92,9 @@ public extension SVG {
9192
return image
9293
}
9394

94-
func pngData(size: CGSize? = nil, scale: CGFloat = 0, insets: Insets = .zero) throws -> Data {
95-
let (bounds, pixelsWide, pixelsHigh) = makeBounds(size: size, scale: scale, insets: insets)
95+
func pngData(scale: CGFloat = 0) throws -> Data {
96+
let scale = scale == 0 ? SVG.defaultScale : scale
97+
let (bounds, pixelsWide, pixelsHigh) = Self.makeBounds(size: size, scale: scale)
9698
guard let bitmap = makeBitmap(width: pixelsWide, height: pixelsHigh, isOpaque: false),
9799
let ctx = NSGraphicsContext(bitmapImageRep: bitmap)?.cgContext else {
98100
throw Error("Failed to create CGContext")
@@ -108,8 +110,9 @@ public extension SVG {
108110
return data
109111
}
110112

111-
func jpegData(size: CGSize? = nil, scale: CGFloat = 0, compressionQuality quality: CGFloat = 1, insets: Insets = .zero) throws -> Data {
112-
let (bounds, pixelsWide, pixelsHigh) = makeBounds(size: size, scale: scale, insets: insets)
113+
func jpegData(scale: CGFloat = 0, compressionQuality quality: CGFloat = 1) throws -> Data {
114+
let scale = scale == 0 ? SVG.defaultScale : scale
115+
let (bounds, pixelsWide, pixelsHigh) = Self.makeBounds(size: size, scale: scale)
113116
guard let bitmap = makeBitmap(width: pixelsWide, height: pixelsHigh, isOpaque: true),
114117
let ctx = NSGraphicsContext(bitmapImageRep: bitmap)?.cgContext else {
115118
throw Error("Failed to create CGContext")
@@ -127,6 +130,10 @@ public extension SVG {
127130
return data
128131
}
129132

133+
internal static var defaultScale: CGFloat {
134+
NSScreen.main?.backingScaleFactor ?? 1.0
135+
}
136+
130137
private struct Error: LocalizedError {
131138
var errorDescription: String?
132139

@@ -138,11 +145,6 @@ public extension SVG {
138145

139146
extension SVG {
140147

141-
func makeBounds(size: CGSize?, scale: CGFloat, insets: Insets) -> (bounds: CGRect, pixelsWide: Int, pixelsHigh: Int) {
142-
let scale = scale == 0 ? (NSScreen.main?.backingScaleFactor ?? 1.0) : scale
143-
return Self.makeBounds(size: size, defaultSize: self.size, scale: scale, insets: insets)
144-
}
145-
146148
func makeBitmap(width: Int, height: Int, isOpaque: Bool) -> NSBitmapImageRep? {
147149
guard width > 0 && height > 0 else { return nil }
148150
return NSBitmapImageRep(

SwiftDraw/SVG+CoreGraphics.swift

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -38,31 +38,25 @@ public extension CGContext {
3838
func draw(_ image: SVG, in rect: CGRect? = nil) {
3939
let defaultRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
4040
let renderer = CGRenderer(context: self)
41+
saveGState()
4142

42-
guard let rect = rect, rect != defaultRect else {
43-
renderer.perform(image.commands)
44-
return
43+
if let rect = rect, rect != defaultRect {
44+
translateBy(x: rect.origin.x, y: rect.origin.y)
45+
scaleBy(
46+
x: rect.width / image.size.width,
47+
y: rect.height / image.size.height
48+
)
4549
}
50+
renderer.perform(image.commands)
4651

47-
let scale = CGSize(width: rect.width / image.size.width,
48-
height: rect.height / image.size.height)
49-
draw(image.commands, in: rect, scale: scale)
50-
}
51-
52-
fileprivate func draw(_ commands: [RendererCommand<CGTypes>], in rect: CGRect, scale: CGSize = CGSize(width: 1.0, height: 1.0)) {
53-
let renderer = CGRenderer(context: self)
54-
saveGState()
55-
translateBy(x: rect.origin.x, y: rect.origin.y)
56-
scaleBy(x: scale.width, y: scale.height)
57-
renderer.perform(commands)
5852
restoreGState()
5953
}
6054
}
6155

6256
public extension SVG {
6357

64-
func pdfData(size: CGSize? = nil, insets: Insets = .zero) throws -> Data {
65-
let (bounds, pixelsWide, pixelsHigh) = makeBounds(size: size, scale: 1, insets: insets)
58+
func pdfData() throws -> Data {
59+
let (bounds, pixelsWide, pixelsHigh) = Self.makeBounds(size: size, scale: 1)
6660
var mediaBox = CGRect(x: 0.0, y: 0.0, width: CGFloat(pixelsWide), height: CGFloat(pixelsHigh))
6761

6862
let data = NSMutableData()
@@ -92,31 +86,18 @@ public extension SVG {
9286

9387
extension SVG {
9488

95-
static func makeBounds(size: CGSize?,
96-
defaultSize: CGSize,
97-
scale: CGFloat,
98-
insets: Insets) -> (bounds: CGRect, pixelsWide: Int, pixelsHigh: Int) {
99-
let viewport = CGSize(
100-
width: defaultSize.width - (insets.left + insets.right),
101-
height: defaultSize.height - (insets.top + insets.bottom)
89+
static func makeBounds(size: CGSize, scale: CGFloat) -> (bounds: CGRect, pixelsWide: Int, pixelsHigh: Int) {
90+
let bounds = CGRect(
91+
x: 0,
92+
y: 0,
93+
width: size.width * scale,
94+
height: size.height * scale
10295
)
10396

104-
let size = size ?? viewport
105-
106-
let sx = size.width / viewport.width
107-
let sy = size.height / viewport.height
108-
109-
let width = size.width * scale
110-
let height = size.height * scale
111-
let insets = insets.applying(sx: sx * scale, sy: sy * scale)
112-
let bounds = CGRect(x: -insets.left,
113-
y: -insets.top,
114-
width: width + insets.left + insets.right,
115-
height: height + insets.top + insets.bottom)
11697
return (
11798
bounds: bounds,
118-
pixelsWide: Int(width),
119-
pixelsHigh: Int(height)
99+
pixelsWide: Int(exactly: ceil(bounds.width)) ?? 0,
100+
pixelsHigh: Int(exactly: ceil(bounds.height)) ?? 0
120101
)
121102
}
122103
}

SwiftDraw/SVG+Deprecated.swift

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//
2+
// SVG+Deprecated.swift
3+
// SwiftDraw
4+
//
5+
// Created by Simon Whitty on 23/2/25.
6+
// Copyright 2025 Simon Whitty
7+
//
8+
// Distributed under the permissive zlib license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/SwiftDraw
12+
//
13+
// This software is provided 'as-is', without any express or implied
14+
// warranty. In no event will the authors be held liable for any damages
15+
// arising from the use of this software.
16+
//
17+
// Permission is granted to anyone to use this software for any purpose,
18+
// including commercial applications, and to alter it and redistribute it
19+
// freely, subject to the following restrictions:
20+
//
21+
// 1. The origin of this software must not be misrepresented; you must not
22+
// claim that you wrote the original software. If you use this software
23+
// in a product, an acknowledgment in the product documentation would be
24+
// appreciated but is not required.
25+
//
26+
// 2. Altered source versions must be plainly marked as such, and must not be
27+
// misrepresented as being the original software.
28+
//
29+
// 3. This notice may not be removed or altered from any source distribution.
30+
//
31+
32+
#if canImport(CoreGraphics)
33+
import CoreGraphics
34+
import Foundation
35+
36+
#if canImport(UIKit)
37+
import UIKit
38+
#endif
39+
40+
public extension SVG {
41+
42+
@available(*, deprecated, message: "add insets via SVG.expand() before pngData")
43+
func pngData(scale: CGFloat = 0, insets: Insets) throws -> Data {
44+
try inset(insets).pngData(scale: scale)
45+
}
46+
47+
@available(*, deprecated, message: "set size via SVG.size() before pngData")
48+
func pngData(size: CGSize, scale: CGFloat = 0) throws -> Data {
49+
try self.size(size).pngData(scale: scale)
50+
}
51+
52+
@available(*, deprecated, message: "add insets via SVG.expand() before jpegData")
53+
func jpegData(scale: CGFloat = 0, compressionQuality quality: CGFloat = 1, insets: Insets) throws -> Data {
54+
try inset(insets).jpegData(scale: scale, compressionQuality: quality)
55+
}
56+
57+
@available(*, deprecated, message: "set size via SVG.size() before jpegData")
58+
func jpegData(size: CGSize, scale: CGFloat = 0, compressionQuality quality: CGFloat = 1) throws -> Data {
59+
try self.size(size).jpegData(scale: scale, compressionQuality: quality)
60+
}
61+
62+
private func inset(_ insets: Insets) -> SVG {
63+
expand(top: -insets.top, left: -insets.left, bottom: -insets.bottom, right: -insets.right)
64+
}
65+
66+
#if canImport(UIKit)
67+
@available(*, deprecated, message: "add insets via SVG.expand() before rasterize()")
68+
func rasterize(scale: CGFloat = 0, insets: UIEdgeInsets) -> UIImage {
69+
inset(insets).rasterize(scale: scale)
70+
}
71+
72+
@available(*, deprecated, message: "add insets via SVG.expand() before pngData()")
73+
func pngData(scale: CGFloat = 0, insets: UIEdgeInsets) throws -> Data {
74+
try inset(insets).pngData(scale: scale)
75+
}
76+
77+
@available(*, deprecated, message: "add insets via SVG.expand() before jpegData()")
78+
func jpegData(scale: CGFloat = 0, compressionQuality quality: CGFloat = 1, insets: UIEdgeInsets) throws -> Data {
79+
try inset(insets).jpegData(scale: scale, compressionQuality: quality)
80+
}
81+
82+
private func inset(_ insets: UIEdgeInsets) -> SVG {
83+
expand(top: -insets.top, left: -insets.left, bottom: -insets.bottom, right: -insets.right)
84+
}
85+
#endif
86+
87+
}
88+
#endif

0 commit comments

Comments
 (0)