diff --git a/Examples/Basic.xcodeproj/project.pbxproj b/Examples/Basic.xcodeproj/project.pbxproj
index 3c5a33a..813eb65 100644
--- a/Examples/Basic.xcodeproj/project.pbxproj
+++ b/Examples/Basic.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 52;
+ objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -312,6 +312,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = C8TWBM2E6Q;
INFOPLIST_FILE = Sources/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -331,6 +332,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = C8TWBM2E6Q;
INFOPLIST_FILE = Sources/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
diff --git a/Examples/Sources/ViewController.swift b/Examples/Sources/ViewController.swift
index 83cc130..3b15342 100644
--- a/Examples/Sources/ViewController.swift
+++ b/Examples/Sources/ViewController.swift
@@ -65,7 +65,7 @@ class ViewController: UIViewController {
override func loadView() {
let imageView = UIImageView(frame: UIScreen.main.bounds)
- imageView.image = SVG(named: "gradient-stroke.svg", in: .samples)?.rasterize()
+ imageView.image = SVG(named: "shapes.svg", in: .samples)?.rasterize()
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .white
self.view = imageView
diff --git a/Samples.bundle/base64-image.svg b/Samples.bundle/base64-image.svg
new file mode 100644
index 0000000..f3da110
--- /dev/null
+++ b/Samples.bundle/base64-image.svg
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/Samples.bundle/base64.svg b/Samples.bundle/base64.svg
index 69e2958..d3e325f 100644
--- a/Samples.bundle/base64.svg
+++ b/Samples.bundle/base64.svg
@@ -2,6 +2,7 @@
\ No newline at end of file
diff --git a/SwiftDraw/DOM.Image.swift b/SwiftDraw/DOM.Image.swift
index f12d92f..95acba4 100644
--- a/SwiftDraw/DOM.Image.swift
+++ b/SwiftDraw/DOM.Image.swift
@@ -31,16 +31,14 @@
extension DOM {
final class Image: GraphicsElement {
var href: URL
- var width: Coordinate
- var height: Coordinate
-
+ var width: Coordinate?
+ var height: Coordinate?
+
var x: Coordinate?
var y: Coordinate?
- init(href: URL, width: Coordinate, height: Coordinate) {
+ init(href: URL) {
self.href = href
- self.width = width
- self.height = height
super.init()
}
}
diff --git a/SwiftDraw/LayerTree.Builder.Layer.swift b/SwiftDraw/LayerTree.Builder.Layer.swift
index 5f3cbb2..7f83f9d 100644
--- a/SwiftDraw/LayerTree.Builder.Layer.swift
+++ b/SwiftDraw/LayerTree.Builder.Layer.swift
@@ -71,9 +71,15 @@ extension LayerTree.Builder {
static func makeImageContents(from image: DOM.Image) throws -> LayerTree.Layer.Contents {
guard
let decoded = image.href.decodedData,
- let im = LayerTree.Image(mimeType: decoded.mimeType, data: decoded.data) else {
+ var im = LayerTree.Image(mimeType: decoded.mimeType, data: decoded.data) else {
throw LayerTree.Error.invalid("Cannot decode image")
}
+
+ im.origin.x = LayerTree.Float(image.x ?? 0)
+ im.origin.y = LayerTree.Float(image.y ?? 0)
+ im.width = image.width.map { LayerTree.Float($0) }
+ im.height = image.height.map { LayerTree.Float($0) }
+
return .image(im)
}
}
diff --git a/SwiftDraw/LayerTree.CommandGenerator.swift b/SwiftDraw/LayerTree.CommandGenerator.swift
index 5bda210..8496922 100644
--- a/SwiftDraw/LayerTree.CommandGenerator.swift
+++ b/SwiftDraw/LayerTree.CommandGenerator.swift
@@ -243,7 +243,31 @@ extension LayerTree {
func renderCommands(for image: Image) -> [RendererCommand
] {
guard let renderImage = provider.createImage(from: image) else { return [] }
- return [.draw(image: renderImage)]
+ let size = provider.createSize(from: renderImage)
+ guard size.width > 0 && size.height > 0 else { return [] }
+
+ let frame = makeImageFrame(for: image, bitmapSize: size)
+ let rect = provider.createRect(from: frame)
+ return [.draw(image: renderImage, in: rect)]
+ }
+
+ func makeImageFrame(for image: Image, bitmapSize: LayerTree.Size) -> LayerTree.Rect {
+ var frame = LayerTree.Rect(
+ x: image.origin.x,
+ y: image.origin.y,
+ width: image.width ?? bitmapSize.width,
+ height: image.height ?? bitmapSize.height
+ )
+
+ let aspectRatio = bitmapSize.width / bitmapSize.height
+
+ if let height = image.height, image.width == nil {
+ frame.size.width = height * aspectRatio
+ }
+ if let width = image.width, image.height == nil {
+ frame.size.height = width / aspectRatio
+ }
+ return frame
}
func renderCommands(for text: String, at point: Point, attributes: TextAttributes, colorConverter: ColorConverter = DefaultColorConverter()) -> [RendererCommand] {
diff --git a/SwiftDraw/LayerTree.Image.swift b/SwiftDraw/LayerTree.Image.swift
index a98e1ce..904c297 100644
--- a/SwiftDraw/LayerTree.Image.swift
+++ b/SwiftDraw/LayerTree.Image.swift
@@ -32,23 +32,35 @@
import Foundation
extension LayerTree {
- enum Image: Equatable {
- case jpeg(data: Data)
- case png(data: Data)
-
- init?(mimeType: String, data: Data) {
- guard data.count > 0 else { return nil }
-
- switch mimeType {
- case "image/png":
- self = .png(data: data)
- case "image/jpeg":
- self = .jpeg(data: data)
- case "image/jpg":
- self = .jpeg(data: data)
- default:
- return nil
- }
+ struct Image: Equatable {
+
+ var bitmap: Bitmap
+ var origin: Point = .zero
+ var width: LayerTree.Float?
+ var height: LayerTree.Float?
+
+ enum Bitmap: Equatable {
+ case jpeg(Data)
+ case png(Data)
+ }
+
+ init(bitmap: Bitmap) {
+ self.bitmap = bitmap
+ }
+
+ init?(mimeType: String, data: Data) {
+ guard data.count > 0 else { return nil }
+
+ switch mimeType {
+ case "image/png":
+ self.bitmap = .png(data)
+ case "image/jpeg":
+ self.bitmap = .jpeg(data)
+ case "image/jpg":
+ self.bitmap = .jpeg(data)
+ default:
+ return nil
+ }
+ }
}
- }
}
diff --git a/SwiftDraw/Parser.XML.Image.swift b/SwiftDraw/Parser.XML.Image.swift
index be78bbd..37f8dfd 100644
--- a/SwiftDraw/Parser.XML.Image.swift
+++ b/SwiftDraw/Parser.XML.Image.swift
@@ -33,13 +33,13 @@ extension XMLParser {
func parseImage(_ att: AttributeParser) throws -> DOM.Image {
let href: DOM.URL = try att.parseUrl("xlink:href")
- let width: DOM.Coordinate = try att.parseCoordinate("width")
- let height: DOM.Coordinate = try att.parseCoordinate("height")
-
- let use = DOM.Image(href: href, width: width, height: height)
- use.x = try att.parseCoordinate("x")
- use.y = try att.parseCoordinate("y")
-
- return use
+
+ let image = DOM.Image(href: href)
+ image.x = try att.parseCoordinate("x")
+ image.y = try att.parseCoordinate("y")
+ image.width = try att.parseCoordinate("width")
+ image.height = try att.parseCoordinate("height")
+
+ return image
}
}
diff --git a/SwiftDraw/Renderer.CGText.swift b/SwiftDraw/Renderer.CGText.swift
index 209f83b..bf1f06b 100644
--- a/SwiftDraw/Renderer.CGText.swift
+++ b/SwiftDraw/Renderer.CGText.swift
@@ -209,7 +209,11 @@ struct CGTextProvider: RendererTypeProvider {
func createImage(from image: LayerTree.Image) -> LayerTree.Image? {
return image
}
-
+
+ func createSize(from image: LayerTree.Image) -> LayerTree.Size {
+ LayerTree.Size(image.width ?? 0, image.height ?? 0)
+ }
+
func getBounds(from shape: LayerTree.Shape) -> LayerTree.Rect {
#if canImport(CoreGraphics)
return CGProvider().getBounds(from: shape)
@@ -556,7 +560,7 @@ public final class CGTextRenderer: Renderer {
}
}
- func draw(image: LayerTree.Image) {
+ func draw(image: LayerTree.Image, in rect: String) {
lines.append("ctx.saveGState()")
lines.append("ctx.translateBy(x: 0, y: image.height)")
lines.append("ctx.scaleBy(x: 1, y: -1)")
diff --git a/SwiftDraw/Renderer.CoreGraphics.swift b/SwiftDraw/Renderer.CoreGraphics.swift
index e09ca19..f239ef7 100644
--- a/SwiftDraw/Renderer.CoreGraphics.swift
+++ b/SwiftDraw/Renderer.CoreGraphics.swift
@@ -268,14 +268,21 @@ struct CGProvider: RendererTypeProvider {
}
func createImage(from image: LayerTree.Image) -> CGImage? {
- switch image {
- case .jpeg(data: let d):
+ switch image.bitmap {
+ case .jpeg(let d):
return CGImage.from(data: d)
- case .png(data: let d):
+ case .png(let d):
return CGImage.from(data: d)
}
}
+ func createSize(from image: CGImage) -> LayerTree.Size {
+ LayerTree.Size(
+ LayerTree.Float(image.width),
+ LayerTree.Float(image.height)
+ )
+ }
+
func getBounds(from shape: LayerTree.Shape) -> LayerTree.Rect {
let bounds = createPath(from: shape).boundingBoxOfPath
return LayerTree.Rect(x: LayerTree.Float(bounds.origin.x),
@@ -412,12 +419,13 @@ struct CGRenderer: Renderer {
ctx.fillPath(using: rule)
}
- func draw(image: CGImage) {
- let rect = CGRect(x: 0, y: 0, width: image.width, height: image.height)
+ func draw(image: CGImage, in rect: CGRect) {
pushState()
- translate(tx: 0, ty: rect.height)
+ translate(tx: rect.minX, ty: rect.maxY)
scale(sx: 1, sy: -1)
- ctx.draw(image, in: rect)
+ pushState()
+ ctx.draw(image, in: CGRect(origin: .zero, size: rect.size))
+ popState()
popState()
}
diff --git a/SwiftDraw/Renderer.LayerTree.swift b/SwiftDraw/Renderer.LayerTree.swift
index 7a8af89..0258ab5 100644
--- a/SwiftDraw/Renderer.LayerTree.swift
+++ b/SwiftDraw/Renderer.LayerTree.swift
@@ -115,7 +115,14 @@ struct LayerTreeProvider: RendererTypeProvider {
func createImage(from image: LayerTree.Image) -> LayerTree.Image? {
return image
}
-
+
+ func createSize(from image: LayerTree.Image) -> LayerTree.Size {
+ LayerTree.Size(
+ image.width ?? 0,
+ image.height ?? 0
+ )
+ }
+
func getBounds(from shape: LayerTree.Shape) -> LayerTree.Rect {
return LayerTree.Rect(x: 0, y: 0, width: 0, height: 0)
}
diff --git a/SwiftDraw/Renderer.swift b/SwiftDraw/Renderer.swift
index ff54a0a..cc75882 100644
--- a/SwiftDraw/Renderer.swift
+++ b/SwiftDraw/Renderer.swift
@@ -67,6 +67,7 @@ protocol RendererTypeProvider {
func createLineCap(from cap: LayerTree.LineCap) -> Types.LineCap
func createLineJoin(from join: LayerTree.LineJoin) -> Types.LineJoin
func createImage(from image: LayerTree.Image) -> Types.Image?
+ func createSize(from image: Types.Image) -> LayerTree.Size
func getBounds(from shape: LayerTree.Shape) -> LayerTree.Rect
}
@@ -99,7 +100,7 @@ protocol Renderer {
func stroke(path: Types.Path)
func clipStrokeOutline(path: Types.Path)
func fill(path: Types.Path, rule: Types.FillRule)
- func draw(image: Types.Image)
+ func draw(image: Types.Image, in rect: Types.Rect)
func draw(linear gradient: Types.Gradient, from start: Types.Point, to end: Types.Point)
func draw(radial gradient: Types.Gradient, startCenter: Types.Point, startRadius: Types.Float, endCenter: Types.Point, endRadius: Types.Float)
}
@@ -151,8 +152,8 @@ extension Renderer {
clipStrokeOutline(path: p)
case .fill(let p, let r):
fill(path: p, rule: r)
- case .draw(image: let i):
- draw(image: i)
+ case .draw(image: let i, in: let r):
+ draw(image: i, in: r)
case .drawLinearGradient(let g, let start, let end):
draw(linear: g, from: start, to: end)
case let .drawRadialGradient(g, startCenter, startRadius, endCenter, endRadius):
@@ -192,7 +193,7 @@ enum RendererCommand {
case clipStrokeOutline(Types.Path)
case fill(Types.Path, rule: Types.FillRule)
- case draw(image: Types.Image)
+ case draw(image: Types.Image, in: Types.Rect)
case drawLinearGradient(Types.Gradient, from: Types.Point, to: Types.Point)
case drawRadialGradient(Types.Gradient, startCenter: Types.Point, startRadius: Types.Float, endCenter: Types.Point, endRadius: Types.Float)
diff --git a/SwiftDrawTests/LayerTree.Builder.LayerTests.swift b/SwiftDrawTests/LayerTree.Builder.LayerTests.swift
index 2358e94..5d5e6b9 100644
--- a/SwiftDrawTests/LayerTree.Builder.LayerTests.swift
+++ b/SwiftDrawTests/LayerTree.Builder.LayerTests.swift
@@ -43,14 +43,12 @@ final class LayerTreeBuilderLayerTests: XCTestCase {
}
func testMakeImageContentsFromDOM() throws {
- let image = DOM.Image(href: URL(maybeData: "data:image/png;base64,f00d")!,
- width: 50,
- height: 50)
+ let image = DOM.Image(href: URL(maybeData: "data:image/png;base64,f00d")!)
let contents = try LayerTree.Builder.makeImageContents(from: image)
XCTAssertEqual(contents, .image(.png(data: Data(base64Encoded: "f00d")!)))
- let invalid = DOM.Image(href: URL(string: "aa")!, width: 10, height: 20)
+ let invalid = DOM.Image(href: URL(string: "aa")!)
XCTAssertThrowsError(try LayerTree.Builder.makeImageContents(from: invalid))
}
@@ -86,3 +84,14 @@ final class LayerTreeBuilderLayerTests: XCTestCase {
XCTAssertEqual(l2.transform, [.translate(tx: 0, ty: 20)])
}
}
+
+extension LayerTree.Image {
+
+ static func png(data: Data) -> Self {
+ Self(bitmap: .png(data))
+ }
+
+ static func jpeg(data: Data) -> Self {
+ Self(bitmap: .jpeg(data))
+ }
+}
diff --git a/SwiftDrawTests/MockRenderer.swift b/SwiftDrawTests/MockRenderer.swift
index 03a039e..c0a4440 100644
--- a/SwiftDrawTests/MockRenderer.swift
+++ b/SwiftDrawTests/MockRenderer.swift
@@ -126,10 +126,10 @@ final class MockRenderer: Renderer {
operations.append("fillPath")
}
- func draw(image: LayerTree.Image) {
+ func draw(image: LayerTree.Image, in rect: LayerTree.Rect) {
operations.append("drawImage")
}
-
+
func draw(linear gradient: LayerTree.Gradient, from start: LayerTree.Point, to end: LayerTree.Point) {
operations.append("drawLinearGradient")
}
diff --git a/SwiftDrawTests/RendererTests.swift b/SwiftDrawTests/RendererTests.swift
index fdbe45b..126fa1c 100644
--- a/SwiftDrawTests/RendererTests.swift
+++ b/SwiftDrawTests/RendererTests.swift
@@ -59,7 +59,7 @@ final class RendererTests: XCTestCase {
.clipStrokeOutline(.mock),
.setAlpha(0.5),
.setBlend(mode: .sourceIn),
- .draw(image: .mock),
+ .draw(image: .mock, in: .zero),
.drawLinearGradient(.mock, from: .zero, to: .zero),
.drawRadialGradient(.mock, startCenter: .zero, startRadius: 0, endCenter: .zero, endRadius: 0)
])