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
4 changes: 3 additions & 1 deletion Examples/Basic.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion Examples/Sources/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions Samples.bundle/base64-image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions Samples.bundle/base64.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 4 additions & 6 deletions SwiftDraw/DOM.Image.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down
8 changes: 7 additions & 1 deletion SwiftDraw/LayerTree.Builder.Layer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
26 changes: 25 additions & 1 deletion SwiftDraw/LayerTree.CommandGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,31 @@ extension LayerTree {

func renderCommands(for image: Image) -> [RendererCommand<P.Types>] {
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<P.Types>] {
Expand Down
48 changes: 30 additions & 18 deletions SwiftDraw/LayerTree.Image.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
}
16 changes: 8 additions & 8 deletions SwiftDraw/Parser.XML.Image.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
8 changes: 6 additions & 2 deletions SwiftDraw/Renderer.CGText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)")
Expand Down
22 changes: 15 additions & 7 deletions SwiftDraw/Renderer.CoreGraphics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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()
}

Expand Down
9 changes: 8 additions & 1 deletion SwiftDraw/Renderer.LayerTree.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
9 changes: 5 additions & 4 deletions SwiftDraw/Renderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -192,7 +193,7 @@ enum RendererCommand<Types: RendererTypes> {
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)

Expand Down
17 changes: 13 additions & 4 deletions SwiftDrawTests/LayerTree.Builder.LayerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down Expand Up @@ -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))
}
}
4 changes: 2 additions & 2 deletions SwiftDrawTests/MockRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
2 changes: 1 addition & 1 deletion SwiftDrawTests/RendererTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
])
Expand Down