Add swift svg
This commit is contained in:
parent
ce64d84013
commit
e51fef57d8
136 changed files with 11457 additions and 23 deletions
|
|
@ -24,7 +24,7 @@ swift_library(
|
|||
"//submodules/WebPBinding:WebPBinding",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
|
||||
"//submodules/Svg:Svg",
|
||||
"//submodules/Svg/LegacyImpl",
|
||||
"//submodules/Utils/RangeSet:RangeSet",
|
||||
"//submodules/ImageCompression",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ import TinyThumbnail
|
|||
import ImageTransparency
|
||||
import AppBundle
|
||||
import MusicAlbumArtResources
|
||||
import Svg
|
||||
import RangeSet
|
||||
import Accelerate
|
||||
import ImageCompression
|
||||
import LegacyImpl
|
||||
|
||||
private enum ResourceFileData {
|
||||
case data(Data)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
objc_library(
|
||||
swift_library(
|
||||
name = "Svg",
|
||||
enable_modules = True,
|
||||
module_name = "Svg",
|
||||
srcs = glob([
|
||||
"Sources/**/*.m",
|
||||
"Sources/**/*.c",
|
||||
"Sources/**/*.h",
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
hdrs = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"PublicHeaders",
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
deps = [
|
||||
"//submodules/Svg/LegacyImpl",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
23
submodules/Svg/LegacyImpl/BUILD
Normal file
23
submodules/Svg/LegacyImpl/BUILD
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
objc_library(
|
||||
name = "LegacyImpl",
|
||||
enable_modules = True,
|
||||
module_name = "LegacyImpl",
|
||||
srcs = glob([
|
||||
"Sources/**/*.m",
|
||||
"Sources/**/*.c",
|
||||
"Sources/**/*.h",
|
||||
]),
|
||||
hdrs = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"PublicHeaders",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -27,6 +27,6 @@ GiftPatternData * _Nullable getGiftPatternData(NSData * _Nonnull data);
|
|||
UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIColor * _Nonnull backgroundColor, CGFloat scale, bool fit);
|
||||
UIImage * _Nullable renderPreparedImageWithSymbol(NSData * _Nonnull data, CGSize size, UIColor * _Nonnull backgroundColor, CGFloat scale, bool fit, UIImage * _Nullable symbolImage, int32_t modelRectIndex);
|
||||
|
||||
UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor * _Nullable backgroundColor, UIColor * _Nullable foregroundColor, CGFloat scale, bool opaque);
|
||||
UIImage * _Nullable drawSvgImageImpl(NSData * _Nonnull data, CGSize size, UIColor * _Nullable backgroundColor, UIColor * _Nullable foregroundColor, CGFloat scale, bool opaque);
|
||||
|
||||
#endif /* Lottie_h */
|
||||
|
|
@ -302,7 +302,7 @@ void renderShape(NSVGshape *shape, CGContextRef context, UIColor *foregroundColo
|
|||
}
|
||||
}
|
||||
|
||||
UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor *backgroundColor, UIColor *foregroundColor, CGFloat canvasScale, bool opaque) {
|
||||
UIImage * _Nullable drawSvgImageImpl(NSData * _Nonnull data, CGSize size, UIColor *backgroundColor, UIColor *foregroundColor, CGFloat canvasScale, bool opaque) {
|
||||
if (!data || data.length == 0) return nil;
|
||||
|
||||
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
|
||||
7
submodules/Svg/Sources/Svg.swift
Normal file
7
submodules/Svg/Sources/Svg.swift
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import LegacyImpl
|
||||
|
||||
public func drawSvgImage(data: Data, size: CGSize, backgroundColor: UIColor?, foregroundColor: UIColor?, scale: CGFloat, opaque: Bool) -> UIImage? {
|
||||
return drawSvgImageImpl(data, size, backgroundColor, foregroundColor, scale, opaque)
|
||||
}
|
||||
|
|
@ -219,7 +219,7 @@ swift_library(
|
|||
"//submodules/ItemListVenueItem:ItemListVenueItem",
|
||||
"//submodules/SemanticStatusNode:SemanticStatusNode",
|
||||
"//submodules/AccountUtils:AccountUtils",
|
||||
"//submodules/Svg:Svg",
|
||||
"//submodules/Svg/LegacyImpl",
|
||||
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
|
||||
"//submodules/TooltipUI:TooltipUI",
|
||||
"//submodules/ListMessageItem:ListMessageItem",
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ public final class PeerInfoRatingComponent: Component {
|
|||
}
|
||||
|
||||
if let url = Bundle.main.url(forResource: "profile_level\(levelIndex)_outer", withExtension: "svg"), let data = try? Data(contentsOf: url) {
|
||||
if let image = generateTintedImage(image: drawSvgImage(data, size, nil, nil, 0.0, false), color: component.borderColor) {
|
||||
if let image = generateTintedImage(image: drawSvgImage(data: data, size: size, backgroundColor: nil, foregroundColor: nil, scale: 0.0, opaque: false), color: component.borderColor) {
|
||||
image.draw(in: CGRect(origin: CGPoint(x: 0.0, y: backgroundOffsetsY[levelIndex] ?? 0.0), size: size), blendMode: .normal, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
|
@ -372,7 +372,7 @@ public final class PeerInfoRatingComponent: Component {
|
|||
}
|
||||
|
||||
if let url = Bundle.main.url(forResource: "profile_level\(levelIndex)_inner", withExtension: "svg"), let data = try? Data(contentsOf: url) {
|
||||
if let image = generateTintedImage(image: drawSvgImage(data, size, nil, nil, 0.0, false), color: component.backgroundColor) {
|
||||
if let image = generateTintedImage(image: drawSvgImage(data: data, size: size, backgroundColor: nil, foregroundColor: nil, scale: 0.0, opaque: false), color: component.backgroundColor) {
|
||||
image.draw(in: CGRect(origin: CGPoint(x: 0.0, y: backgroundOffsetsY[levelIndex] ?? 0.0), size: size), blendMode: .normal, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import WallpaperResources
|
|||
import GZip
|
||||
import TelegramUniversalVideoContent
|
||||
import GradientBackground
|
||||
import Svg
|
||||
import LegacyImpl
|
||||
import UniversalMediaPlayer
|
||||
import RangeSet
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ swift_library(
|
|||
"//submodules/PhotoResources:PhotoResources",
|
||||
"//submodules/PersistentStringHash:PersistentStringHash",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/Svg:Svg",
|
||||
"//submodules/Svg/LegacyImpl",
|
||||
"//submodules/GZip:GZip",
|
||||
"//submodules/GradientBackground:GradientBackground",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import LocalMediaResources
|
|||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AppBundle
|
||||
import Svg
|
||||
import LegacyImpl
|
||||
import GradientBackground
|
||||
import GZip
|
||||
|
||||
|
|
@ -1360,7 +1360,7 @@ public func themeImage(account: Account, accountManager: AccountManager<Telegram
|
|||
case let .pattern(data, colors, intensity):
|
||||
let wallpaperImage = generateImage(arguments.drawingSize, rotatedContext: { size, context in
|
||||
drawWallpaperGradientImage(colors.map(UIColor.init(rgb:)), context: context, size: size)
|
||||
if let unpackedData = TGGUnzipData(data, 2 * 1024 * 1024), let image = drawSvgImage(unpackedData, arguments.drawingSize, .clear, .black, 1.0, true) {
|
||||
if let unpackedData = TGGUnzipData(data, 2 * 1024 * 1024), let image = drawSvgImageImpl(unpackedData, arguments.drawingSize, .clear, .black, 1.0, true) {
|
||||
context.setBlendMode(.softLight)
|
||||
context.setAlpha(abs(CGFloat(intensity)) / 100.0)
|
||||
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: arguments.drawingSize))
|
||||
|
|
|
|||
17
third-party/Swift2D/BUILD
vendored
Normal file
17
third-party/Swift2D/BUILD
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "Swift2D",
|
||||
module_name = "Swift2D",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-suppress-warnings",
|
||||
],
|
||||
deps = [
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
19
third-party/Swift2D/Sources/Point+CoreGraphics.swift
vendored
Normal file
19
third-party/Swift2D/Sources/Point+CoreGraphics.swift
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#if canImport(CoreGraphics)
|
||||
import CoreGraphics
|
||||
#else
|
||||
import Foundation
|
||||
#endif
|
||||
|
||||
public extension Point {
|
||||
/// Initialize a `Point` using the provided `CGPoint` values.
|
||||
init(_ point: CGPoint) {
|
||||
self.init(x: point.x, y: point.y)
|
||||
}
|
||||
}
|
||||
|
||||
public extension CGPoint {
|
||||
/// Initialize a `CPPoint` using the provided `Point` values.
|
||||
init(_ point: Point) {
|
||||
self.init(x: point.x, y: point.y)
|
||||
}
|
||||
}
|
||||
82
third-party/Swift2D/Sources/Point.swift
vendored
Normal file
82
third-party/Swift2D/Sources/Point.swift
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/// The representation of a single point in a two-dimensional plane.
|
||||
public struct Point: Hashable, Codable, Sendable, CustomStringConvertible {
|
||||
|
||||
public static let zero: Point = Point(x: 0, y: 0)
|
||||
public static let nan: Point = Point(x: Double.nan, y: Double.nan)
|
||||
public static let infinite: Point = Point(x: -Double.greatestFiniteMagnitude / 2, y: -Double.greatestFiniteMagnitude / 2)
|
||||
public static let null: Point = Point(x: Double.infinity, y: Double.infinity)
|
||||
|
||||
public let x: Double
|
||||
public let y: Double
|
||||
|
||||
public init(x: Double = 0.0, y: Double = 0.0) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
}
|
||||
|
||||
public init(x: Float, y: Float) {
|
||||
self.x = Double(x)
|
||||
self.y = Double(y)
|
||||
}
|
||||
|
||||
public init(x: Int, y: Int) {
|
||||
self.x = Double(x)
|
||||
self.y = Double(y)
|
||||
}
|
||||
|
||||
public var description: String { "Point(x: \(x), y: \(y))" }
|
||||
public var isZero: Bool { self == .zero }
|
||||
public var isNaN: Bool { self == .nan }
|
||||
public var isInfinite: Bool { self == .infinite }
|
||||
public var isNull: Bool { self == .null }
|
||||
|
||||
/// Create a new instance maintaining the `y` value while using the provided `x` value.
|
||||
public func x(_ value: Double) -> Point {
|
||||
Point(x: value, y: y)
|
||||
}
|
||||
|
||||
/// Create a new instance maintaining the `x` value while using the provided `y` value.
|
||||
public func y(_ value: Double) -> Point {
|
||||
Point(x: x, y: value)
|
||||
}
|
||||
|
||||
/// The _mirror_ of the instance, rotated 180° around the provided `point`
|
||||
/// in a two-dimensional plane.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - point: The anchor to use in calculating the reflection.
|
||||
public func reflecting(around point: Point) -> Point {
|
||||
let x = (2 * point.x) - x
|
||||
let y = (2 * point.y) - y
|
||||
return Point(x: x, y: y)
|
||||
}
|
||||
|
||||
public static func == (lhs: Point, rhs: Point) -> Bool {
|
||||
if lhs.x.isNaN, rhs.x.isNaN, lhs.y.isNaN, rhs.y.isNaN {
|
||||
return true
|
||||
}
|
||||
|
||||
if lhs.x.isInfinite, rhs.x.isInfinite, lhs.y.isInfinite, rhs.y.isInfinite {
|
||||
return true
|
||||
}
|
||||
|
||||
return lhs.x == rhs.x && lhs.y == rhs.y
|
||||
}
|
||||
}
|
||||
|
||||
public extension Point {
|
||||
@available(*, deprecated, renamed: "x(_:)")
|
||||
func with(x value: Double) -> Point {
|
||||
x(value)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "y(_:)")
|
||||
func with(y value: Double) -> Point {
|
||||
y(value)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "reflecting(around:)")
|
||||
func reflection(using point: Point) -> Point {
|
||||
reflecting(around: point)
|
||||
}
|
||||
}
|
||||
32
third-party/Swift2D/Sources/Rect+CoreGraphics.swift
vendored
Normal file
32
third-party/Swift2D/Sources/Rect+CoreGraphics.swift
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#if canImport(CoreGraphics)
|
||||
import CoreGraphics
|
||||
#else
|
||||
import Foundation
|
||||
#endif
|
||||
|
||||
public extension Rect {
|
||||
/// Initialize a `Rect` using the provided `CGRect` values.
|
||||
init(_ rect: CGRect) {
|
||||
self.init(
|
||||
origin: Point(rect.origin),
|
||||
size: Size(rect.size)
|
||||
)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "CGRect(_:)", message: "Use CGRect initializer directly")
|
||||
var cgRect: CGRect {
|
||||
CGRect(self)
|
||||
}
|
||||
}
|
||||
|
||||
public extension CGRect {
|
||||
/// Initialize a `CGRect` using the provided `Rect` values.
|
||||
init(_ rect: Rect) {
|
||||
self.init(
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
)
|
||||
}
|
||||
}
|
||||
251
third-party/Swift2D/Sources/Rect.swift
vendored
Normal file
251
third-party/Swift2D/Sources/Rect.swift
vendored
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/// The location and dimensions of a rectangle.
|
||||
public struct Rect: Hashable, Codable, Sendable, CustomStringConvertible {
|
||||
|
||||
public static let zero: Rect = Rect(origin: .zero, size: .zero)
|
||||
public static let nan: Rect = Rect(origin: .nan, size: .nan)
|
||||
public static let infinite: Rect = Rect(origin: .infinite, size: .infinite)
|
||||
public static let null: Rect = Rect(origin: .null, size: .zero)
|
||||
|
||||
/// A point that specifies the coordinates of the rectangle’s origin.
|
||||
public let origin: Point
|
||||
/// A size that specifies the height and width of the rectangle.
|
||||
public let size: Size
|
||||
|
||||
public init(origin: Point = .zero, size: Size = .zero) {
|
||||
self.origin = origin
|
||||
self.size = size
|
||||
}
|
||||
|
||||
public init(x: Double, y: Double, width: Double, height: Double) {
|
||||
origin = Point(x: x, y: y)
|
||||
size = Size(width: width, height: height)
|
||||
}
|
||||
|
||||
public init(x: Float, y: Float, width: Float, height: Float) {
|
||||
origin = Point(x: x, y: y)
|
||||
size = Size(width: width, height: height)
|
||||
}
|
||||
|
||||
public init(x: Int, y: Int, width: Int, height: Int) {
|
||||
origin = Point(x: x, y: y)
|
||||
size = Size(width: width, height: height)
|
||||
}
|
||||
|
||||
public var description: String { "Rect(origin: \(origin), size: \(size))" }
|
||||
public var isZero: Bool { self == .zero }
|
||||
public var isNaN: Bool { self == .nan }
|
||||
public var isInfinite: Bool { self == .infinite }
|
||||
public var isNull: Bool { origin == .infinite || origin == .null }
|
||||
public var isEmpty: Bool { isNull || width == 0.0 || height == 0.0 }
|
||||
|
||||
/// The x-coordinate of the rectangle origin
|
||||
public var x: Double { origin.x }
|
||||
|
||||
/// The y-coordinate of the rectangle origin
|
||||
public var y: Double { origin.y }
|
||||
|
||||
/// The width of the rectangle.
|
||||
public var width: Double { size.width }
|
||||
|
||||
/// The height of the rectangle.
|
||||
public var height: Double { size.height }
|
||||
|
||||
/// The middle of the `Rect` both horizontally and vertically.
|
||||
public var center: Point { Point(x: midX, y: midY) }
|
||||
|
||||
/// The smallest value for the x-coordinate of the rectangle.
|
||||
public var minX: Double {
|
||||
if size.width < 0.0 {
|
||||
return origin.x - abs(size.width)
|
||||
}
|
||||
|
||||
return origin.x
|
||||
}
|
||||
|
||||
/// The x-coordinate that establishes the center of a rectangle.
|
||||
public var midX: Double { minX + size.widthRadius }
|
||||
|
||||
/// The largest value of the x-coordinate for the rectangle.
|
||||
public var maxX: Double {
|
||||
if size.width < 0.0 {
|
||||
return origin.x
|
||||
}
|
||||
|
||||
return origin.x + size.width
|
||||
}
|
||||
|
||||
/// The smallest value for the y-coordinate of the rectangle.
|
||||
public var minY: Double {
|
||||
if size.height < 0.0 {
|
||||
return origin.y - abs(size.height)
|
||||
}
|
||||
|
||||
return origin.y
|
||||
}
|
||||
|
||||
/// The y-coordinate that establishes the center of the rectangle.
|
||||
public var midY: Double { minY + size.heightRadius }
|
||||
|
||||
/// The largest value for the y-coordinate of the rectangle.
|
||||
public var maxY: Double {
|
||||
if size.height < 0.0 {
|
||||
return origin.y
|
||||
}
|
||||
|
||||
return origin.y + size.height
|
||||
}
|
||||
|
||||
/// Returns a rectangle with a positive width and height.
|
||||
public var standardized: Rect {
|
||||
guard !isNull else {
|
||||
return .null
|
||||
}
|
||||
|
||||
return Rect(x: minX, y: minY, width: abs(width), height: abs(height))
|
||||
}
|
||||
|
||||
public func origin(_ value: Point) -> Rect {
|
||||
Rect(origin: value, size: size)
|
||||
}
|
||||
|
||||
public func size(_ value: Size) -> Rect {
|
||||
Rect(origin: origin, size: value)
|
||||
}
|
||||
|
||||
public func contains(_ point: Point) -> Bool {
|
||||
guard !isNull else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard !isEmpty else {
|
||||
return false
|
||||
}
|
||||
|
||||
return (minX ..< maxX).contains(point.x) && (minY ..< maxY).contains(point.y)
|
||||
}
|
||||
|
||||
public func contains(_ rect: Rect) -> Bool {
|
||||
union(rect) == self
|
||||
}
|
||||
|
||||
public func intersects(_ rect: Rect) -> Bool {
|
||||
!intersection(rect).isNull
|
||||
}
|
||||
|
||||
public func intersection(_ rect: Rect) -> Rect {
|
||||
guard !isNull else {
|
||||
return .null
|
||||
}
|
||||
|
||||
guard !rect.isNull else {
|
||||
return .null
|
||||
}
|
||||
|
||||
let r1 = standardized
|
||||
let r1spanH = r1.minX ... r1.maxX
|
||||
let r1spanV = r1.minY ... r1.maxY
|
||||
|
||||
let r2 = rect.standardized
|
||||
let r2spanH = r2.minX ... r2.maxX
|
||||
let r2spanV = r2.minY ... r2.maxY
|
||||
|
||||
guard r1spanH.overlaps(r2spanH), r2spanV.overlaps(r2spanV) else {
|
||||
return .null
|
||||
}
|
||||
|
||||
let overlapH = r1spanH.clamped(to: r2spanH)
|
||||
let width: Double = if overlapH == r1spanH {
|
||||
r1.width
|
||||
} else if overlapH == r2spanH {
|
||||
r2.width
|
||||
} else {
|
||||
overlapH.upperBound - overlapH.lowerBound
|
||||
}
|
||||
|
||||
let overlapV = r1spanV.clamped(to: r2spanV)
|
||||
let height: Double = if overlapV == r1spanV {
|
||||
r1.height
|
||||
} else if overlapV == r2spanV {
|
||||
r2.height
|
||||
} else {
|
||||
overlapV.upperBound - overlapV.lowerBound
|
||||
}
|
||||
|
||||
return Rect(x: overlapH.lowerBound, y: overlapV.lowerBound, width: width, height: height)
|
||||
}
|
||||
|
||||
public func union(_ rect: Rect) -> Rect {
|
||||
guard !isNull else {
|
||||
return rect
|
||||
}
|
||||
|
||||
guard !rect.isNull else {
|
||||
return self
|
||||
}
|
||||
|
||||
let r1 = standardized
|
||||
let r2 = rect.standardized
|
||||
|
||||
let minX = min(r1.minX, r2.minX)
|
||||
let maxX = max(r1.maxX, r2.maxX)
|
||||
let minY = min(r1.minY, r2.minY)
|
||||
let maxY = max(r1.maxY, r2.maxY)
|
||||
|
||||
return Rect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
|
||||
}
|
||||
|
||||
public func offsetBy(dx: Double, dy: Double) -> Rect {
|
||||
guard !isNull else {
|
||||
return self
|
||||
}
|
||||
|
||||
let rect = standardized
|
||||
return Rect(
|
||||
origin: Point(
|
||||
x: rect.origin.x + dx,
|
||||
y: rect.origin.y + dy
|
||||
),
|
||||
size: rect.size
|
||||
)
|
||||
}
|
||||
|
||||
public func insetBy(dx: Double, dy: Double) -> Rect {
|
||||
guard !isNull else {
|
||||
return self
|
||||
}
|
||||
|
||||
let normalized = standardized
|
||||
let rect = Rect(
|
||||
origin: Point(
|
||||
x: normalized.x + dx,
|
||||
y: normalized.y + dy
|
||||
),
|
||||
size: Size(
|
||||
width: normalized.width - (dx * 2),
|
||||
height: normalized.height - (dy * 2)
|
||||
)
|
||||
)
|
||||
|
||||
guard rect.size.width >= 0, rect.size.height >= 0 else {
|
||||
return .null
|
||||
}
|
||||
|
||||
return rect
|
||||
}
|
||||
|
||||
public func expandedBy(dx: Double, dy: Double) -> Rect {
|
||||
insetBy(dx: -dx, dy: -dy)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Rect {
|
||||
@available(*, deprecated, renamed: "origin(_:)")
|
||||
func with(origin value: Point) -> Rect {
|
||||
Rect(origin: value, size: size)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "size(_:)")
|
||||
func with(size value: Size) -> Rect {
|
||||
Rect(origin: origin, size: value)
|
||||
}
|
||||
}
|
||||
19
third-party/Swift2D/Sources/Size+CoreGraphics.swift
vendored
Normal file
19
third-party/Swift2D/Sources/Size+CoreGraphics.swift
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#if canImport(CoreGraphics)
|
||||
import CoreGraphics
|
||||
#else
|
||||
import Foundation
|
||||
#endif
|
||||
|
||||
public extension Size {
|
||||
/// Initialize a `Size` using the provided `CGSize` values.
|
||||
init(_ size: CGSize) {
|
||||
self.init(width: size.width, height: size.height)
|
||||
}
|
||||
}
|
||||
|
||||
public extension CGSize {
|
||||
/// Initialize a `CGSize` using the provided `Size` values.
|
||||
init(_ size: Size) {
|
||||
self.init(width: size.width, height: size.height)
|
||||
}
|
||||
}
|
||||
87
third-party/Swift2D/Sources/Size.swift
vendored
Normal file
87
third-party/Swift2D/Sources/Size.swift
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/// A representation of two-dimensional width and height values.
|
||||
public struct Size: Hashable, Codable, Sendable, CustomStringConvertible {
|
||||
|
||||
public static let zero: Size = Size(width: 0.0, height: 0.0)
|
||||
public static let nan: Size = Size(width: Double.nan, height: Double.nan)
|
||||
public static let infinite: Size = Size(width: Double.greatestFiniteMagnitude, height: Double.greatestFiniteMagnitude)
|
||||
|
||||
public let width: Double
|
||||
public let height: Double
|
||||
|
||||
public init(width: Double = 0.0, height: Double = 0.0) {
|
||||
self.width = width
|
||||
self.height = height
|
||||
}
|
||||
|
||||
public init(width: Float, height: Float) {
|
||||
self.width = Double(width)
|
||||
self.height = Double(height)
|
||||
}
|
||||
|
||||
public init(width: Int, height: Int) {
|
||||
self.width = Double(width)
|
||||
self.height = Double(height)
|
||||
}
|
||||
|
||||
public var description: String { "Size(width: \(width), height: \(height))" }
|
||||
public var isZero: Bool { self == .zero }
|
||||
public var isNaN: Bool { self == .nan }
|
||||
public var isInfinite: Bool { self == .infinite }
|
||||
|
||||
/// The radius defined by the `width` dimension.
|
||||
public var widthRadius: Double { abs(width) / 2.0 }
|
||||
|
||||
/// The radius defined by the `height` dimension.
|
||||
public var heightRadius: Double { abs(height) / 2.0 }
|
||||
|
||||
/// The largest of the width and height radii.
|
||||
public var maxRadius: Double { max(widthRadius, heightRadius) }
|
||||
|
||||
/// The smallest of the width and height radii.
|
||||
public var minRadius: Double { min(widthRadius, heightRadius) }
|
||||
|
||||
/// The `Point` at which the `widthRadius` & `heightRadius` intersect.
|
||||
public var center: Point { Point(x: widthRadius, y: heightRadius) }
|
||||
|
||||
/// Create a new instance maintaining the `height` value while using the provided `width` value.
|
||||
public func width(_ value: Double) -> Size {
|
||||
Size(width: value, height: height)
|
||||
}
|
||||
|
||||
/// Create a new instance maintaining the `width` value while using the provided `height` value.
|
||||
public func height(_ value: Double) -> Size {
|
||||
Size(width: width, height: value)
|
||||
}
|
||||
|
||||
public static func == (lhs: Size, rhs: Size) -> Bool {
|
||||
if lhs.width.isNaN, rhs.width.isNaN, lhs.height.isNaN, rhs.height.isNaN {
|
||||
return true
|
||||
}
|
||||
|
||||
return lhs.width == rhs.width && lhs.height == rhs.height
|
||||
}
|
||||
}
|
||||
|
||||
public extension Size {
|
||||
@available(*, deprecated, renamed: "widthRadius")
|
||||
var xRadius: Double { abs(width) / 2.0 }
|
||||
|
||||
@available(*, deprecated, renamed: "heightRadius")
|
||||
var yRadius: Double { abs(height) / 2.0 }
|
||||
|
||||
@available(*, deprecated, renamed: "widthRadius")
|
||||
var horizontalRadius: Double { xRadius }
|
||||
|
||||
@available(*, deprecated, renamed: "heightRadius")
|
||||
var verticalRadius: Double { yRadius }
|
||||
|
||||
@available(*, deprecated, renamed: "width(_:)")
|
||||
func with(width value: Double) -> Size {
|
||||
width(value)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "height(_:)")
|
||||
func with(height value: Double) -> Size {
|
||||
height(value)
|
||||
}
|
||||
}
|
||||
17
third-party/SwiftColor/BUILD
vendored
Normal file
17
third-party/SwiftColor/BUILD
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "SwiftColor",
|
||||
module_name = "SwiftColor",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-suppress-warnings",
|
||||
],
|
||||
deps = [
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
20
third-party/SwiftColor/Sources/Clamping.swift
vendored
Normal file
20
third-party/SwiftColor/Sources/Clamping.swift
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
@propertyWrapper
|
||||
public struct Clamping<Value: Comparable> {
|
||||
var value: Value
|
||||
let range: ClosedRange<Value>
|
||||
|
||||
public init(wrappedValue value: Value, _ range: ClosedRange<Value>) {
|
||||
precondition(range.contains(value))
|
||||
self.value = value
|
||||
self.range = range
|
||||
}
|
||||
|
||||
public var wrappedValue: Value {
|
||||
get {
|
||||
value
|
||||
}
|
||||
set(newValue) {
|
||||
value = min(max(range.lowerBound, newValue), range.upperBound)
|
||||
}
|
||||
}
|
||||
}
|
||||
3
third-party/SwiftColor/Sources/ColorSpace.swift
vendored
Normal file
3
third-party/SwiftColor/Sources/ColorSpace.swift
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
public enum ColorSpace: Equatable, Sendable {
|
||||
case rgba
|
||||
}
|
||||
24
third-party/SwiftColor/Sources/Pigment+AppKit.swift
vendored
Normal file
24
third-party/SwiftColor/Sources/Pigment+AppKit.swift
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import Foundation
|
||||
#if canImport(AppKit) && !targetEnvironment(macCatalyst)
|
||||
import AppKit
|
||||
|
||||
public extension Pigment {
|
||||
/// Initialize a `Pigment` using an `NSColor`.
|
||||
init(_ color: NSColor) {
|
||||
red = color.redComponent
|
||||
green = color.greenComponent
|
||||
blue = color.blueComponent
|
||||
alpha = color.alphaComponent
|
||||
}
|
||||
|
||||
var nsColor: NSColor {
|
||||
NSColor(red: red, green: green, blue: blue, alpha: alpha)
|
||||
}
|
||||
}
|
||||
|
||||
public extension NSColor {
|
||||
var pigment: Pigment {
|
||||
Pigment(self)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
39
third-party/SwiftColor/Sources/Pigment+CoreGraphics.swift
vendored
Normal file
39
third-party/SwiftColor/Sources/Pigment+CoreGraphics.swift
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import Foundation
|
||||
#if canImport(CoreGraphics)
|
||||
import CoreGraphics
|
||||
|
||||
public extension Pigment {
|
||||
/// Initialize a `Pigment` using an `CGColor`.
|
||||
init?(_ color: CGColor) {
|
||||
let components = color.components ?? []
|
||||
switch components.count {
|
||||
case 2:
|
||||
// Monochrome/Grayscale
|
||||
// TODO: Express this in 'Color'.
|
||||
red = 0.0
|
||||
green = 0.0
|
||||
blue = 0.0
|
||||
alpha = components[1]
|
||||
case 4:
|
||||
// RGB
|
||||
red = components[0]
|
||||
green = components[1]
|
||||
blue = components[2]
|
||||
alpha = components[3]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension CGColor {
|
||||
static func make(_ color: Pigment) -> CGColor {
|
||||
if color.red == 0.0, color.green == 0.0, color.blue == 0.0, color.alpha == 0.0 {
|
||||
// Return the CG color equivalent of `UIColor.clear`.
|
||||
return CGColor(genericGrayGamma2_2Gray: 0.0, alpha: 0.0)
|
||||
}
|
||||
|
||||
return CGColor(srgbRed: color.red, green: color.green, blue: color.blue, alpha: color.alpha)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
95
third-party/SwiftColor/Sources/Pigment+Float.swift
vendored
Normal file
95
third-party/SwiftColor/Sources/Pigment+Float.swift
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import Foundation
|
||||
|
||||
public extension Pigment {
|
||||
/// Initialize a `Pigment` using `Float` values leaning towards the 'Red' spectrum.
|
||||
///
|
||||
/// - parameters
|
||||
/// - red: A value in the range of 0.0 to 1.0 representing the **red** percent.
|
||||
/// - green: A value in the range of 0.0 to 1.0 representing the **green** percent.
|
||||
/// - blue: A value in the range of 0.0 to 1.0 representing the **blue** percent.
|
||||
/// - alpha: A value in the range of 0.0 to 1.0 representing the **alpha/opacity/transparency** percent.
|
||||
init(
|
||||
red: Float,
|
||||
green: Float = 0.0,
|
||||
blue: Float = 0.0,
|
||||
alpha: Float = 1.0
|
||||
) {
|
||||
self.red = Double(red)
|
||||
self.green = Double(green)
|
||||
self.blue = Double(blue)
|
||||
self.alpha = Double(alpha)
|
||||
}
|
||||
|
||||
/// Initialize a `Pigment` using `Float` values leaning towards the 'Green' spectrum.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - green: A value in the range of 0.0 to 1.0 representing the **green** percent.
|
||||
/// - blue: A value in the range of 0.0 to 1.0 representing the **blue** percent.
|
||||
/// - red: A value in the range of 0.0 to 1.0 representing the **red** percent.
|
||||
/// - alpha: A value in the range of 0.0 to 1.0 representing the **alpha/opacity/transparency** percent.
|
||||
init(
|
||||
green: Float,
|
||||
blue: Float = 0.0,
|
||||
red: Float = 0.0,
|
||||
alpha: Float = 1.0
|
||||
) {
|
||||
self.red = Double(red)
|
||||
self.green = Double(green)
|
||||
self.blue = Double(blue)
|
||||
self.alpha = Double(alpha)
|
||||
}
|
||||
|
||||
/// Initialize a `Pigment` using `Float` values leaning towards the 'Blue' spectrum.
|
||||
///
|
||||
/// - parameter:
|
||||
/// - blue: A value in the range of 0.0 to 1.0 representing the **blue** percent.
|
||||
/// - red: A value in the range of 0.0 to 1.0 representing the **red** percent.
|
||||
/// - green: A value in the range of 0.0 to 1.0 representing the **green** percent.
|
||||
/// - alpha: A value in the range of 0.0 to 1.0 representing the **alpha/opacity/transparency** percent.
|
||||
init(
|
||||
blue: Float,
|
||||
red: Float = 0.0,
|
||||
green: Float = 0.0,
|
||||
alpha: Float = 1.0
|
||||
) {
|
||||
self.red = Double(red)
|
||||
self.green = Double(green)
|
||||
self.blue = Double(blue)
|
||||
self.alpha = Double(alpha)
|
||||
}
|
||||
|
||||
/// Initialize a `Pigment` using variadic `Float` values.
|
||||
///
|
||||
/// All _values_ should be expressed in the range of 0.0 to 1.0.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - values: A number of `Float` which are mapped to **red**, **green**, **blue** in that order.
|
||||
/// - alpha: Amount of _opacity/transparency_ to apply.
|
||||
init(
|
||||
_ values: Float...,
|
||||
alpha: Float
|
||||
) {
|
||||
if values.count > 0 {
|
||||
red = Double(values[0].clamped(to: 0 ... 1))
|
||||
} else {
|
||||
red = 0.0
|
||||
}
|
||||
if values.count > 1 {
|
||||
green = Double(values[1].clamped(to: 0 ... 1))
|
||||
} else {
|
||||
green = 0.0
|
||||
}
|
||||
if values.count > 2 {
|
||||
blue = Double(values[2].clamped(to: 0 ... 1))
|
||||
} else {
|
||||
blue = 0.0
|
||||
}
|
||||
self.alpha = Double(alpha.clamped(to: 0 ... 1))
|
||||
}
|
||||
}
|
||||
|
||||
extension Float {
|
||||
func clamped(to range: ClosedRange<Float>) -> Float {
|
||||
min(max(range.lowerBound, self), range.upperBound)
|
||||
}
|
||||
}
|
||||
145
third-party/SwiftColor/Sources/Pigment+Hex.swift
vendored
Normal file
145
third-party/SwiftColor/Sources/Pigment+Hex.swift
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import Foundation
|
||||
|
||||
public extension Pigment {
|
||||
/// Initializes a `Pigment` with an `Int` in the expected format of **0x000**.
|
||||
///
|
||||
/// Used as a short-hand. If the hex 0x123 is provided, it is interpreted as 0x112233.
|
||||
init(
|
||||
hex3 hex: Int,
|
||||
@Clamping(0 ... 1) alpha: Double = 1.0
|
||||
) {
|
||||
let values = Self.hex3(hex: hex, alpha: alpha)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
self.alpha = values.alpha
|
||||
}
|
||||
|
||||
/// Shorthand **0x0000** initializer similar to `init(hex3:alpha:)` where
|
||||
/// the last digit represent the alpha component.
|
||||
init(
|
||||
hex4 hex: Int
|
||||
) {
|
||||
let values = Self.hex4(hex: hex)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
alpha = values.alpha
|
||||
}
|
||||
|
||||
/// Initializes with a standard format hex representation of color in the form of **0x1E2C3D**.
|
||||
init(
|
||||
hex6 hex: Int,
|
||||
@Clamping(0 ... 1) alpha: Double = 1.0
|
||||
) {
|
||||
let values = Self.hex6(hex: hex, alpha: alpha)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
self.alpha = values.alpha
|
||||
}
|
||||
|
||||
#if !os(watchOS)
|
||||
/// Extended form of `init(hex6:alpha:)` expecting **0x112233FF**, that uses the last
|
||||
/// bits for the alpha component.
|
||||
init(
|
||||
hex8 hex: Int
|
||||
) {
|
||||
let values = Self.hex8(hex: hex)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
alpha = values.alpha
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Initializes a `Pigment` with an `Int` representation of an RGB(a) Hex Value
|
||||
///
|
||||
/// This initializer will do its best to interpret the intentions of what is provided.
|
||||
/// **YOUR RESULTS WILL VARY**, and it's best to use one of the `init(hex?:)` initializers.
|
||||
///
|
||||
/// - Parameter hex: Hex value
|
||||
/// - Parameter alpha: The opacity value of the color object
|
||||
init(
|
||||
_ hex: Int,
|
||||
alpha: Double? = nil
|
||||
) {
|
||||
#if !os(watchOS)
|
||||
if hex > 0xFFFFFF {
|
||||
let values = Self.hex8(hex: hex)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
self.alpha = values.alpha
|
||||
return
|
||||
}
|
||||
#endif
|
||||
if hex > 0xFFFF {
|
||||
let values = Self.hex6(hex: hex, alpha: alpha ?? 1.0)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
self.alpha = values.alpha
|
||||
} else if hex > 0xFFF {
|
||||
let values = Self.hex4(hex: hex)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
self.alpha = values.alpha
|
||||
} else {
|
||||
let values = Self.hex3(hex: hex, alpha: alpha ?? 1.0)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
self.alpha = values.alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Pigment {
|
||||
static func hex3(
|
||||
hex: Int,
|
||||
@Clamping(0 ... 1) alpha: Double = 1.0
|
||||
) -> (red: Double, green: Double, blue: Double, alpha: Double) {
|
||||
let red = Double(duplicateBits((hex & 0xF00) >> 8)) / 255.0
|
||||
let green = Double(duplicateBits((hex & 0x0F0) >> 4)) / 255.0
|
||||
let blue = Double(duplicateBits((hex & 0x00F) >> 0)) / 255.0
|
||||
return (red, green, blue, alpha)
|
||||
}
|
||||
|
||||
static func hex4(
|
||||
hex: Int
|
||||
) -> (red: Double, green: Double, blue: Double, alpha: Double) {
|
||||
let red = Double(duplicateBits((hex & 0xF000) >> 12)) / 255.0
|
||||
let green = Double(duplicateBits((hex & 0x0F00) >> 8)) / 255.0
|
||||
let blue = Double(duplicateBits((hex & 0x00F0) >> 4)) / 255.0
|
||||
let alpha = Double(duplicateBits((hex & 0x000F) >> 0)) / 255.0
|
||||
return (red, green, blue, alpha)
|
||||
}
|
||||
|
||||
static func hex6(
|
||||
hex: Int,
|
||||
@Clamping(0 ... 1) alpha: Double = 1.0
|
||||
) -> (red: Double, green: Double, blue: Double, alpha: Double) {
|
||||
let red = Double((hex & 0xFF0000) >> 16) / 255.0
|
||||
let green = Double((hex & 0x00FF00) >> 8) / 255.0
|
||||
let blue = Double((hex & 0x0000FF) >> 0) / 255.0
|
||||
return (red, green, blue, alpha)
|
||||
}
|
||||
|
||||
#if !os(watchOS)
|
||||
static func hex8(
|
||||
hex: Int
|
||||
) -> (red: Double, green: Double, blue: Double, alpha: Double) {
|
||||
let red = Double((hex & 0xFF00_0000) >> 24) / 255.0
|
||||
let green = Double((hex & 0x00FF_0000) >> 16) / 255.0
|
||||
let blue = Double((hex & 0x0000_FF00) >> 8) / 255.0
|
||||
let alpha = Double((hex & 0x0000_00FF) >> 0) / 255.0
|
||||
return (red, green, blue, alpha)
|
||||
}
|
||||
#endif
|
||||
|
||||
static func duplicateBits(_ value: Int) -> Int {
|
||||
(value << 4) + value
|
||||
}
|
||||
}
|
||||
89
third-party/SwiftColor/Sources/Pigment+Int.swift
vendored
Normal file
89
third-party/SwiftColor/Sources/Pigment+Int.swift
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import Foundation
|
||||
|
||||
public extension Pigment {
|
||||
/// Initialize a `Pigment` using `Int` values leaning towards the 'Red' spectrum.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - red: A value in the range of 0 to 255 representing the **red** bits
|
||||
/// - green: A value in the range of 0 to 255 representing the **green** bits
|
||||
/// - blue: A value in the range of 0 to 255 representing the **blue** bits
|
||||
/// - alpha: A value in the range of 0.0 to 1.0 representing the **alpha/opacity/transparency** percent.
|
||||
init(
|
||||
@Clamping(0 ... 255) red: Int,
|
||||
@Clamping(0 ... 255) green: Int = 0,
|
||||
@Clamping(0 ... 255) blue: Int = 0,
|
||||
@Clamping(0.0 ... 1.0) alpha: Double = 1.0
|
||||
) {
|
||||
self.red = Double(red) / 255.0
|
||||
self.green = Double(green) / 255.0
|
||||
self.blue = Double(blue) / 255.0
|
||||
self.alpha = alpha
|
||||
}
|
||||
|
||||
/// Initialize a `Pigment` using `Int` values leaning towards the 'Green' spectrum.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - green: A value in the range of 0 to 255 representing the **green** bits
|
||||
/// - blue: A value in the range of 0 to 255 representing the **blue** bits
|
||||
/// - red: A value in the range of 0 to 255 representing the **red** bits
|
||||
/// - alpha: A value in the range of 0.0 to 1.0 representing the **alpha/opacity/transparency** percent.
|
||||
init(
|
||||
@Clamping(0 ... 255) green: Int,
|
||||
@Clamping(0 ... 255) blue: Int = 0,
|
||||
@Clamping(0 ... 255) red: Int = 0,
|
||||
@Clamping(0.0 ... 1.0) alpha: Double = 1.0
|
||||
) {
|
||||
self.red = Double(red) / 255.0
|
||||
self.green = Double(green) / 255.0
|
||||
self.blue = Double(blue) / 255.0
|
||||
self.alpha = alpha
|
||||
}
|
||||
|
||||
/// Initialize a `Pigment` using `Int` values leaning towards the 'Blue' spectrum.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - blue: A value in the range of 0 to 255 representing the **blue** bits
|
||||
/// - red: A value in the range of 0 to 255 representing the **red** bits
|
||||
/// - green: A value in the range of 0 to 255 representing the **green** bits
|
||||
/// - alpha: A value in the range of 0.0 to 1.0 representing the **alpha/opacity/transparency** percent.
|
||||
init(
|
||||
@Clamping(0 ... 255) blue: Int,
|
||||
@Clamping(0 ... 255) red: Int = 0,
|
||||
@Clamping(0 ... 255) green: Int = 0,
|
||||
@Clamping(0.0 ... 1.0) alpha: Double = 1.0
|
||||
) {
|
||||
self.red = Double(red) / 255.0
|
||||
self.green = Double(green) / 255.0
|
||||
self.blue = Double(blue) / 255.0
|
||||
self.alpha = alpha
|
||||
}
|
||||
|
||||
/// Initialize a `Pigment` using variadic `Int` values.
|
||||
///
|
||||
/// All _values_ should be expressed in the range of 0 to 255.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - values: A number of `Int` which are mapped to **red**, **green**, **blue** in that order.
|
||||
/// - alpha: A value in the range of 0.0 to 1.0 representing the **alpha/opacity/transparency** percent.
|
||||
init(
|
||||
_ values: Int...,
|
||||
@Clamping(0 ... 1) alpha: Double
|
||||
) {
|
||||
if values.count > 0 {
|
||||
red = Double(values[0]) / 255.0
|
||||
} else {
|
||||
red = 0.0
|
||||
}
|
||||
if values.count > 1 {
|
||||
green = Double(values[1]) / 255.0
|
||||
} else {
|
||||
green = 0.0
|
||||
}
|
||||
if values.count > 2 {
|
||||
blue = Double(values[2]) / 255.0
|
||||
} else {
|
||||
blue = 0.0
|
||||
}
|
||||
self.alpha = alpha
|
||||
}
|
||||
}
|
||||
470
third-party/SwiftColor/Sources/Pigment+Name.swift
vendored
Normal file
470
third-party/SwiftColor/Sources/Pigment+Name.swift
vendored
Normal file
|
|
@ -0,0 +1,470 @@
|
|||
import Foundation
|
||||
|
||||
public extension Pigment {
|
||||
@available(*, deprecated, renamed: "Name")
|
||||
typealias Keyword = Name
|
||||
|
||||
enum Name: String, CaseIterable {
|
||||
case aliceBlue
|
||||
case antiqueWhite
|
||||
case aqua
|
||||
case aquamarine
|
||||
case azure
|
||||
case beige
|
||||
case bisque
|
||||
case black
|
||||
case blanchedAlmond
|
||||
case blue
|
||||
case blueViolet
|
||||
case brown
|
||||
case burlywood
|
||||
case cadetBlue
|
||||
case chartreuse
|
||||
case chocolate
|
||||
case coral
|
||||
case cornflowerBlue
|
||||
case cornsilk
|
||||
case crimson
|
||||
case cyan
|
||||
case darkBlue
|
||||
case darkCyan
|
||||
case darkGoldenrod
|
||||
case darkGray
|
||||
case darkGreen
|
||||
case darkGrey
|
||||
case darkKhaki
|
||||
case darkMagenta
|
||||
case darkOliveGreen
|
||||
case darkOrange
|
||||
case darkOrchid
|
||||
case darkRed
|
||||
case darkSalmon
|
||||
case darkSeagreen
|
||||
case darkSlateBlue
|
||||
case darkSlateGray
|
||||
case darkSlateGrey
|
||||
case darkTurquoise
|
||||
case darkViolet
|
||||
case deepPink
|
||||
case deepSkyblue
|
||||
case dimGray
|
||||
case dimGrey
|
||||
case dodgerBlue
|
||||
case firebrick
|
||||
case floralWhite
|
||||
case forestGreen
|
||||
case fuchsia
|
||||
case gainsboro
|
||||
case ghostWhite
|
||||
case gold
|
||||
case goldenrod
|
||||
case gray
|
||||
case green
|
||||
case greenYellow
|
||||
case grey
|
||||
case honeydew
|
||||
case hotPink
|
||||
case indianRed
|
||||
case indigo
|
||||
case ivory
|
||||
case khaki
|
||||
case lavender
|
||||
case lavenderBlush
|
||||
case lawnGreen
|
||||
case lemonChiffon
|
||||
case lightBlue
|
||||
case lightCoral
|
||||
case lightCyan
|
||||
case lightGoldenrodYellow
|
||||
case lightGray
|
||||
case lightGreen
|
||||
case lightGrey
|
||||
case lightPink
|
||||
case lightSalmon
|
||||
case lightSeagreen
|
||||
case lightSkyBlue
|
||||
case lightSlateGray
|
||||
case lightSlateGrey
|
||||
case lightSteelBlue
|
||||
case lightYellow
|
||||
case lime
|
||||
case limeGreen
|
||||
case linen
|
||||
case magenta
|
||||
case maroon
|
||||
case mediumAquamarine
|
||||
case mediumBlue
|
||||
case mediumOrchid
|
||||
case mediumPurple
|
||||
case mediumSeagreen
|
||||
case mediumSlateBlue
|
||||
case mediumSpringGreen
|
||||
case mediumTurquoise
|
||||
case mediumVioletRed
|
||||
case midnightBlue
|
||||
case mintCream
|
||||
case mistyRose
|
||||
case moccasin
|
||||
case navajoWhite
|
||||
case navy
|
||||
case oldLace
|
||||
case olive
|
||||
case oliveDrab
|
||||
case orange
|
||||
case orangeRed
|
||||
case orchid
|
||||
case paleGoldenrod
|
||||
case paleGreen
|
||||
case paleTurquoise
|
||||
case paleVioletRed
|
||||
case papayaWhip
|
||||
case peachPuff
|
||||
case peru
|
||||
case pink
|
||||
case plum
|
||||
case powderBlue
|
||||
case purple
|
||||
case red
|
||||
case rosyBrown
|
||||
case royalBlue
|
||||
case saddleBrown
|
||||
case salmon
|
||||
case sandyBrown
|
||||
case seagreen
|
||||
case seashell
|
||||
case sienna
|
||||
case silver
|
||||
case skyBlue
|
||||
case slateBlue
|
||||
case slateGray
|
||||
case slateGrey
|
||||
case snow
|
||||
case springGreen
|
||||
case steelBlue
|
||||
case tan
|
||||
case teal
|
||||
case thistle
|
||||
case tomato
|
||||
case turquoise
|
||||
case violet
|
||||
case wheat
|
||||
case white
|
||||
case whitesmoke
|
||||
case yellow
|
||||
case yellowGreen
|
||||
|
||||
public var rgb: (red: Int, green: Int, blue: Int) {
|
||||
switch self {
|
||||
case .aliceBlue: (240, 248, 255)
|
||||
case .antiqueWhite: (250, 235, 215)
|
||||
case .aqua: (0, 255, 255)
|
||||
case .aquamarine: (127, 255, 212)
|
||||
case .azure: (240, 255, 255)
|
||||
case .beige: (245, 245, 220)
|
||||
case .bisque: (255, 228, 196)
|
||||
case .black: (0, 0, 0)
|
||||
case .blanchedAlmond: (255, 235, 205)
|
||||
case .blue: (0, 0, 255)
|
||||
case .blueViolet: (138, 43, 226)
|
||||
case .brown: (165, 42, 42)
|
||||
case .burlywood: (222, 184, 135)
|
||||
case .cadetBlue: (95, 158, 160)
|
||||
case .chartreuse: (127, 255, 0)
|
||||
case .chocolate: (210, 105, 30)
|
||||
case .coral: (255, 127, 80)
|
||||
case .cornflowerBlue: (100, 149, 237)
|
||||
case .cornsilk: (255, 248, 220)
|
||||
case .crimson: (220, 20, 60)
|
||||
case .cyan: (0, 255, 255)
|
||||
case .darkBlue: (0, 0, 139)
|
||||
case .darkCyan: (0, 139, 139)
|
||||
case .darkGoldenrod: (184, 134, 11)
|
||||
case .darkGray: (169, 169, 169)
|
||||
case .darkGreen: (0, 100, 0)
|
||||
case .darkGrey: (169, 169, 169)
|
||||
case .darkKhaki: (189, 183, 107)
|
||||
case .darkMagenta: (139, 0, 139)
|
||||
case .darkOliveGreen: (85, 107, 47)
|
||||
case .darkOrange: (255, 140, 0)
|
||||
case .darkOrchid: (153, 50, 204)
|
||||
case .darkRed: (139, 0, 0)
|
||||
case .darkSalmon: (233, 150, 122)
|
||||
case .darkSeagreen: (143, 188, 143)
|
||||
case .darkSlateBlue: (72, 61, 139)
|
||||
case .darkSlateGray: (47, 79, 79)
|
||||
case .darkSlateGrey: (47, 79, 79)
|
||||
case .darkTurquoise: (0, 206, 209)
|
||||
case .darkViolet: (148, 0, 211)
|
||||
case .deepPink: (255, 20, 147)
|
||||
case .deepSkyblue: (0, 191, 255)
|
||||
case .dimGray: (105, 105, 105)
|
||||
case .dimGrey: (105, 105, 105)
|
||||
case .dodgerBlue: (30, 144, 255)
|
||||
case .firebrick: (178, 34, 34)
|
||||
case .floralWhite: (255, 250, 240)
|
||||
case .forestGreen: (34, 139, 34)
|
||||
case .fuchsia: (255, 0, 255)
|
||||
case .gainsboro: (220, 220, 220)
|
||||
case .ghostWhite: (248, 248, 255)
|
||||
case .gold: (255, 215, 0)
|
||||
case .goldenrod: (218, 165, 32)
|
||||
case .gray: (128, 128, 128)
|
||||
case .green: (0, 128, 0)
|
||||
case .greenYellow: (173, 255, 47)
|
||||
case .grey: (128, 128, 128)
|
||||
case .honeydew: (240, 255, 240)
|
||||
case .hotPink: (255, 105, 180)
|
||||
case .indianRed: (205, 92, 92)
|
||||
case .indigo: (75, 0, 130)
|
||||
case .ivory: (255, 255, 240)
|
||||
case .khaki: (240, 230, 140)
|
||||
case .lavender: (230, 230, 250)
|
||||
case .lavenderBlush: (255, 240, 245)
|
||||
case .lawnGreen: (124, 252, 0)
|
||||
case .lemonChiffon: (255, 250, 205)
|
||||
case .lightBlue: (173, 216, 230)
|
||||
case .lightCoral: (240, 128, 128)
|
||||
case .lightCyan: (224, 255, 255)
|
||||
case .lightGoldenrodYellow: (250, 250, 210)
|
||||
case .lightGray: (211, 211, 211)
|
||||
case .lightGreen: (144, 238, 144)
|
||||
case .lightGrey: (211, 211, 211)
|
||||
case .lightPink: (255, 182, 193)
|
||||
case .lightSalmon: (255, 160, 122)
|
||||
case .lightSeagreen: (32, 178, 170)
|
||||
case .lightSkyBlue: (135, 206, 250)
|
||||
case .lightSlateGray: (119, 136, 153)
|
||||
case .lightSlateGrey: (119, 136, 153)
|
||||
case .lightSteelBlue: (176, 196, 222)
|
||||
case .lightYellow: (255, 255, 224)
|
||||
case .lime: (0, 255, 0)
|
||||
case .limeGreen: (50, 205, 50)
|
||||
case .linen: (250, 240, 230)
|
||||
case .magenta: (255, 0, 255)
|
||||
case .maroon: (128, 0, 0)
|
||||
case .mediumAquamarine: (102, 205, 170)
|
||||
case .mediumBlue: (0, 0, 205)
|
||||
case .mediumOrchid: (186, 85, 211)
|
||||
case .mediumPurple: (147, 112, 219)
|
||||
case .mediumSeagreen: (60, 179, 113)
|
||||
case .mediumSlateBlue: (123, 104, 238)
|
||||
case .mediumSpringGreen: (0, 250, 154)
|
||||
case .mediumTurquoise: (72, 209, 204)
|
||||
case .mediumVioletRed: (199, 21, 133)
|
||||
case .midnightBlue: (25, 25, 112)
|
||||
case .mintCream: (245, 255, 250)
|
||||
case .mistyRose: (255, 228, 225)
|
||||
case .moccasin: (255, 228, 181)
|
||||
case .navajoWhite: (255, 222, 173)
|
||||
case .navy: (0, 0, 128)
|
||||
case .oldLace: (253, 245, 230)
|
||||
case .olive: (128, 128, 0)
|
||||
case .oliveDrab: (107, 142, 35)
|
||||
case .orange: (255, 165, 0)
|
||||
case .orangeRed: (255, 69, 0)
|
||||
case .orchid: (218, 112, 214)
|
||||
case .paleGoldenrod: (238, 232, 170)
|
||||
case .paleGreen: (152, 251, 152)
|
||||
case .paleTurquoise: (175, 238, 238)
|
||||
case .paleVioletRed: (219, 112, 147)
|
||||
case .papayaWhip: (255, 239, 213)
|
||||
case .peachPuff: (255, 218, 185)
|
||||
case .peru: (205, 133, 63)
|
||||
case .pink: (255, 192, 203)
|
||||
case .plum: (221, 160, 221)
|
||||
case .powderBlue: (176, 224, 230)
|
||||
case .purple: (128, 0, 128)
|
||||
case .red: (255, 0, 0)
|
||||
case .rosyBrown: (188, 143, 143)
|
||||
case .royalBlue: (65, 105, 225)
|
||||
case .saddleBrown: (139, 69, 19)
|
||||
case .salmon: (250, 128, 114)
|
||||
case .sandyBrown: (244, 164, 96)
|
||||
case .seagreen: (46, 139, 87)
|
||||
case .seashell: (255, 245, 238)
|
||||
case .sienna: (160, 82, 45)
|
||||
case .silver: (192, 192, 192)
|
||||
case .skyBlue: (135, 206, 235)
|
||||
case .slateBlue: (106, 90, 205)
|
||||
case .slateGray: (112, 128, 144)
|
||||
case .slateGrey: (112, 128, 144)
|
||||
case .snow: (255, 250, 250)
|
||||
case .springGreen: (0, 255, 127)
|
||||
case .steelBlue: (70, 130, 180)
|
||||
case .tan: (210, 180, 140)
|
||||
case .teal: (0, 128, 128)
|
||||
case .thistle: (216, 191, 216)
|
||||
case .tomato: (255, 99, 71)
|
||||
case .turquoise: (64, 224, 208)
|
||||
case .violet: (238, 130, 238)
|
||||
case .wheat: (245, 222, 179)
|
||||
case .white: (255, 255, 255)
|
||||
case .whitesmoke: (245, 245, 245)
|
||||
case .yellow: (255, 255, 0)
|
||||
case .yellowGreen: (154, 205, 50)
|
||||
}
|
||||
}
|
||||
|
||||
public var pigment: Pigment {
|
||||
switch self {
|
||||
case .aliceBlue: Pigment(240, 248, 255, alpha: 1.0)
|
||||
case .antiqueWhite: Pigment(250, 235, 215, alpha: 1.0)
|
||||
case .aqua: Pigment(0, 255, 255, alpha: 1.0)
|
||||
case .aquamarine: Pigment(127, 255, 212, alpha: 1.0)
|
||||
case .azure: Pigment(240, 255, 255, alpha: 1.0)
|
||||
case .beige: Pigment(245, 245, 220, alpha: 1.0)
|
||||
case .bisque: Pigment(255, 228, 196, alpha: 1.0)
|
||||
case .black: Pigment(0, 0, 0, alpha: 1.0)
|
||||
case .blanchedAlmond: Pigment(255, 235, 205, alpha: 1.0)
|
||||
case .blue: Pigment(0, 0, 255, alpha: 1.0)
|
||||
case .blueViolet: Pigment(138, 43, 226, alpha: 1.0)
|
||||
case .brown: Pigment(165, 42, 42, alpha: 1.0)
|
||||
case .burlywood: Pigment(222, 184, 135, alpha: 1.0)
|
||||
case .cadetBlue: Pigment(95, 158, 160, alpha: 1.0)
|
||||
case .chartreuse: Pigment(127, 255, 0, alpha: 1.0)
|
||||
case .chocolate: Pigment(210, 105, 30, alpha: 1.0)
|
||||
case .coral: Pigment(255, 127, 80, alpha: 1.0)
|
||||
case .cornflowerBlue: Pigment(100, 149, 237, alpha: 1.0)
|
||||
case .cornsilk: Pigment(255, 248, 220, alpha: 1.0)
|
||||
case .crimson: Pigment(220, 20, 60, alpha: 1.0)
|
||||
case .cyan: Pigment(0, 255, 255, alpha: 1.0)
|
||||
case .darkBlue: Pigment(0, 0, 139, alpha: 1.0)
|
||||
case .darkCyan: Pigment(0, 139, 139, alpha: 1.0)
|
||||
case .darkGoldenrod: Pigment(184, 134, 11, alpha: 1.0)
|
||||
case .darkGray: Pigment(169, 169, 169, alpha: 1.0)
|
||||
case .darkGreen: Pigment(0, 100, 0, alpha: 1.0)
|
||||
case .darkGrey: Pigment(169, 169, 169, alpha: 1.0)
|
||||
case .darkKhaki: Pigment(189, 183, 107, alpha: 1.0)
|
||||
case .darkMagenta: Pigment(139, 0, 139, alpha: 1.0)
|
||||
case .darkOliveGreen: Pigment(85, 107, 47, alpha: 1.0)
|
||||
case .darkOrange: Pigment(255, 140, 0, alpha: 1.0)
|
||||
case .darkOrchid: Pigment(153, 50, 204, alpha: 1.0)
|
||||
case .darkRed: Pigment(139, 0, 0, alpha: 1.0)
|
||||
case .darkSalmon: Pigment(233, 150, 122, alpha: 1.0)
|
||||
case .darkSeagreen: Pigment(143, 188, 143, alpha: 1.0)
|
||||
case .darkSlateBlue: Pigment(72, 61, 139, alpha: 1.0)
|
||||
case .darkSlateGray: Pigment(47, 79, 79, alpha: 1.0)
|
||||
case .darkSlateGrey: Pigment(47, 79, 79, alpha: 1.0)
|
||||
case .darkTurquoise: Pigment(0, 206, 209, alpha: 1.0)
|
||||
case .darkViolet: Pigment(148, 0, 211, alpha: 1.0)
|
||||
case .deepPink: Pigment(255, 20, 147, alpha: 1.0)
|
||||
case .deepSkyblue: Pigment(0, 191, 255, alpha: 1.0)
|
||||
case .dimGray: Pigment(105, 105, 105, alpha: 1.0)
|
||||
case .dimGrey: Pigment(105, 105, 105, alpha: 1.0)
|
||||
case .dodgerBlue: Pigment(30, 144, 255, alpha: 1.0)
|
||||
case .firebrick: Pigment(178, 34, 34, alpha: 1.0)
|
||||
case .floralWhite: Pigment(255, 250, 240, alpha: 1.0)
|
||||
case .forestGreen: Pigment(34, 139, 34, alpha: 1.0)
|
||||
case .fuchsia: Pigment(255, 0, 255, alpha: 1.0)
|
||||
case .gainsboro: Pigment(220, 220, 220, alpha: 1.0)
|
||||
case .ghostWhite: Pigment(248, 248, 255, alpha: 1.0)
|
||||
case .gold: Pigment(255, 215, 0, alpha: 1.0)
|
||||
case .goldenrod: Pigment(218, 165, 32, alpha: 1.0)
|
||||
case .gray: Pigment(128, 128, 128, alpha: 1.0)
|
||||
case .green: Pigment(0, 128, 0, alpha: 1.0)
|
||||
case .greenYellow: Pigment(173, 255, 47, alpha: 1.0)
|
||||
case .grey: Pigment(128, 128, 128, alpha: 1.0)
|
||||
case .honeydew: Pigment(240, 255, 240, alpha: 1.0)
|
||||
case .hotPink: Pigment(255, 105, 180, alpha: 1.0)
|
||||
case .indianRed: Pigment(205, 92, 92, alpha: 1.0)
|
||||
case .indigo: Pigment(75, 0, 130, alpha: 1.0)
|
||||
case .ivory: Pigment(255, 255, 240, alpha: 1.0)
|
||||
case .khaki: Pigment(240, 230, 140, alpha: 1.0)
|
||||
case .lavender: Pigment(230, 230, 250, alpha: 1.0)
|
||||
case .lavenderBlush: Pigment(255, 240, 245, alpha: 1.0)
|
||||
case .lawnGreen: Pigment(124, 252, 0, alpha: 1.0)
|
||||
case .lemonChiffon: Pigment(255, 250, 205, alpha: 1.0)
|
||||
case .lightBlue: Pigment(173, 216, 230, alpha: 1.0)
|
||||
case .lightCoral: Pigment(240, 128, 128, alpha: 1.0)
|
||||
case .lightCyan: Pigment(224, 255, 255, alpha: 1.0)
|
||||
case .lightGoldenrodYellow: Pigment(250, 250, 210, alpha: 1.0)
|
||||
case .lightGray: Pigment(211, 211, 211, alpha: 1.0)
|
||||
case .lightGreen: Pigment(144, 238, 144, alpha: 1.0)
|
||||
case .lightGrey: Pigment(211, 211, 211, alpha: 1.0)
|
||||
case .lightPink: Pigment(255, 182, 193, alpha: 1.0)
|
||||
case .lightSalmon: Pigment(255, 160, 122, alpha: 1.0)
|
||||
case .lightSeagreen: Pigment(32, 178, 170, alpha: 1.0)
|
||||
case .lightSkyBlue: Pigment(135, 206, 250, alpha: 1.0)
|
||||
case .lightSlateGray: Pigment(119, 136, 153, alpha: 1.0)
|
||||
case .lightSlateGrey: Pigment(119, 136, 153, alpha: 1.0)
|
||||
case .lightSteelBlue: Pigment(176, 196, 222, alpha: 1.0)
|
||||
case .lightYellow: Pigment(255, 255, 224, alpha: 1.0)
|
||||
case .lime: Pigment(0, 255, 0, alpha: 1.0)
|
||||
case .limeGreen: Pigment(50, 205, 50, alpha: 1.0)
|
||||
case .linen: Pigment(250, 240, 230, alpha: 1.0)
|
||||
case .magenta: Pigment(255, 0, 255, alpha: 1.0)
|
||||
case .maroon: Pigment(128, 0, 0, alpha: 1.0)
|
||||
case .mediumAquamarine: Pigment(102, 205, 170, alpha: 1.0)
|
||||
case .mediumBlue: Pigment(0, 0, 205, alpha: 1.0)
|
||||
case .mediumOrchid: Pigment(186, 85, 211, alpha: 1.0)
|
||||
case .mediumPurple: Pigment(147, 112, 219, alpha: 1.0)
|
||||
case .mediumSeagreen: Pigment(60, 179, 113, alpha: 1.0)
|
||||
case .mediumSlateBlue: Pigment(123, 104, 238, alpha: 1.0)
|
||||
case .mediumSpringGreen: Pigment(0, 250, 154, alpha: 1.0)
|
||||
case .mediumTurquoise: Pigment(72, 209, 204, alpha: 1.0)
|
||||
case .mediumVioletRed: Pigment(199, 21, 133, alpha: 1.0)
|
||||
case .midnightBlue: Pigment(25, 25, 112, alpha: 1.0)
|
||||
case .mintCream: Pigment(245, 255, 250, alpha: 1.0)
|
||||
case .mistyRose: Pigment(255, 228, 225, alpha: 1.0)
|
||||
case .moccasin: Pigment(255, 228, 181, alpha: 1.0)
|
||||
case .navajoWhite: Pigment(255, 222, 173, alpha: 1.0)
|
||||
case .navy: Pigment(0, 0, 128, alpha: 1.0)
|
||||
case .oldLace: Pigment(253, 245, 230, alpha: 1.0)
|
||||
case .olive: Pigment(128, 128, 0, alpha: 1.0)
|
||||
case .oliveDrab: Pigment(107, 142, 35, alpha: 1.0)
|
||||
case .orange: Pigment(255, 165, 0, alpha: 1.0)
|
||||
case .orangeRed: Pigment(255, 69, 0, alpha: 1.0)
|
||||
case .orchid: Pigment(218, 112, 214, alpha: 1.0)
|
||||
case .paleGoldenrod: Pigment(238, 232, 170, alpha: 1.0)
|
||||
case .paleGreen: Pigment(152, 251, 152, alpha: 1.0)
|
||||
case .paleTurquoise: Pigment(175, 238, 238, alpha: 1.0)
|
||||
case .paleVioletRed: Pigment(219, 112, 147, alpha: 1.0)
|
||||
case .papayaWhip: Pigment(255, 239, 213, alpha: 1.0)
|
||||
case .peachPuff: Pigment(255, 218, 185, alpha: 1.0)
|
||||
case .peru: Pigment(205, 133, 63, alpha: 1.0)
|
||||
case .pink: Pigment(255, 192, 203, alpha: 1.0)
|
||||
case .plum: Pigment(221, 160, 221, alpha: 1.0)
|
||||
case .powderBlue: Pigment(176, 224, 230, alpha: 1.0)
|
||||
case .purple: Pigment(128, 0, 128, alpha: 1.0)
|
||||
case .red: Pigment(255, 0, 0, alpha: 1.0)
|
||||
case .rosyBrown: Pigment(188, 143, 143, alpha: 1.0)
|
||||
case .royalBlue: Pigment(65, 105, 225, alpha: 1.0)
|
||||
case .saddleBrown: Pigment(139, 69, 19, alpha: 1.0)
|
||||
case .salmon: Pigment(250, 128, 114, alpha: 1.0)
|
||||
case .sandyBrown: Pigment(244, 164, 96, alpha: 1.0)
|
||||
case .seagreen: Pigment(46, 139, 87, alpha: 1.0)
|
||||
case .seashell: Pigment(255, 245, 238, alpha: 1.0)
|
||||
case .sienna: Pigment(160, 82, 45, alpha: 1.0)
|
||||
case .silver: Pigment(192, 192, 192, alpha: 1.0)
|
||||
case .skyBlue: Pigment(135, 206, 235, alpha: 1.0)
|
||||
case .slateBlue: Pigment(106, 90, 205, alpha: 1.0)
|
||||
case .slateGray: Pigment(112, 128, 144, alpha: 1.0)
|
||||
case .slateGrey: Pigment(112, 128, 144, alpha: 1.0)
|
||||
case .snow: Pigment(255, 250, 250, alpha: 1.0)
|
||||
case .springGreen: Pigment(0, 255, 127, alpha: 1.0)
|
||||
case .steelBlue: Pigment(70, 130, 180, alpha: 1.0)
|
||||
case .tan: Pigment(210, 180, 140, alpha: 1.0)
|
||||
case .teal: Pigment(0, 128, 128, alpha: 1.0)
|
||||
case .thistle: Pigment(216, 191, 216, alpha: 1.0)
|
||||
case .tomato: Pigment(255, 99, 71, alpha: 1.0)
|
||||
case .turquoise: Pigment(64, 224, 208, alpha: 1.0)
|
||||
case .violet: Pigment(238, 130, 238, alpha: 1.0)
|
||||
case .wheat: Pigment(245, 222, 179, alpha: 1.0)
|
||||
case .white: Pigment(255, 255, 255, alpha: 1.0)
|
||||
case .whitesmoke: Pigment(245, 245, 245, alpha: 1.0)
|
||||
case .yellow: Pigment(255, 255, 0, alpha: 1.0)
|
||||
case .yellowGreen: Pigment(154, 205, 50, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(
|
||||
_ name: Name,
|
||||
@Clamping(0 ... 1) alpha: Double = 1.0
|
||||
) {
|
||||
red = Double(name.rgb.red) / 255.0
|
||||
green = Double(name.rgb.green) / 255.0
|
||||
blue = Double(name.rgb.blue) / 255.0
|
||||
self.alpha = alpha
|
||||
}
|
||||
}
|
||||
76
third-party/SwiftColor/Sources/Pigment+String.swift
vendored
Normal file
76
third-party/SwiftColor/Sources/Pigment+String.swift
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import Foundation
|
||||
|
||||
public extension Pigment {
|
||||
init(_ value: String, alpha: Double = 1.0) {
|
||||
if let keyword = Name.allCases.first(where: { $0.rawValue.caseInsensitiveCompare(value) == .orderedSame }) {
|
||||
red = keyword.pigment.red
|
||||
green = keyword.pigment.green
|
||||
blue = keyword.pigment.blue
|
||||
self.alpha = alpha
|
||||
return
|
||||
}
|
||||
|
||||
if ExtendedKeyword.allCases.contains(where: { $0.rawValue.caseInsensitiveCompare(value) == .orderedSame }) {
|
||||
red = 1.0
|
||||
green = 1.0
|
||||
blue = 1.0
|
||||
self.alpha = alpha
|
||||
return
|
||||
}
|
||||
|
||||
var hex = value
|
||||
if hex.hasPrefix("#") {
|
||||
hex = String(hex.dropFirst())
|
||||
}
|
||||
|
||||
guard let hexValue = Int(hex, radix: 16) else {
|
||||
red = 1.0
|
||||
green = 1.0
|
||||
blue = 1.0
|
||||
self.alpha = alpha
|
||||
return
|
||||
}
|
||||
|
||||
switch hex.count {
|
||||
case 3:
|
||||
let values = Self.hex3(hex: hexValue)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
self.alpha = values.alpha
|
||||
case 4:
|
||||
let values = Self.hex4(hex: hexValue)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
self.alpha = values.alpha
|
||||
case 6:
|
||||
let values = Self.hex6(hex: hexValue, alpha: alpha)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
self.alpha = values.alpha
|
||||
#if !os(watchOS)
|
||||
case 8:
|
||||
let values = Self.hex8(hex: hexValue)
|
||||
red = values.red
|
||||
green = values.green
|
||||
blue = values.blue
|
||||
self.alpha = values.alpha
|
||||
#endif
|
||||
default:
|
||||
red = 1.0
|
||||
green = 1.0
|
||||
blue = 1.0
|
||||
self.alpha = alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension Pigment {
|
||||
enum ExtendedKeyword: String, CaseIterable {
|
||||
case none
|
||||
case clear
|
||||
case transparent
|
||||
}
|
||||
}
|
||||
9
third-party/SwiftColor/Sources/Pigment+SwiftUI.swift
vendored
Normal file
9
third-party/SwiftColor/Sources/Pigment+SwiftUI.swift
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#if canImport(SwiftUI)
|
||||
import SwiftUI
|
||||
|
||||
public extension Pigment {
|
||||
var color: Color {
|
||||
Color(red: red, green: green, blue: blue, opacity: alpha)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
37
third-party/SwiftColor/Sources/Pigment+UIKit.swift
vendored
Normal file
37
third-party/SwiftColor/Sources/Pigment+UIKit.swift
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import Foundation
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
|
||||
public extension Pigment {
|
||||
init(_ color: UIColor) {
|
||||
var redComponent: CGFloat = 1.0
|
||||
var greenComponent: CGFloat = 1.0
|
||||
var blueComponent: CGFloat = 1.0
|
||||
var alphaComponent: CGFloat = 1.0
|
||||
|
||||
guard color.getRed(&redComponent, green: &greenComponent, blue: &blueComponent, alpha: &alphaComponent) else {
|
||||
// TODO: Fail Initializer? Default Colors?
|
||||
red = redComponent
|
||||
green = greenComponent
|
||||
blue = blueComponent
|
||||
alpha = alphaComponent
|
||||
return
|
||||
}
|
||||
|
||||
red = redComponent
|
||||
green = greenComponent
|
||||
blue = blueComponent
|
||||
alpha = alphaComponent
|
||||
}
|
||||
|
||||
var uiColor: UIColor {
|
||||
UIColor(red: red, green: green, blue: blue, alpha: alpha)
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIColor {
|
||||
var pigment: Pigment {
|
||||
Pigment(self)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
53
third-party/SwiftColor/Sources/Pigment.swift
vendored
Normal file
53
third-party/SwiftColor/Sources/Pigment.swift
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/// A platform agnostic representation of Color
|
||||
///
|
||||
/// The components - red, green, blue, & alpha - are maintained as a floating-point representation.
|
||||
/// Each value can range from 0.0 to 1.0 (e.g. 0 to 100 percent).
|
||||
///
|
||||
/// 'Pure White' is represented by values all equal to **1.0**.
|
||||
public struct Pigment: Sendable {
|
||||
|
||||
public let colorSpace: ColorSpace = .rgba
|
||||
public let red: Double
|
||||
public let green: Double
|
||||
public let blue: Double
|
||||
public let alpha: Double
|
||||
|
||||
public init(
|
||||
@Clamping(0 ... 1) red: Double = 1.0,
|
||||
@Clamping(0 ... 1) green: Double = 1.0,
|
||||
@Clamping(0 ... 1) blue: Double = 1.0,
|
||||
@Clamping(0 ... 1) alpha: Double = 1.0
|
||||
) {
|
||||
self.red = red
|
||||
self.green = green
|
||||
self.blue = blue
|
||||
self.alpha = alpha
|
||||
}
|
||||
}
|
||||
|
||||
extension Pigment: CustomStringConvertible {
|
||||
public var description: String {
|
||||
String(format: "Pigment(red: %.4f, green: %.4f, blue: %.4f, alpha: %.2f)", red, green, blue, alpha)
|
||||
}
|
||||
}
|
||||
|
||||
extension Pigment: Equatable {
|
||||
public static func == (lhs: Pigment, rhs: Pigment) -> Bool {
|
||||
guard lhs.red == rhs.red else {
|
||||
return false
|
||||
}
|
||||
guard lhs.green == rhs.green else {
|
||||
return false
|
||||
}
|
||||
guard lhs.blue == rhs.blue else {
|
||||
return false
|
||||
}
|
||||
guard lhs.alpha == rhs.alpha else {
|
||||
return false
|
||||
}
|
||||
guard lhs.colorSpace == rhs.colorSpace else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
19
third-party/SwiftSVG/BUILD
vendored
Normal file
19
third-party/SwiftSVG/BUILD
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "SwiftSVG",
|
||||
module_name = "SwiftSVG",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//third-party/XMLCoder",
|
||||
"//third-party/Swift2D",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
93
third-party/SwiftSVG/Sources/Circle.swift
vendored
Normal file
93
third-party/SwiftSVG/Sources/Circle.swift
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import Swift2D
|
||||
import XMLCoder
|
||||
|
||||
/// Basic shape, used to draw circles based on a center point and a radius.
|
||||
///
|
||||
/// The arc of a ‘circle’ element begins at the "3 o'clock" point on the radius and progresses towards the
|
||||
/// "9 o'clock" point. The starting point and direction of the arc are affected by the user space transform
|
||||
/// in the same manner as the geometry of the element.
|
||||
///
|
||||
/// ## Documentation
|
||||
/// [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle)
|
||||
/// | [W3](https://www.w3.org/TR/SVG11/shapes.html#CircleElement)
|
||||
public struct Circle: Element {
|
||||
|
||||
/// The x-axis coordinate of the center of the circle.
|
||||
public var x: Double = 0.0
|
||||
/// The y-axis coordinate of the center of the circle.
|
||||
public var y: Double = 0.0
|
||||
/// The radius of the circle.
|
||||
public var r: Double = 0.0
|
||||
|
||||
// MARK: CoreAttributes
|
||||
|
||||
public var id: String?
|
||||
|
||||
// MARK: PresentationAttributes
|
||||
|
||||
public var fillColor: String?
|
||||
public var fillOpacity: Double?
|
||||
public var fillRule: Fill.Rule?
|
||||
public var strokeColor: String?
|
||||
public var strokeWidth: Double?
|
||||
public var strokeOpacity: Double?
|
||||
public var strokeLineCap: Stroke.LineCap?
|
||||
public var strokeLineJoin: Stroke.LineJoin?
|
||||
public var strokeMiterLimit: Double?
|
||||
public var transform: String?
|
||||
|
||||
// MARK: StylingAttributes
|
||||
|
||||
public var style: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case x = "cx"
|
||||
case y = "cy"
|
||||
case r
|
||||
case id
|
||||
case fillColor = "fill"
|
||||
case fillOpacity = "fill-opacity"
|
||||
case fillRule = "fill-rule"
|
||||
case strokeColor = "stroke"
|
||||
case strokeWidth = "stroke-width"
|
||||
case strokeOpacity = "stroke-opacity"
|
||||
case strokeLineCap = "stroke-linecap"
|
||||
case strokeLineJoin = "stroke-linejoin"
|
||||
case strokeMiterLimit = "stroke-miterlimit"
|
||||
case transform
|
||||
case style
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(x: Double, y: Double, r: Double) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.r = r
|
||||
}
|
||||
}
|
||||
|
||||
extension Circle: CustomStringConvertible {
|
||||
public var description: String {
|
||||
let desc = "<circle cx=\"\(x)\" cy=\"\(y)\" r=\"\(r)\""
|
||||
return desc + " \(attributeDescription) />"
|
||||
}
|
||||
}
|
||||
|
||||
extension Circle: DirectionalCommandRepresentable {
|
||||
public func commands(clockwise: Bool) throws -> [Path.Command] {
|
||||
EllipseProcessor(circle: self).commands(clockwise: clockwise)
|
||||
}
|
||||
}
|
||||
|
||||
extension Circle: DynamicNodeDecoding {
|
||||
public static func nodeDecoding(for key: any CodingKey) -> XMLDecoder.NodeDecoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
|
||||
extension Circle: DynamicNodeEncoding {
|
||||
public static func nodeEncoding(for key: any CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
15
third-party/SwiftSVG/Sources/CommandRepresentable.swift
vendored
Normal file
15
third-party/SwiftSVG/Sources/CommandRepresentable.swift
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/// Elements conforming to `CommandRepresentable` can be expressed in the form of `Path.Command`s.
|
||||
public protocol CommandRepresentable {
|
||||
func commands() throws -> [Path.Command]
|
||||
}
|
||||
|
||||
public protocol DirectionalCommandRepresentable: CommandRepresentable {
|
||||
func commands(clockwise: Bool) throws -> [Path.Command]
|
||||
}
|
||||
|
||||
public extension DirectionalCommandRepresentable {
|
||||
/// Defaults to anti/counter-clockwise commands.
|
||||
func commands() throws -> [Path.Command] {
|
||||
try commands(clockwise: false)
|
||||
}
|
||||
}
|
||||
58
third-party/SwiftSVG/Sources/Container.swift
vendored
Normal file
58
third-party/SwiftSVG/Sources/Container.swift
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
public protocol Container {
|
||||
var circles: [Circle]? { get set }
|
||||
var ellipses: [Ellipse]? { get set }
|
||||
var groups: [Group]? { get set }
|
||||
var lines: [Line]? { get set }
|
||||
var paths: [Path]? { get set }
|
||||
var polygons: [Polygon]? { get set }
|
||||
var polylines: [Polyline]? { get set }
|
||||
var rectangles: [Rectangle]? { get set }
|
||||
var texts: [Text]? { get set }
|
||||
}
|
||||
|
||||
enum ContainerKeys: String, CodingKey {
|
||||
case circles = "circle"
|
||||
case ellipses = "ellipse"
|
||||
case groups = "g"
|
||||
case lines = "line"
|
||||
case paths = "path"
|
||||
case polylines = "polyline"
|
||||
case polygons = "polygon"
|
||||
case rectangles = "rect"
|
||||
case texts = "text"
|
||||
}
|
||||
|
||||
public extension Container {
|
||||
var containerDescription: String {
|
||||
var contents: String = ""
|
||||
|
||||
let circles = circles?.compactMap(\.description) ?? []
|
||||
circles.forEach { contents.append("\n\($0)") }
|
||||
|
||||
let ellipses = ellipses?.compactMap(\.description) ?? []
|
||||
ellipses.forEach { contents.append("\n\($0)") }
|
||||
|
||||
let groups = groups?.compactMap(\.description) ?? []
|
||||
groups.forEach { contents.append("\n\($0)") }
|
||||
|
||||
let lines = lines?.compactMap(\.description) ?? []
|
||||
lines.forEach { contents.append("\n\($0)") }
|
||||
|
||||
let paths = paths?.compactMap(\.description) ?? []
|
||||
paths.forEach { contents.append("\n\($0)") }
|
||||
|
||||
let polylines = polylines?.compactMap(\.description) ?? []
|
||||
polylines.forEach { contents.append("\n\($0)") }
|
||||
|
||||
let polygons = polygons?.compactMap(\.description) ?? []
|
||||
polygons.forEach { contents.append("\n\($0)") }
|
||||
|
||||
let rectangles = rectangles?.compactMap(\.description) ?? []
|
||||
rectangles.forEach { contents.append("\n\($0)") }
|
||||
|
||||
let texts = texts?.compactMap(\.description) ?? []
|
||||
texts.forEach { contents.append("\n\($0)") }
|
||||
|
||||
return contents
|
||||
}
|
||||
}
|
||||
17
third-party/SwiftSVG/Sources/CoreAttributes.swift
vendored
Normal file
17
third-party/SwiftSVG/Sources/CoreAttributes.swift
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
public protocol CoreAttributes {
|
||||
var id: String? { get set }
|
||||
}
|
||||
|
||||
enum CoreAttributesKeys: String, CodingKey {
|
||||
case id
|
||||
}
|
||||
|
||||
public extension CoreAttributes {
|
||||
var coreDescription: String {
|
||||
if let id {
|
||||
"\(CoreAttributesKeys.id.rawValue)=\"\(id)\""
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
46
third-party/SwiftSVG/Sources/Element.swift
vendored
Normal file
46
third-party/SwiftSVG/Sources/Element.swift
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
public protocol Element: CoreAttributes, PresentationAttributes, StylingAttributes {}
|
||||
|
||||
public extension Element {
|
||||
var attributeDescription: String {
|
||||
var components: [String] = []
|
||||
|
||||
if !coreDescription.isEmpty {
|
||||
components.append(coreDescription)
|
||||
}
|
||||
if !presentationDescription.isEmpty {
|
||||
components.append(presentationDescription)
|
||||
}
|
||||
if !stylingDescription.isEmpty {
|
||||
components.append(stylingDescription)
|
||||
}
|
||||
|
||||
return components.joined(separator: " ")
|
||||
}
|
||||
}
|
||||
|
||||
public extension CommandRepresentable where Self: Element {
|
||||
/// When a `Path` is accessed on an element, the path that is returned should have the supplied transformations
|
||||
/// applied.
|
||||
///
|
||||
/// For instance, if
|
||||
/// * a `Path.data` contains relative elements,
|
||||
/// * and `transformations` contains a `.translate`
|
||||
///
|
||||
/// Than the path created will not only use 'absolute' instructions, but those instructions will be modified to
|
||||
/// include the required transformation.
|
||||
func path(applying transformations: [Transformation] = []) throws -> Path {
|
||||
var _transformations = transformations
|
||||
_transformations.append(contentsOf: self.transformations)
|
||||
|
||||
let commands = try commands().map { $0.applying(transformations: _transformations) }
|
||||
|
||||
var path = Path(commands: commands)
|
||||
path.fillColor = fillColor
|
||||
path.fillOpacity = fillOpacity
|
||||
path.strokeColor = strokeColor
|
||||
path.strokeOpacity = strokeOpacity
|
||||
path.strokeWidth = strokeWidth
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
93
third-party/SwiftSVG/Sources/Ellipse.swift
vendored
Normal file
93
third-party/SwiftSVG/Sources/Ellipse.swift
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import Swift2D
|
||||
import XMLCoder
|
||||
|
||||
/// SVG basic shape, used to create ellipses based on a center coordinate, and both their x and y radius.
|
||||
///
|
||||
/// The arc of an ‘ellipse’ element begins at the "3 o'clock" point on the radius and progresses towards the
|
||||
/// "9 o'clock" point. The starting point and direction of the arc are affected by the user space transform in the same
|
||||
/// manner as the geometry of the element.
|
||||
public struct Ellipse: Element {
|
||||
|
||||
/// The x position of the ellipse.
|
||||
public var x: Double = 0.0
|
||||
/// The y position of the ellipse.
|
||||
public var y: Double = 0.0
|
||||
/// The radius of the ellipse on the x axis.
|
||||
public var rx: Double = 0.0
|
||||
/// The radius of the ellipse on the y axis.
|
||||
public var ry: Double = 0.0
|
||||
|
||||
// MARK: CoreAttributes
|
||||
|
||||
public var id: String?
|
||||
|
||||
// MARK: PresentationAttributes
|
||||
|
||||
public var fillColor: String?
|
||||
public var fillOpacity: Double?
|
||||
public var fillRule: Fill.Rule?
|
||||
public var strokeColor: String?
|
||||
public var strokeWidth: Double?
|
||||
public var strokeOpacity: Double?
|
||||
public var strokeLineCap: Stroke.LineCap?
|
||||
public var strokeLineJoin: Stroke.LineJoin?
|
||||
public var strokeMiterLimit: Double?
|
||||
public var transform: String?
|
||||
|
||||
// MARK: StylingAttributes
|
||||
|
||||
public var style: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case x = "cx"
|
||||
case y = "cy"
|
||||
case rx
|
||||
case ry
|
||||
case id
|
||||
case fillColor = "fill"
|
||||
case fillOpacity = "fill-opacity"
|
||||
case fillRule = "fill-rule"
|
||||
case strokeColor = "stroke"
|
||||
case strokeWidth = "stroke-width"
|
||||
case strokeOpacity = "stroke-opacity"
|
||||
case strokeLineCap = "stroke-linecap"
|
||||
case strokeLineJoin = "stroke-linejoin"
|
||||
case strokeMiterLimit = "stroke-miterlimit"
|
||||
case transform
|
||||
case style
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(x: Double, y: Double, rx: Double, ry: Double) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.rx = rx
|
||||
self.ry = ry
|
||||
}
|
||||
}
|
||||
|
||||
extension Ellipse: CustomStringConvertible {
|
||||
public var description: String {
|
||||
let desc = "<ellipse cx=\"\(x)\" cy=\"\(y)\" rx=\"\(rx)\" ry=\"\(ry)\""
|
||||
return desc + " \(attributeDescription) />"
|
||||
}
|
||||
}
|
||||
|
||||
extension Ellipse: DirectionalCommandRepresentable {
|
||||
public func commands(clockwise: Bool) throws -> [Path.Command] {
|
||||
EllipseProcessor(ellipse: self).commands(clockwise: clockwise)
|
||||
}
|
||||
}
|
||||
|
||||
extension Ellipse: DynamicNodeDecoding {
|
||||
public static func nodeDecoding(for key: any CodingKey) -> XMLDecoder.NodeDecoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
|
||||
extension Ellipse: DynamicNodeEncoding {
|
||||
public static func nodeEncoding(for key: any CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
37
third-party/SwiftSVG/Sources/Extensions/Point+SwiftSVG.swift
vendored
Normal file
37
third-party/SwiftSVG/Sources/Extensions/Point+SwiftSVG.swift
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import Swift2D
|
||||
|
||||
extension Point {
|
||||
static var nan: Point {
|
||||
Point(x: Double.nan, y: Double.nan)
|
||||
}
|
||||
|
||||
var hasNaN: Bool {
|
||||
x.isNaN || y.isNaN
|
||||
}
|
||||
|
||||
/// Returns a copy of the instance with the **x** value replaced with the provided value.
|
||||
func with(x value: Double) -> Point {
|
||||
Point(x: value, y: y)
|
||||
}
|
||||
|
||||
/// Returns a copy of the instance with the **y** value replaced with the provided value.
|
||||
func with(y value: Double) -> Point {
|
||||
Point(x: x, y: value)
|
||||
}
|
||||
|
||||
/// Adjusts the **x** value by the provided amount.
|
||||
///
|
||||
/// This will explicitly check for `.isNaN`, and if encountered, will simply
|
||||
/// use the provided value.
|
||||
func adjusting(x value: Double) -> Point {
|
||||
(x.isNaN) ? with(x: value) : with(x: x + value)
|
||||
}
|
||||
|
||||
/// Adjusts the **y** value by the provided amount.
|
||||
///
|
||||
/// This will explicitly check for `.isNaN`, and if encountered, will simply
|
||||
/// use the provided value.
|
||||
func adjusting(y value: Double) -> Point {
|
||||
(y.isNaN) ? with(y: value) : with(y: y + value)
|
||||
}
|
||||
}
|
||||
44
third-party/SwiftSVG/Sources/Fill.swift
vendored
Normal file
44
third-party/SwiftSVG/Sources/Fill.swift
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import Swift2D
|
||||
|
||||
public struct Fill {
|
||||
|
||||
public var color: String?
|
||||
public var opacity: Double?
|
||||
public var rule: Rule = .nonZero
|
||||
|
||||
public init() {}
|
||||
|
||||
/// Presentation attribute defining the algorithm to use to determine the inside part of a shape.
|
||||
///
|
||||
/// The default `Rule` is `.nonzero`.
|
||||
public enum Rule: String, Sendable, Codable, CaseIterable {
|
||||
/// The value evenodd determines the "insideness" of a point in the shape by drawing a ray from that point to
|
||||
/// infinity in any direction and counting the number of path segments from the given shape that the ray
|
||||
/// crosses. If this number is odd, the point is inside; if even, the point is outside.
|
||||
case evenOdd = "evenodd"
|
||||
/// The value nonzero determines the "insideness" of a point in the shape by drawing a ray from that point to
|
||||
/// infinity in any direction, and then examining the places where a segment of the shape crosses the ray.
|
||||
/// Starting with a count of zero, add one each time a path segment crosses the ray from left to right and
|
||||
/// subtract one each time a path segment crosses the ray from right to left. After counting the crossings, if
|
||||
/// the result is zero then the point is outside the path. Otherwise, it is inside.
|
||||
case nonZero = "nonzero"
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let rawValue = try container.decode(String.self)
|
||||
guard let rule = Rule(rawValue: rawValue) else {
|
||||
print("Attempts to decode Fill.Rule with rawValue: '\(rawValue)'")
|
||||
self = .nonZero
|
||||
return
|
||||
}
|
||||
|
||||
self = rule
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Fill.Rule: CustomStringConvertible {
|
||||
public var description: String {
|
||||
rawValue
|
||||
}
|
||||
}
|
||||
152
third-party/SwiftSVG/Sources/Group.swift
vendored
Normal file
152
third-party/SwiftSVG/Sources/Group.swift
vendored
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import XMLCoder
|
||||
|
||||
/// A container used to group other SVG elements.
|
||||
///
|
||||
/// Grouping constructs, when used in conjunction with the ‘desc’ and ‘title’ elements, provide information
|
||||
/// about document structure and semantics.
|
||||
///
|
||||
/// ## Documentation
|
||||
/// [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g)
|
||||
/// | [W3](https://www.w3.org/TR/SVG11/struct.html#Groups)
|
||||
public struct Group: Container, Element {
|
||||
|
||||
// Container
|
||||
public var circles: [Circle]?
|
||||
public var ellipses: [Ellipse]?
|
||||
public var groups: [Group]?
|
||||
public var lines: [Line]?
|
||||
public var paths: [Path]?
|
||||
public var polygons: [Polygon]?
|
||||
public var polylines: [Polyline]?
|
||||
public var rectangles: [Rectangle]?
|
||||
public var texts: [Text]?
|
||||
|
||||
// MARK: CoreAttributes
|
||||
|
||||
public var id: String?
|
||||
public var title: String?
|
||||
public var desc: String?
|
||||
|
||||
// MARK: PresentationAttributes
|
||||
|
||||
public var fillColor: String?
|
||||
public var fillOpacity: Double?
|
||||
public var fillRule: Fill.Rule?
|
||||
public var strokeColor: String?
|
||||
public var strokeWidth: Double?
|
||||
public var strokeOpacity: Double?
|
||||
public var strokeLineCap: Stroke.LineCap?
|
||||
public var strokeLineJoin: Stroke.LineJoin?
|
||||
public var strokeMiterLimit: Double?
|
||||
public var transform: String?
|
||||
|
||||
// MARK: StylingAttributes
|
||||
|
||||
public var style: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case circles = "circle"
|
||||
case ellipses = "ellipse"
|
||||
case groups = "g"
|
||||
case lines = "line"
|
||||
case paths = "path"
|
||||
case polylines = "polyline"
|
||||
case polygons = "polygon"
|
||||
case rectangles = "rect"
|
||||
case texts = "text"
|
||||
case id
|
||||
case title
|
||||
case desc
|
||||
case fillColor = "fill"
|
||||
case fillOpacity = "fill-opacity"
|
||||
case fillRule = "fill-rule"
|
||||
case strokeColor = "stroke"
|
||||
case strokeWidth = "stroke-width"
|
||||
case strokeOpacity = "stroke-opacity"
|
||||
case strokeLineCap = "stroke-linecap"
|
||||
case strokeLineJoin = "stroke-linejoin"
|
||||
case strokeMiterLimit = "stroke-miterlimit"
|
||||
case transform
|
||||
case style
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
/// A representation of all the sub-`Path`s in the `Group`.
|
||||
public func subpaths(applying transformations: [Transformation] = []) throws -> [Path] {
|
||||
var _transformations = transformations
|
||||
_transformations.append(contentsOf: self.transformations)
|
||||
|
||||
var output: [Path] = []
|
||||
|
||||
if let circles {
|
||||
try output.append(contentsOf: circles.compactMap { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let ellipses {
|
||||
try output.append(contentsOf: ellipses.compactMap { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let rectangles {
|
||||
try output.append(contentsOf: rectangles.compactMap { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let polygons {
|
||||
try output.append(contentsOf: polygons.compactMap { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let polylines {
|
||||
try output.append(contentsOf: polylines.compactMap { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let paths {
|
||||
try output.append(contentsOf: paths.map { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let groups {
|
||||
try groups.forEach {
|
||||
try output.append(contentsOf: $0.subpaths(applying: _transformations))
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
extension Group: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var contents: String = ""
|
||||
|
||||
if let title {
|
||||
contents.append("\n<title>\(title)</title>")
|
||||
}
|
||||
|
||||
if let desc {
|
||||
contents.append("\n<desc>\(desc)</desc>")
|
||||
}
|
||||
|
||||
contents.append(containerDescription)
|
||||
|
||||
return "<g \(attributeDescription) >\(contents)\n</g>"
|
||||
}
|
||||
}
|
||||
|
||||
extension Group: DynamicNodeDecoding {
|
||||
public static func nodeDecoding(for key: any CodingKey) -> XMLDecoder.NodeDecoding {
|
||||
if let _ = ContainerKeys(stringValue: key.stringValue) {
|
||||
return .element
|
||||
}
|
||||
|
||||
return .attribute
|
||||
}
|
||||
}
|
||||
|
||||
extension Group: DynamicNodeEncoding {
|
||||
public static func nodeEncoding(for key: any CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
if let _ = ContainerKeys(stringValue: key.stringValue) {
|
||||
return .element
|
||||
}
|
||||
|
||||
return .attribute
|
||||
}
|
||||
}
|
||||
88
third-party/SwiftSVG/Sources/Internal/EllipseProcessor.swift
vendored
Normal file
88
third-party/SwiftSVG/Sources/Internal/EllipseProcessor.swift
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import Foundation
|
||||
import Swift2D
|
||||
|
||||
struct EllipseProcessor {
|
||||
|
||||
let x: Double
|
||||
let y: Double
|
||||
let rx: Double
|
||||
let ry: Double
|
||||
|
||||
/// The _optimal_ offset for control points when representing a
|
||||
/// circle/ellipse as 4 bezier curves.
|
||||
///
|
||||
/// [Stack Overflow](https://stackoverflow.com/questions/1734745/how-to-create-circle-with-bézier-curves)
|
||||
static func controlPointOffset(_ radius: Double) -> Double {
|
||||
(Double(4.0 / 3.0) * tan(Double.pi / 8.0)) * radius
|
||||
}
|
||||
|
||||
init(ellipse: Ellipse) {
|
||||
x = ellipse.x
|
||||
y = ellipse.y
|
||||
rx = ellipse.rx
|
||||
ry = ellipse.ry
|
||||
}
|
||||
|
||||
init(circle: Circle) {
|
||||
x = circle.x
|
||||
y = circle.y
|
||||
rx = circle.r
|
||||
ry = circle.r
|
||||
}
|
||||
|
||||
func commands(clockwise: Bool) -> [Path.Command] {
|
||||
var commands: [Path.Command] = []
|
||||
|
||||
let xOffset = Self.controlPointOffset(rx)
|
||||
let yOffset = Self.controlPointOffset(ry)
|
||||
|
||||
let zero = Point(x: x + rx, y: y)
|
||||
let ninety = Point(x: x, y: y - ry)
|
||||
let oneEighty = Point(x: x - rx, y: y)
|
||||
let twoSeventy = Point(x: x, y: y + ry)
|
||||
|
||||
var cp1: Point = .zero
|
||||
var cp2: Point = .zero
|
||||
|
||||
// Starting at degree 0 (the right most point)
|
||||
commands.append(.moveTo(point: zero))
|
||||
|
||||
if clockwise {
|
||||
cp1 = Point(x: zero.x, y: zero.y + yOffset)
|
||||
cp2 = Point(x: twoSeventy.x + xOffset, y: twoSeventy.y)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: twoSeventy))
|
||||
|
||||
cp1 = Point(x: twoSeventy.x - xOffset, y: twoSeventy.y)
|
||||
cp2 = Point(x: oneEighty.x, y: oneEighty.y + yOffset)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: oneEighty))
|
||||
|
||||
cp1 = Point(x: oneEighty.x, y: oneEighty.y - yOffset)
|
||||
cp2 = Point(x: ninety.x - xOffset, y: ninety.y)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: ninety))
|
||||
|
||||
cp1 = Point(x: ninety.x + xOffset, y: ninety.y)
|
||||
cp2 = Point(x: zero.x, y: zero.y - yOffset)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: zero))
|
||||
} else {
|
||||
cp1 = Point(x: zero.x, y: zero.y - yOffset)
|
||||
cp2 = Point(x: ninety.x + xOffset, y: ninety.y)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: ninety))
|
||||
|
||||
cp1 = Point(x: ninety.x - xOffset, y: ninety.y)
|
||||
cp2 = Point(x: oneEighty.x, y: oneEighty.y - yOffset)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: oneEighty))
|
||||
|
||||
cp1 = Point(x: oneEighty.x, y: oneEighty.y + yOffset)
|
||||
cp2 = Point(x: twoSeventy.x - xOffset, y: twoSeventy.y)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: twoSeventy))
|
||||
|
||||
cp1 = Point(x: twoSeventy.x + xOffset, y: twoSeventy.y)
|
||||
cp2 = Point(x: zero.x, y: zero.y + yOffset)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: zero))
|
||||
}
|
||||
|
||||
commands.append(.closePath)
|
||||
|
||||
return commands
|
||||
}
|
||||
}
|
||||
12
third-party/SwiftSVG/Sources/Internal/PathProcessor.swift
vendored
Normal file
12
third-party/SwiftSVG/Sources/Internal/PathProcessor.swift
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import Foundation
|
||||
|
||||
struct PathProcessor {
|
||||
|
||||
let data: String
|
||||
|
||||
func commands() throws -> [Path.Command] {
|
||||
let parser = Path.ComponentParser()
|
||||
let components = try Path.Component.components(from: data)
|
||||
return try parser.parse(components)
|
||||
}
|
||||
}
|
||||
52
third-party/SwiftSVG/Sources/Internal/PolygonProcressor.swift
vendored
Normal file
52
third-party/SwiftSVG/Sources/Internal/PolygonProcressor.swift
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import Foundation
|
||||
import Swift2D
|
||||
|
||||
struct PolygonProcessor {
|
||||
|
||||
let points: String
|
||||
|
||||
func commands() throws -> [Path.Command] {
|
||||
let pairs = points.components(separatedBy: " ")
|
||||
let components = pairs.flatMap { $0.components(separatedBy: ",") }
|
||||
guard components.count > 0 else {
|
||||
return []
|
||||
}
|
||||
|
||||
guard components.count % 2 == 0 else {
|
||||
// An odd number of components means that parsing probably failed
|
||||
return []
|
||||
}
|
||||
|
||||
var commands: [Path.Command] = []
|
||||
|
||||
var firstValue: Bool = true
|
||||
for (idx, component) in components.enumerated() {
|
||||
guard let _value = Double(component) else {
|
||||
return commands
|
||||
}
|
||||
|
||||
let value = Double(_value)
|
||||
|
||||
if firstValue {
|
||||
if idx == 0 {
|
||||
commands.append(.moveTo(point: Point(x: value, y: .nan)))
|
||||
} else {
|
||||
commands.append(.lineTo(point: Point(x: value, y: .nan)))
|
||||
}
|
||||
firstValue = false
|
||||
} else {
|
||||
let count = commands.count
|
||||
guard let modified = try? commands.last?.adjustingArgument(at: 1, by: value) else {
|
||||
return commands
|
||||
}
|
||||
|
||||
commands[count - 1] = modified
|
||||
firstValue = true
|
||||
}
|
||||
}
|
||||
|
||||
commands.append(.closePath)
|
||||
|
||||
return commands
|
||||
}
|
||||
}
|
||||
54
third-party/SwiftSVG/Sources/Internal/PolylineProcessor.swift
vendored
Normal file
54
third-party/SwiftSVG/Sources/Internal/PolylineProcessor.swift
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import Foundation
|
||||
import Swift2D
|
||||
|
||||
struct PolylineProcessor {
|
||||
|
||||
let points: String
|
||||
|
||||
func commands() throws -> [Path.Command] {
|
||||
let pairs = points.components(separatedBy: " ")
|
||||
let components = pairs.flatMap { $0.components(separatedBy: ",") }
|
||||
let values = components.compactMap { Double($0) }.map { Double($0) }
|
||||
|
||||
guard values.count > 2 else {
|
||||
// More than just a starting point is required.
|
||||
return []
|
||||
}
|
||||
|
||||
guard values.count % 2 == 0 else {
|
||||
// An odd number of components means that parsing probably failed
|
||||
return []
|
||||
}
|
||||
|
||||
var commands: [Path.Command] = []
|
||||
|
||||
let move = values.prefix(upTo: 2)
|
||||
let segments = values.suffix(from: 2)
|
||||
|
||||
commands.append(.moveTo(point: Point(x: move[0], y: move[1])))
|
||||
|
||||
var _value: Double = .nan
|
||||
for value in segments {
|
||||
if _value.isNaN {
|
||||
_value = value
|
||||
} else {
|
||||
commands.append(.lineTo(point: Point(x: _value, y: value)))
|
||||
_value = .nan
|
||||
}
|
||||
}
|
||||
|
||||
let reversedSegments = segments.dropLast(2).reversed()
|
||||
for value in reversedSegments {
|
||||
if _value.isNaN {
|
||||
_value = value
|
||||
} else {
|
||||
commands.append(.lineTo(point: Point(x: _value, y: value)))
|
||||
_value = .nan
|
||||
}
|
||||
}
|
||||
|
||||
commands.append(.closePath)
|
||||
|
||||
return commands
|
||||
}
|
||||
}
|
||||
175
third-party/SwiftSVG/Sources/Internal/RectangleProcessor.swift
vendored
Normal file
175
third-party/SwiftSVG/Sources/Internal/RectangleProcessor.swift
vendored
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import Swift2D
|
||||
|
||||
struct RectangleProcessor {
|
||||
|
||||
let rectangle: Rectangle
|
||||
|
||||
func commands(clockwise: Bool) -> [Path.Command] {
|
||||
var rx = rectangle.rx
|
||||
var ry = rectangle.ry
|
||||
|
||||
if let _rx = rx, _rx > (rectangle.width / 2.0) {
|
||||
rx = rectangle.width / 2.0
|
||||
}
|
||||
|
||||
if let _ry = ry, _ry > (rectangle.height / 2.0) {
|
||||
ry = rectangle.height / 2.0
|
||||
}
|
||||
|
||||
var commands: [Path.Command] = []
|
||||
|
||||
switch (rx, ry) {
|
||||
case (.some(let radiusX), .some(let radiusY)) where radiusX != radiusY:
|
||||
// Use Cubic Bezier Curve to form rounded corners
|
||||
// TODO: Verify that the control points are right
|
||||
|
||||
var cp1: Point = .zero
|
||||
var cp2: Point = .zero
|
||||
var point: Point = Point(x: rectangle.x + radiusX, y: rectangle.y)
|
||||
|
||||
commands.append(.moveTo(point: point))
|
||||
|
||||
if clockwise {
|
||||
point = .init(x: rectangle.x + rectangle.width - radiusX, y: rectangle.y)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp1 = .init(x: rectangle.x + rectangle.width, y: rectangle.y)
|
||||
cp2 = cp1
|
||||
point = .init(x: rectangle.x + rectangle.width, y: rectangle.y + radiusY)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: point))
|
||||
|
||||
point = .init(x: rectangle.x + rectangle.width, y: rectangle.y + rectangle.height - radiusY)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp1 = .init(x: rectangle.x + rectangle.width, y: rectangle.y + rectangle.height)
|
||||
cp2 = cp1
|
||||
point = .init(x: rectangle.x + rectangle.width - radiusX, y: rectangle.y + rectangle.height)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: point))
|
||||
|
||||
point = .init(x: rectangle.x + radiusX, y: rectangle.y + rectangle.height)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp1 = .init(x: rectangle.x, y: rectangle.y + rectangle.height)
|
||||
cp2 = cp1
|
||||
point = .init(x: rectangle.x, y: rectangle.y + rectangle.height - radiusY)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: point))
|
||||
|
||||
point = .init(x: rectangle.x, y: rectangle.y + radiusY)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp1 = .init(x: rectangle.x, y: rectangle.y)
|
||||
cp2 = cp1
|
||||
point = .init(x: rectangle.x + radiusX, y: rectangle.y)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: point))
|
||||
} else {
|
||||
cp1 = .init(x: rectangle.x, y: rectangle.y)
|
||||
cp2 = cp1
|
||||
point = .init(x: rectangle.x, y: rectangle.y + radiusY)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: point))
|
||||
|
||||
point = .init(x: rectangle.x, y: rectangle.y + rectangle.height - radiusY)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp1 = .init(x: rectangle.x, y: rectangle.y + rectangle.height)
|
||||
cp2 = cp1
|
||||
point = .init(x: rectangle.x + radiusX, y: rectangle.y + rectangle.height)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: point))
|
||||
|
||||
point = .init(x: rectangle.x + rectangle.width - radiusX, y: rectangle.y + rectangle.height)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp1 = .init(x: rectangle.x + rectangle.width, y: rectangle.y + rectangle.height)
|
||||
cp2 = cp1
|
||||
point = .init(x: rectangle.x + rectangle.width, y: rectangle.y + rectangle.height - radiusY)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: point))
|
||||
|
||||
point = .init(x: rectangle.x + rectangle.width, y: rectangle.y + radiusY)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp1 = .init(x: rectangle.x + rectangle.width, y: rectangle.y)
|
||||
cp2 = cp1
|
||||
point = .init(x: rectangle.x + rectangle.width - radiusX, y: rectangle.y)
|
||||
commands.append(.cubicBezierCurve(cp1: cp1, cp2: cp2, point: point))
|
||||
}
|
||||
case (.some(let radius), .none), (.none, .some(let radius)), (.some(let radius), _):
|
||||
// use Quadratic Bezier Curve to form rounded corners
|
||||
|
||||
var cp: Point = .zero
|
||||
var point: Point = Point(x: rectangle.x + radius, y: rectangle.y)
|
||||
|
||||
commands.append(.moveTo(point: point))
|
||||
|
||||
if clockwise {
|
||||
point = .init(x: (rectangle.x + rectangle.width) - radius, y: rectangle.y)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp = .init(x: rectangle.x + rectangle.width, y: rectangle.y)
|
||||
point = .init(x: rectangle.x + rectangle.width, y: rectangle.y + radius)
|
||||
commands.append(.quadraticBezierCurve(cp: cp, point: point))
|
||||
|
||||
point = .init(x: rectangle.x + rectangle.width, y: (rectangle.y + rectangle.height) - radius)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp = .init(x: rectangle.x + rectangle.width, y: rectangle.y + rectangle.height)
|
||||
point = .init(x: rectangle.x + rectangle.width - radius, y: rectangle.y + rectangle.height)
|
||||
commands.append(.quadraticBezierCurve(cp: cp, point: point))
|
||||
|
||||
point = .init(x: rectangle.x + radius, y: rectangle.y + rectangle.height)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp = .init(x: rectangle.x, y: rectangle.y + rectangle.height)
|
||||
point = .init(x: rectangle.x, y: rectangle.y + rectangle.height - radius)
|
||||
commands.append(.quadraticBezierCurve(cp: cp, point: point))
|
||||
|
||||
point = .init(x: rectangle.x, y: rectangle.y + radius)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp = .init(x: rectangle.x, y: rectangle.y)
|
||||
point = .init(x: rectangle.x + radius, y: rectangle.y)
|
||||
commands.append(.quadraticBezierCurve(cp: cp, point: point))
|
||||
} else {
|
||||
cp = .init(x: rectangle.x, y: rectangle.y)
|
||||
point = .init(x: rectangle.x, y: rectangle.y + radius)
|
||||
commands.append(.quadraticBezierCurve(cp: cp, point: point))
|
||||
|
||||
point = .init(x: rectangle.x, y: rectangle.y + rectangle.height - radius)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp = .init(x: rectangle.x, y: rectangle.y + rectangle.height)
|
||||
point = .init(x: rectangle.x + radius, y: rectangle.y + rectangle.height)
|
||||
commands.append(.quadraticBezierCurve(cp: cp, point: point))
|
||||
|
||||
point = .init(x: rectangle.x + rectangle.width - radius, y: rectangle.y + rectangle.height)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp = .init(x: rectangle.x + rectangle.width, y: rectangle.y + rectangle.height)
|
||||
point = .init(x: rectangle.x + rectangle.width, y: rectangle.y + rectangle.height - radius)
|
||||
commands.append(.quadraticBezierCurve(cp: cp, point: point))
|
||||
|
||||
point = .init(x: rectangle.x + rectangle.width, y: rectangle.y + radius)
|
||||
commands.append(.lineTo(point: point))
|
||||
|
||||
cp = .init(x: rectangle.x + rectangle.width, y: rectangle.y)
|
||||
point = .init(x: rectangle.x + rectangle.width - radius, y: rectangle.y)
|
||||
commands.append(.quadraticBezierCurve(cp: cp, point: point))
|
||||
}
|
||||
case (.none, .none):
|
||||
// draw three line segments.
|
||||
commands.append(.moveTo(point: Point(x: rectangle.x, y: rectangle.y)))
|
||||
|
||||
if clockwise {
|
||||
commands.append(.lineTo(point: Point(x: rectangle.x + rectangle.width, y: rectangle.y)))
|
||||
commands.append(.lineTo(point: Point(x: rectangle.x + rectangle.width, y: rectangle.y + rectangle.height)))
|
||||
commands.append(.lineTo(point: Point(x: rectangle.x, y: rectangle.y + rectangle.height)))
|
||||
} else {
|
||||
commands.append(.lineTo(point: Point(x: rectangle.x, y: rectangle.y + rectangle.height)))
|
||||
commands.append(.lineTo(point: Point(x: rectangle.x + rectangle.width, y: rectangle.y + rectangle.height)))
|
||||
commands.append(.lineTo(point: Point(x: rectangle.x + rectangle.width, y: rectangle.y)))
|
||||
}
|
||||
}
|
||||
|
||||
commands.append(.closePath)
|
||||
|
||||
return commands
|
||||
}
|
||||
}
|
||||
98
third-party/SwiftSVG/Sources/Line.swift
vendored
Normal file
98
third-party/SwiftSVG/Sources/Line.swift
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import Swift2D
|
||||
import XMLCoder
|
||||
|
||||
/// SVG basic shape used to create a line connecting two points.
|
||||
///
|
||||
/// ## Documentation
|
||||
/// [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)
|
||||
/// | [W3](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)
|
||||
public struct Line: Element {
|
||||
|
||||
/// Defines the x-axis coordinate of the line starting point.
|
||||
public var x1: Double = 0.0
|
||||
/// Defines the x-axis coordinate of the line ending point.
|
||||
public var y1: Double = 0.0
|
||||
/// Defines the y-axis coordinate of the line starting point.
|
||||
public var x2: Double = 0.0
|
||||
/// Defines the y-axis coordinate of the line ending point.
|
||||
public var y2: Double = 0.0
|
||||
|
||||
// MARK: CoreAttributes
|
||||
|
||||
public var id: String?
|
||||
|
||||
// MARK: PresentationAttributes
|
||||
|
||||
public var fillColor: String?
|
||||
public var fillOpacity: Double?
|
||||
public var fillRule: Fill.Rule?
|
||||
public var strokeColor: String?
|
||||
public var strokeWidth: Double?
|
||||
public var strokeOpacity: Double?
|
||||
public var strokeLineCap: Stroke.LineCap?
|
||||
public var strokeLineJoin: Stroke.LineJoin?
|
||||
public var strokeMiterLimit: Double?
|
||||
public var transform: String?
|
||||
|
||||
// MARK: StylingAttributes
|
||||
|
||||
public var style: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case x1
|
||||
case y1
|
||||
case x2
|
||||
case y2
|
||||
case id
|
||||
case fillColor = "fill"
|
||||
case fillOpacity = "fill-opacity"
|
||||
case fillRule = "fill-rule"
|
||||
case strokeColor = "stroke"
|
||||
case strokeWidth = "stroke-width"
|
||||
case strokeOpacity = "stroke-opacity"
|
||||
case strokeLineCap = "stroke-linecap"
|
||||
case strokeLineJoin = "stroke-linejoin"
|
||||
case strokeMiterLimit = "stroke-miterlimit"
|
||||
case transform
|
||||
case style
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(x1: Double, y1: Double, x2: Double, y2: Double) {
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
}
|
||||
}
|
||||
|
||||
extension Line: CommandRepresentable {
|
||||
public func commands() throws -> [Path.Command] {
|
||||
[
|
||||
.moveTo(point: Point(x: x1, y: y1)),
|
||||
.lineTo(point: Point(x: x2, y: y2)),
|
||||
.lineTo(point: Point(x: x1, y: y1)),
|
||||
.closePath,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
extension Line: CustomStringConvertible {
|
||||
public var description: String {
|
||||
let desc = "<line x1=\"\(x1)\" y1=\"\(y1)\" x2=\"\(x2)\" y2=\"\(y2)\""
|
||||
return desc + " \(attributeDescription) />"
|
||||
}
|
||||
}
|
||||
|
||||
extension Line: DynamicNodeDecoding {
|
||||
public static func nodeDecoding(for key: any CodingKey) -> XMLDecoder.NodeDecoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
|
||||
extension Line: DynamicNodeEncoding {
|
||||
public static func nodeEncoding(for key: any CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
276
third-party/SwiftSVG/Sources/Path.Command.swift
vendored
Normal file
276
third-party/SwiftSVG/Sources/Path.Command.swift
vendored
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
import Foundation
|
||||
import Swift2D
|
||||
|
||||
public extension Path {
|
||||
/// Path commands are instructions that define a path to be drawn.
|
||||
///
|
||||
/// Each command is composed of a command letter and numbers that represent the command parameters.
|
||||
enum Command: Equatable, Sendable, CustomStringConvertible {
|
||||
/// Moves the current drawing point
|
||||
case moveTo(point: Point)
|
||||
/// Draw a straight line from the current point to the point provided
|
||||
case lineTo(point: Point)
|
||||
/// Draw a smooth curve using three points (+ origin)
|
||||
case cubicBezierCurve(cp1: Point, cp2: Point, point: Point)
|
||||
/// Draw a smooth curve using two points (+ origin)
|
||||
case quadraticBezierCurve(cp: Point, point: Point)
|
||||
/// Draw a curve defined as a portion of an ellipse
|
||||
case ellipticalArcCurve(rx: Double, ry: Double, angle: Double, largeArc: Bool, clockwise: Bool, point: Point)
|
||||
/// ClosePath instructions draw a straight line from the current position to the first point in the path.
|
||||
case closePath
|
||||
|
||||
public enum Prefix: Character, CaseIterable {
|
||||
case move = "M"
|
||||
case relativeMove = "m"
|
||||
case line = "L"
|
||||
case relativeLine = "l"
|
||||
case horizontalLine = "H"
|
||||
case relativeHorizontalLine = "h"
|
||||
case verticalLine = "V"
|
||||
case relativeVerticalLine = "v"
|
||||
case cubicBezierCurve = "C"
|
||||
case relativeCubicBezierCurve = "c"
|
||||
case smoothCubicBezierCurve = "S"
|
||||
case relativeSmoothCubicBezierCurve = "s"
|
||||
case quadraticBezierCurve = "Q"
|
||||
case relativeQuadraticBezierCurve = "q"
|
||||
case smoothQuadraticBezierCurve = "T"
|
||||
case relativeSmoothQuadraticBezierCurve = "t"
|
||||
case ellipticalArcCurve = "A"
|
||||
case relativeEllipticalArcCurve = "a"
|
||||
case close = "Z"
|
||||
case relativeClose = "z"
|
||||
|
||||
public static var characterSet: CharacterSet {
|
||||
CharacterSet(charactersIn: allCases.map { String($0.rawValue) }.joined())
|
||||
}
|
||||
}
|
||||
|
||||
public enum Coordinates {
|
||||
case absolute
|
||||
case relative
|
||||
}
|
||||
|
||||
public enum Error: Swift.Error {
|
||||
case message(String)
|
||||
case invalidAdjustment(Path.Command)
|
||||
case invalidArgumentPosition(Int, Path.Command)
|
||||
case invalidRelativeCommand
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .moveTo(let point):
|
||||
return "\(Prefix.move.rawValue)\(point.x),\(point.y)"
|
||||
case .lineTo(let point):
|
||||
return "\(Prefix.line.rawValue)\(point.x),\(point.y)"
|
||||
case .cubicBezierCurve(let cp1, let cp2, let point):
|
||||
return "\(Prefix.cubicBezierCurve.rawValue)\(cp1.x),\(cp1.y) \(cp2.x),\(cp2.y) \(point.x),\(point.y)"
|
||||
case .quadraticBezierCurve(let cp, let point):
|
||||
return "\(Prefix.quadraticBezierCurve.rawValue)\(cp.x),\(cp.y) \(point.x),\(point.y)"
|
||||
case .ellipticalArcCurve(let rx, let ry, let angle, let largeArc, let clockwise, let point):
|
||||
let la = largeArc ? 1 : 0
|
||||
let cw = clockwise ? 1 : 0
|
||||
return "\(Prefix.ellipticalArcCurve.rawValue)\(rx) \(ry) \(angle) \(la) \(cw) \(point.x) \(point.y)"
|
||||
case .closePath:
|
||||
return "\(Prefix.close.rawValue)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The primary point that dictates the commands action.
|
||||
public var point: Point {
|
||||
switch self {
|
||||
case .moveTo(let point): point
|
||||
case .lineTo(let point): point
|
||||
case .cubicBezierCurve(_, _, let point): point
|
||||
case .quadraticBezierCurve(_, let point): point
|
||||
case .ellipticalArcCurve(_, _, _, _, _, let point): point
|
||||
case .closePath: .zero
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Path.Command {
|
||||
/// Applies the provided `Transformation` to the instances values.
|
||||
func applying(transformation: Transformation) -> Path.Command {
|
||||
switch transformation {
|
||||
case .translate(let x, let y):
|
||||
switch self {
|
||||
case .moveTo(let point):
|
||||
let _point = point.adjusting(x: x).adjusting(y: y)
|
||||
return .moveTo(point: _point)
|
||||
case .lineTo(let point):
|
||||
let _point = point.adjusting(x: x).adjusting(y: y)
|
||||
return .lineTo(point: _point)
|
||||
case .cubicBezierCurve(let cp1, let cp2, let point):
|
||||
let _cp1 = cp1.adjusting(x: x).adjusting(y: y)
|
||||
let _cp2 = cp2.adjusting(x: x).adjusting(y: y)
|
||||
let _point = point.adjusting(x: x).adjusting(y: y)
|
||||
return .cubicBezierCurve(cp1: _cp1, cp2: _cp2, point: _point)
|
||||
case .quadraticBezierCurve(let cp, let point):
|
||||
let _cp = cp.adjusting(x: x).adjusting(y: y)
|
||||
let _point = point.adjusting(x: x).adjusting(y: y)
|
||||
return .quadraticBezierCurve(cp: _cp, point: _point)
|
||||
case .ellipticalArcCurve(let rx, let ry, let angle, let largeArc, let clockwise, let point):
|
||||
let _point = point.adjusting(x: x).adjusting(y: y)
|
||||
return .ellipticalArcCurve(rx: rx, ry: ry, angle: angle, largeArc: largeArc, clockwise: clockwise, point: _point)
|
||||
case .closePath:
|
||||
return self
|
||||
}
|
||||
case .matrix:
|
||||
// TODO: What should occur here?
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies multiple transformations in the order they are specified.
|
||||
func applying(transformations: [Transformation]) -> Path.Command {
|
||||
var command = self
|
||||
|
||||
for transformation in transformations {
|
||||
command = command.applying(transformation: transformation)
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
}
|
||||
|
||||
extension Path.Command {
|
||||
/// Determines if all values are provided (i.e. !.isNaN)
|
||||
var isComplete: Bool {
|
||||
switch self {
|
||||
case .moveTo(let point), .lineTo(let point):
|
||||
!point.hasNaN
|
||||
case .cubicBezierCurve(let cp1, let cp2, let point):
|
||||
!cp1.hasNaN && !cp2.hasNaN && !point.hasNaN
|
||||
case .quadraticBezierCurve(let cp, let point):
|
||||
!cp.hasNaN && !point.hasNaN
|
||||
case .ellipticalArcCurve(let rx, let ry, let angle, _, _, let point):
|
||||
!rx.isNaN && !ry.isNaN && !angle.isNaN && !point.hasNaN
|
||||
case .closePath:
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// The last control point used in drawing the path.
|
||||
///
|
||||
/// Only valid for curves.
|
||||
var lastControlPoint: Point? {
|
||||
switch self {
|
||||
case .cubicBezierCurve(_, let cp2, _):
|
||||
cp2
|
||||
case .quadraticBezierCurve(let cp, _):
|
||||
cp
|
||||
default:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
/// A mirror representation of `lastControlPoint`.
|
||||
var lastControlPointMirror: Point? {
|
||||
guard let cp = lastControlPoint else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Point(x: point.x + (point.x - cp.x), y: point.y + (point.y - cp.y))
|
||||
}
|
||||
|
||||
/// The total number of argument values the command requires.
|
||||
var arguments: Int {
|
||||
switch self {
|
||||
case .moveTo: 2
|
||||
case .lineTo: 2
|
||||
case .cubicBezierCurve: 6
|
||||
case .quadraticBezierCurve: 4
|
||||
case .ellipticalArcCurve: 7
|
||||
case .closePath: 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjusts a Command argument by a specified amount.
|
||||
///
|
||||
/// A `Point` consumes two positions. So, in the example `.quadraticBezierCurve(cp: .zero, point: .zero)`:
|
||||
/// * position 0 = Control Point X
|
||||
/// * position 1 = Control Point Y
|
||||
/// * position 2 = Point X
|
||||
/// * position 3 = Point Y
|
||||
///
|
||||
/// - parameter position: The index of the argument parameter to adjust.
|
||||
/// - parameter value: The value to add to the existing value. If the current value equal `.isNaN`, than the
|
||||
/// supplied value is used as-is.
|
||||
/// - throws: `Path.Command.Error`
|
||||
func adjustingArgument(at position: Int, by value: Double) throws -> Path.Command {
|
||||
switch self {
|
||||
case .moveTo(let point):
|
||||
switch position {
|
||||
case 0:
|
||||
return .moveTo(point: point.adjusting(x: value))
|
||||
case 1:
|
||||
return .moveTo(point: point.adjusting(y: value))
|
||||
default:
|
||||
throw Path.Command.Error.invalidArgumentPosition(position, self)
|
||||
}
|
||||
case .lineTo(let point):
|
||||
switch position {
|
||||
case 0:
|
||||
return .lineTo(point: point.adjusting(x: value))
|
||||
case 1:
|
||||
return .lineTo(point: point.adjusting(y: value))
|
||||
default:
|
||||
throw Path.Command.Error.invalidArgumentPosition(position, self)
|
||||
}
|
||||
case .cubicBezierCurve(let cp1, let cp2, let point):
|
||||
switch position {
|
||||
case 0:
|
||||
return .cubicBezierCurve(cp1: cp1.adjusting(x: value), cp2: cp2, point: point)
|
||||
case 1:
|
||||
return .cubicBezierCurve(cp1: cp1.adjusting(y: value), cp2: cp2, point: point)
|
||||
case 2:
|
||||
return .cubicBezierCurve(cp1: cp1, cp2: cp2.adjusting(x: value), point: point)
|
||||
case 3:
|
||||
return .cubicBezierCurve(cp1: cp1, cp2: cp2.adjusting(y: value), point: point)
|
||||
case 4:
|
||||
return .cubicBezierCurve(cp1: cp1, cp2: cp2, point: point.adjusting(x: value))
|
||||
case 5:
|
||||
return .cubicBezierCurve(cp1: cp1, cp2: cp2, point: point.adjusting(y: value))
|
||||
default:
|
||||
throw Path.Command.Error.invalidArgumentPosition(position, self)
|
||||
}
|
||||
case .quadraticBezierCurve(let cp, let point):
|
||||
switch position {
|
||||
case 0:
|
||||
return .quadraticBezierCurve(cp: cp.adjusting(x: value), point: point)
|
||||
case 1:
|
||||
return .quadraticBezierCurve(cp: cp.adjusting(y: value), point: point)
|
||||
case 2:
|
||||
return .quadraticBezierCurve(cp: cp, point: point.adjusting(x: value))
|
||||
case 3:
|
||||
return .quadraticBezierCurve(cp: cp, point: point.adjusting(y: value))
|
||||
default:
|
||||
throw Path.Command.Error.invalidArgumentPosition(position, self)
|
||||
}
|
||||
case .ellipticalArcCurve(let rx, let ry, let angle, let largeArc, let clockwise, let point):
|
||||
switch position {
|
||||
case 0:
|
||||
return .ellipticalArcCurve(rx: value, ry: ry, angle: angle, largeArc: largeArc, clockwise: clockwise, point: point)
|
||||
case 1:
|
||||
return .ellipticalArcCurve(rx: rx, ry: value, angle: angle, largeArc: largeArc, clockwise: clockwise, point: point)
|
||||
case 2:
|
||||
return .ellipticalArcCurve(rx: rx, ry: ry, angle: value, largeArc: largeArc, clockwise: clockwise, point: point)
|
||||
case 3:
|
||||
return .ellipticalArcCurve(rx: rx, ry: ry, angle: angle, largeArc: !value.isZero, clockwise: clockwise, point: point)
|
||||
case 4:
|
||||
return .ellipticalArcCurve(rx: rx, ry: ry, angle: angle, largeArc: largeArc, clockwise: !value.isZero, point: point)
|
||||
case 5:
|
||||
return .ellipticalArcCurve(rx: rx, ry: ry, angle: angle, largeArc: largeArc, clockwise: clockwise, point: point.adjusting(x: value))
|
||||
case 6:
|
||||
return .ellipticalArcCurve(rx: rx, ry: ry, angle: angle, largeArc: largeArc, clockwise: clockwise, point: point.adjusting(y: value))
|
||||
default:
|
||||
throw Path.Command.Error.invalidArgumentPosition(position, self)
|
||||
}
|
||||
case .closePath:
|
||||
throw Path.Command.Error.invalidAdjustment(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
98
third-party/SwiftSVG/Sources/Path.Component.swift
vendored
Normal file
98
third-party/SwiftSVG/Sources/Path.Component.swift
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import Foundation
|
||||
|
||||
public extension Path {
|
||||
/// A unit of a SVG path data string.
|
||||
enum Component {
|
||||
case prefix(Command.Prefix)
|
||||
case value(Double)
|
||||
|
||||
/// Interprets a `Path` `data` attribute into individual `Component`s for command processing.
|
||||
public static func components(from data: String) throws -> [Component] {
|
||||
var blocks: [String] = []
|
||||
var block: String = ""
|
||||
|
||||
for scalar in data.unicodeScalars {
|
||||
if scalar == "e" {
|
||||
// Account for exponential value notation.
|
||||
block.append(String(scalar))
|
||||
continue
|
||||
}
|
||||
|
||||
if CharacterSet.letters.contains(scalar) {
|
||||
if !block.isEmpty {
|
||||
blocks.append(block)
|
||||
block = ""
|
||||
}
|
||||
|
||||
blocks.append(String(scalar))
|
||||
continue
|
||||
}
|
||||
|
||||
if CharacterSet.whitespaces.contains(scalar) {
|
||||
if !block.isEmpty {
|
||||
blocks.append(block)
|
||||
block = ""
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if CharacterSet(charactersIn: ",").contains(scalar) {
|
||||
if !block.isEmpty {
|
||||
blocks.append(block)
|
||||
block = ""
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if CharacterSet(charactersIn: "-").contains(scalar) {
|
||||
if !block.isEmpty, block.last != "e" {
|
||||
// Again, account for exponential values.
|
||||
blocks.append(block)
|
||||
block = ""
|
||||
}
|
||||
|
||||
block.append(String(scalar))
|
||||
continue
|
||||
}
|
||||
|
||||
if CharacterSet(charactersIn: ".").contains(scalar) {
|
||||
if block.contains(".") {
|
||||
// Already decimal value, this is a new value
|
||||
blocks.append(block)
|
||||
block = ""
|
||||
}
|
||||
|
||||
block.append(String(scalar))
|
||||
continue
|
||||
}
|
||||
|
||||
if CharacterSet.decimalDigits.contains(scalar) {
|
||||
block.append(String(scalar))
|
||||
continue
|
||||
}
|
||||
|
||||
print("Unhandled Character: \(scalar)")
|
||||
}
|
||||
|
||||
if !block.isEmpty {
|
||||
blocks.append(block)
|
||||
block = ""
|
||||
}
|
||||
|
||||
return blocks
|
||||
.filter { !$0.isEmpty }
|
||||
.compactMap {
|
||||
if let prefix = Path.Command.Prefix(rawValue: $0.first!) {
|
||||
.prefix(prefix)
|
||||
} else if let value = Double($0) {
|
||||
.value(value)
|
||||
} else {
|
||||
// throw in the future?
|
||||
nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
275
third-party/SwiftSVG/Sources/Path.ComponentParser.swift
vendored
Normal file
275
third-party/SwiftSVG/Sources/Path.ComponentParser.swift
vendored
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
import Swift2D
|
||||
|
||||
public extension Path {
|
||||
/// Utility used to construct a collection of `Path.Command` from a collection of `Path.Component`.
|
||||
class ComponentParser {
|
||||
/// The command currently being built
|
||||
private var command: Path.Command?
|
||||
/// Coordinate system being used
|
||||
private var coordinates: Path.Command.Coordinates = .absolute
|
||||
/// The argument position of the _command_ to be processed.
|
||||
private var position: Int = 0
|
||||
/// Indicates that only a single value will be processed on the next component pass.
|
||||
private var singleValue: Bool = false
|
||||
/// The originating coordinates of the path.
|
||||
private var pathOrigin: Point = .nan
|
||||
/// The last point as processed by the parser.
|
||||
private var currentPoint: Point = .zero
|
||||
|
||||
public init() {}
|
||||
|
||||
public func parse(_ components: [Path.Component]) throws -> [Path.Command] {
|
||||
var commands: [Path.Command] = []
|
||||
|
||||
try components.forEach { component in
|
||||
if let command = try parse(component, lastCommand: commands.last) {
|
||||
commands.append(command)
|
||||
}
|
||||
}
|
||||
|
||||
return commands
|
||||
}
|
||||
|
||||
private func parse(_ component: Path.Component, lastCommand: Path.Command?) throws -> Path.Command? {
|
||||
switch component {
|
||||
case .prefix(let prefix):
|
||||
setup(prefix: prefix, lastCommand: lastCommand)
|
||||
case .value(let value):
|
||||
try process(value: value, lastCommand: lastCommand)
|
||||
}
|
||||
}
|
||||
|
||||
private func setup(prefix: Path.Command.Prefix, lastCommand: Path.Command?) -> Path.Command? {
|
||||
position = 0
|
||||
singleValue = false
|
||||
|
||||
switch prefix {
|
||||
case .move:
|
||||
command = .moveTo(point: .nan)
|
||||
coordinates = .absolute
|
||||
case .relativeMove:
|
||||
command = .moveTo(point: currentPoint)
|
||||
coordinates = .relative
|
||||
case .line:
|
||||
command = .lineTo(point: .nan)
|
||||
coordinates = .absolute
|
||||
case .relativeLine:
|
||||
command = .lineTo(point: currentPoint)
|
||||
coordinates = .relative
|
||||
case .horizontalLine:
|
||||
command = .lineTo(point: currentPoint.with(x: .nan))
|
||||
coordinates = .absolute
|
||||
case .relativeHorizontalLine:
|
||||
command = .lineTo(point: currentPoint)
|
||||
coordinates = .relative
|
||||
singleValue = true
|
||||
case .verticalLine:
|
||||
command = .lineTo(point: currentPoint.with(y: .nan))
|
||||
coordinates = .absolute
|
||||
position = 1
|
||||
case .relativeVerticalLine:
|
||||
command = .lineTo(point: currentPoint)
|
||||
coordinates = .relative
|
||||
position = 1
|
||||
singleValue = true
|
||||
case .cubicBezierCurve:
|
||||
command = .cubicBezierCurve(cp1: .nan, cp2: .nan, point: .nan)
|
||||
coordinates = .absolute
|
||||
case .relativeCubicBezierCurve:
|
||||
command = .cubicBezierCurve(cp1: currentPoint, cp2: currentPoint, point: currentPoint)
|
||||
coordinates = .relative
|
||||
case .smoothCubicBezierCurve:
|
||||
if case .cubicBezierCurve(_, let cp, _) = lastCommand {
|
||||
command = .cubicBezierCurve(cp1: cp.reflecting(around: currentPoint), cp2: .nan, point: .nan)
|
||||
} else {
|
||||
command = .cubicBezierCurve(cp1: currentPoint, cp2: .nan, point: .nan)
|
||||
}
|
||||
coordinates = .absolute
|
||||
position = 2
|
||||
case .relativeSmoothCubicBezierCurve:
|
||||
if case .cubicBezierCurve(_, let cp, _) = lastCommand {
|
||||
command = .cubicBezierCurve(cp1: cp.reflecting(around: cp.reflecting(around: currentPoint)), cp2: currentPoint, point: currentPoint)
|
||||
} else {
|
||||
command = .cubicBezierCurve(cp1: currentPoint, cp2: currentPoint, point: currentPoint)
|
||||
}
|
||||
coordinates = .relative
|
||||
position = 2
|
||||
case .quadraticBezierCurve:
|
||||
command = .quadraticBezierCurve(cp: .nan, point: .nan)
|
||||
coordinates = .absolute
|
||||
case .relativeQuadraticBezierCurve:
|
||||
command = .quadraticBezierCurve(cp: currentPoint, point: currentPoint)
|
||||
coordinates = .relative
|
||||
case .smoothQuadraticBezierCurve:
|
||||
if case .quadraticBezierCurve(let cp, _) = lastCommand {
|
||||
command = .quadraticBezierCurve(cp: cp.reflecting(around: currentPoint), point: .nan)
|
||||
} else {
|
||||
command = .quadraticBezierCurve(cp: currentPoint, point: .nan)
|
||||
}
|
||||
coordinates = .absolute
|
||||
position = 2
|
||||
case .relativeSmoothQuadraticBezierCurve:
|
||||
if case .quadraticBezierCurve(let cp, _) = lastCommand {
|
||||
command = .quadraticBezierCurve(cp: cp.reflecting(around: currentPoint), point: currentPoint)
|
||||
} else {
|
||||
command = .quadraticBezierCurve(cp: currentPoint, point: currentPoint)
|
||||
}
|
||||
coordinates = .relative
|
||||
position = 2
|
||||
case .ellipticalArcCurve:
|
||||
command = .ellipticalArcCurve(rx: .nan, ry: .nan, angle: .nan, largeArc: false, clockwise: false, point: .nan)
|
||||
coordinates = .absolute
|
||||
case .relativeEllipticalArcCurve:
|
||||
command = .ellipticalArcCurve(rx: .nan, ry: .nan, angle: .nan, largeArc: false, clockwise: false, point: currentPoint)
|
||||
coordinates = .relative
|
||||
case .close, .relativeClose:
|
||||
currentPoint = pathOrigin
|
||||
reset()
|
||||
return .closePath
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func process(value: Double, lastCommand: Path.Command?) throws -> Path.Command? {
|
||||
if let command {
|
||||
try continueCommand(command, with: value)
|
||||
} else {
|
||||
try nextCommand(with: value, lastCommand: lastCommand)
|
||||
}
|
||||
|
||||
if let command, command.isComplete {
|
||||
switch coordinates {
|
||||
case .relative:
|
||||
guard position == -1 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
fallthrough
|
||||
case .absolute:
|
||||
currentPoint = command.point
|
||||
if case .moveTo = command {
|
||||
pathOrigin = command.point
|
||||
}
|
||||
reset()
|
||||
return command
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func continueCommand(_ command: Path.Command, with value: Double) throws {
|
||||
switch command {
|
||||
case .moveTo, .cubicBezierCurve, .quadraticBezierCurve, .ellipticalArcCurve:
|
||||
self.command = try command.adjustingArgument(at: position, by: value)
|
||||
switch coordinates {
|
||||
case .absolute:
|
||||
position += 1
|
||||
case .relative:
|
||||
switch position {
|
||||
case 0 ... (command.arguments - 2):
|
||||
position += 1
|
||||
case command.arguments - 1:
|
||||
position = -1
|
||||
default:
|
||||
break // throw?
|
||||
}
|
||||
}
|
||||
case .lineTo:
|
||||
self.command = try command.adjustingArgument(at: position, by: value)
|
||||
switch coordinates {
|
||||
case .absolute:
|
||||
position += 1
|
||||
case .relative:
|
||||
switch position {
|
||||
case 0:
|
||||
if singleValue {
|
||||
singleValue = false
|
||||
position = -1
|
||||
} else {
|
||||
position += 1
|
||||
}
|
||||
case 1:
|
||||
if singleValue {
|
||||
singleValue = false
|
||||
}
|
||||
position = -1
|
||||
default:
|
||||
break // throw?
|
||||
}
|
||||
}
|
||||
case .closePath:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func nextCommand(with value: Double, lastCommand: Path.Command?) throws {
|
||||
guard let command = lastCommand else {
|
||||
throw Path.Command.Error.invalidRelativeCommand
|
||||
}
|
||||
|
||||
switch command {
|
||||
case .moveTo:
|
||||
switch coordinates {
|
||||
case .absolute:
|
||||
self.command = .lineTo(point: Point(x: value, y: .nan))
|
||||
position = 1
|
||||
case .relative:
|
||||
let c = Path.Command.lineTo(point: command.point)
|
||||
self.command = try c.adjustingArgument(at: 0, by: value)
|
||||
position = 1
|
||||
}
|
||||
case .lineTo:
|
||||
switch coordinates {
|
||||
case .absolute:
|
||||
self.command = .lineTo(point: Point(x: value, y: .nan))
|
||||
position = 1
|
||||
case .relative:
|
||||
let c = Path.Command.lineTo(point: command.point)
|
||||
self.command = try c.adjustingArgument(at: 0, by: value)
|
||||
position = 1
|
||||
}
|
||||
case .cubicBezierCurve:
|
||||
switch coordinates {
|
||||
case .absolute:
|
||||
self.command = .cubicBezierCurve(cp1: Point(x: value, y: .nan), cp2: .nan, point: .nan)
|
||||
position = 1
|
||||
case .relative:
|
||||
let c = Path.Command.cubicBezierCurve(cp1: command.point, cp2: command.point, point: command.point)
|
||||
self.command = try c.adjustingArgument(at: 0, by: value)
|
||||
position = 1
|
||||
}
|
||||
case .quadraticBezierCurve:
|
||||
switch coordinates {
|
||||
case .absolute:
|
||||
self.command = .quadraticBezierCurve(cp: Point(x: value, y: .nan), point: .nan)
|
||||
position = 1
|
||||
case .relative:
|
||||
let c = Path.Command.quadraticBezierCurve(cp: command.point, point: command.point)
|
||||
self.command = try c.adjustingArgument(at: 0, by: value)
|
||||
position = 1
|
||||
}
|
||||
case .ellipticalArcCurve:
|
||||
switch coordinates {
|
||||
case .absolute:
|
||||
self.command = .ellipticalArcCurve(rx: value, ry: .nan, angle: .nan, largeArc: false, clockwise: false, point: .nan)
|
||||
position = 1
|
||||
case .relative:
|
||||
let c = Path.Command.ellipticalArcCurve(rx: .nan, ry: .nan, angle: .nan, largeArc: false, clockwise: false, point: command.point)
|
||||
self.command = try c.adjustingArgument(at: 0, by: value)
|
||||
position = 1
|
||||
}
|
||||
case .closePath:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func reset() {
|
||||
command = nil
|
||||
coordinates = .absolute
|
||||
position = 0
|
||||
singleValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
101
third-party/SwiftSVG/Sources/Path.swift
vendored
Normal file
101
third-party/SwiftSVG/Sources/Path.swift
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import XMLCoder
|
||||
|
||||
/// Generic element to define a shape.
|
||||
///
|
||||
/// A path is defined by including a ‘path’ element in a SVG document which contains a **d="(path data)"**
|
||||
/// attribute, where the **‘d’** attribute contains the moveto, line, curve (both Cubic and Quadratic Bézier),
|
||||
/// arc and closepath instructions.
|
||||
///
|
||||
/// ## Documentation
|
||||
/// [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)
|
||||
/// | [W3](https://www.w3.org/TR/SVG11/paths.html)
|
||||
public struct Path: Element {
|
||||
|
||||
/// The definition of the outline of a shape.
|
||||
public var data: String = ""
|
||||
|
||||
// MARK: CoreAttributes
|
||||
|
||||
public var id: String?
|
||||
|
||||
// MARK: PresentationAttributes
|
||||
|
||||
public var fillColor: String?
|
||||
public var fillOpacity: Double?
|
||||
public var fillRule: Fill.Rule?
|
||||
public var strokeColor: String?
|
||||
public var strokeWidth: Double?
|
||||
public var strokeOpacity: Double?
|
||||
public var strokeLineCap: Stroke.LineCap?
|
||||
public var strokeLineJoin: Stroke.LineJoin?
|
||||
public var strokeMiterLimit: Double?
|
||||
public var transform: String?
|
||||
|
||||
// MARK: StylingAttributes
|
||||
|
||||
public var style: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case data = "d"
|
||||
case id
|
||||
case fillColor = "fill"
|
||||
case fillOpacity = "fill-opacity"
|
||||
case fillRule = "fill-rule"
|
||||
case strokeColor = "stroke"
|
||||
case strokeWidth = "stroke-width"
|
||||
case strokeOpacity = "stroke-opacity"
|
||||
case strokeLineCap = "stroke-linecap"
|
||||
case strokeLineJoin = "stroke-linejoin"
|
||||
case strokeMiterLimit = "stroke-miterlimit"
|
||||
case transform
|
||||
case style
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(data: String) {
|
||||
self.init()
|
||||
self.data = data
|
||||
}
|
||||
|
||||
public init(commands: [Path.Command]) {
|
||||
self.init()
|
||||
data = commands.map(\.description).joined()
|
||||
}
|
||||
}
|
||||
|
||||
extension Path: CommandRepresentable {
|
||||
public func commands() throws -> [Command] {
|
||||
try PathProcessor(data: data).commands()
|
||||
}
|
||||
}
|
||||
|
||||
extension Path: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"<path d=\"\(data)\" \(attributeDescription) />"
|
||||
}
|
||||
}
|
||||
|
||||
extension Path: DynamicNodeDecoding {
|
||||
public static func nodeDecoding(for key: any CodingKey) -> XMLDecoder.NodeDecoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
|
||||
extension Path: DynamicNodeEncoding {
|
||||
public static func nodeEncoding(for key: any CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
|
||||
extension Path: Equatable {
|
||||
public static func == (lhs: Path, rhs: Path) -> Bool {
|
||||
do {
|
||||
let lhsCommands = try lhs.commands()
|
||||
let rhsCommands = try rhs.commands()
|
||||
return lhsCommands == rhsCommands
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
82
third-party/SwiftSVG/Sources/Polygon.swift
vendored
Normal file
82
third-party/SwiftSVG/Sources/Polygon.swift
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import XMLCoder
|
||||
|
||||
/// Defines a closed shape consisting of a set of connected straight line segments.
|
||||
///
|
||||
/// The last point is connected to the first point. For open shapes, see the `Polyline` element. If an odd number of
|
||||
/// coordinates is provided, then the element is in error.
|
||||
///
|
||||
/// ## Documentation
|
||||
/// [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon)
|
||||
/// | [W3](https://www.w3.org/TR/SVG11/shapes.html#PolygonElement)
|
||||
public struct Polygon: Element {
|
||||
|
||||
/// The points that make up the polygon.
|
||||
public var points: String = ""
|
||||
|
||||
// MARK: CoreAttributes
|
||||
|
||||
public var id: String?
|
||||
|
||||
// MARK: PresentationAttributes
|
||||
|
||||
public var fillColor: String?
|
||||
public var fillOpacity: Double?
|
||||
public var fillRule: Fill.Rule?
|
||||
public var strokeColor: String?
|
||||
public var strokeWidth: Double?
|
||||
public var strokeOpacity: Double?
|
||||
public var strokeLineCap: Stroke.LineCap?
|
||||
public var strokeLineJoin: Stroke.LineJoin?
|
||||
public var strokeMiterLimit: Double?
|
||||
public var transform: String?
|
||||
|
||||
// MARK: StylingAttributes
|
||||
|
||||
public var style: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case points
|
||||
case id
|
||||
case fillColor = "fill"
|
||||
case fillOpacity = "fill-opacity"
|
||||
case fillRule = "fill-rule"
|
||||
case strokeColor = "stroke"
|
||||
case strokeWidth = "stroke-width"
|
||||
case strokeOpacity = "stroke-opacity"
|
||||
case strokeLineCap = "stroke-linecap"
|
||||
case strokeLineJoin = "stroke-linejoin"
|
||||
case strokeMiterLimit = "stroke-miterlimit"
|
||||
case transform
|
||||
case style
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(points: String) {
|
||||
self.points = points
|
||||
}
|
||||
}
|
||||
|
||||
extension Polygon: CommandRepresentable {
|
||||
public func commands() throws -> [Path.Command] {
|
||||
try PolygonProcessor(points: points).commands()
|
||||
}
|
||||
}
|
||||
|
||||
extension Polygon: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"<polygon points=\"\(points)\" \(attributeDescription) />"
|
||||
}
|
||||
}
|
||||
|
||||
extension Polygon: DynamicNodeDecoding {
|
||||
public static func nodeDecoding(for key: any CodingKey) -> XMLDecoder.NodeDecoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
|
||||
extension Polygon: DynamicNodeEncoding {
|
||||
public static func nodeEncoding(for key: any CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
81
third-party/SwiftSVG/Sources/Polyline.swift
vendored
Normal file
81
third-party/SwiftSVG/Sources/Polyline.swift
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import XMLCoder
|
||||
|
||||
/// SVG basic shape that creates straight lines connecting several points.
|
||||
///
|
||||
/// Typically a polyline is used to create open shapes as the last point doesn't have to be connected to the first
|
||||
/// point. For closed shapes see the `Polygon` element.
|
||||
///
|
||||
/// ## Documentation
|
||||
/// [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline)
|
||||
/// | [W3](https://www.w3.org/TR/SVG11/shapes.html#PolylineElement)
|
||||
public struct Polyline: Element {
|
||||
|
||||
public var points: String = ""
|
||||
|
||||
// MARK: CoreAttributes
|
||||
|
||||
public var id: String?
|
||||
|
||||
// MARK: PresentationAttributes
|
||||
|
||||
public var fillColor: String?
|
||||
public var fillOpacity: Double?
|
||||
public var fillRule: Fill.Rule?
|
||||
public var strokeColor: String?
|
||||
public var strokeWidth: Double?
|
||||
public var strokeOpacity: Double?
|
||||
public var strokeLineCap: Stroke.LineCap?
|
||||
public var strokeLineJoin: Stroke.LineJoin?
|
||||
public var strokeMiterLimit: Double?
|
||||
public var transform: String?
|
||||
|
||||
// MARK: StylingAttributes
|
||||
|
||||
public var style: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case points
|
||||
case id
|
||||
case fillColor = "fill"
|
||||
case fillOpacity = "fill-opacity"
|
||||
case fillRule = "fill-rule"
|
||||
case strokeColor = "stroke"
|
||||
case strokeWidth = "stroke-width"
|
||||
case strokeOpacity = "stroke-opacity"
|
||||
case strokeLineCap = "stroke-linecap"
|
||||
case strokeLineJoin = "stroke-linejoin"
|
||||
case strokeMiterLimit = "stroke-miterlimit"
|
||||
case transform
|
||||
case style
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(points: String) {
|
||||
self.points = points
|
||||
}
|
||||
}
|
||||
|
||||
extension Polyline: CommandRepresentable {
|
||||
public func commands() throws -> [Path.Command] {
|
||||
try PolylineProcessor(points: points).commands()
|
||||
}
|
||||
}
|
||||
|
||||
extension Polyline: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"<polyline points=\"\(points)\" \(attributeDescription) />"
|
||||
}
|
||||
}
|
||||
|
||||
extension Polyline: DynamicNodeDecoding {
|
||||
public static func nodeDecoding(for key: any CodingKey) -> XMLDecoder.NodeDecoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
|
||||
extension Polyline: DynamicNodeEncoding {
|
||||
public static func nodeEncoding(for key: any CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
120
third-party/SwiftSVG/Sources/PresentationAttributes.swift
vendored
Normal file
120
third-party/SwiftSVG/Sources/PresentationAttributes.swift
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import Foundation
|
||||
import Swift2D
|
||||
|
||||
public protocol PresentationAttributes {
|
||||
var fillColor: String? { get set }
|
||||
var fillOpacity: Double? { get set }
|
||||
var fillRule: Fill.Rule? { get set }
|
||||
var strokeColor: String? { get set }
|
||||
var strokeWidth: Double? { get set }
|
||||
var strokeOpacity: Double? { get set }
|
||||
var strokeLineCap: Stroke.LineCap? { get set }
|
||||
var strokeLineJoin: Stroke.LineJoin? { get set }
|
||||
var strokeMiterLimit: Double? { get set }
|
||||
var transform: String? { get set }
|
||||
}
|
||||
|
||||
enum PresentationAttributesKeys: String, CodingKey {
|
||||
case fillColor = "fill"
|
||||
case fillOpacity = "fill-opacity"
|
||||
case fillRule = "fill-rule"
|
||||
case strokeColor = "stroke"
|
||||
case strokeWidth = "stroke-width"
|
||||
case strokeOpacity = "stroke-opacity"
|
||||
case strokeLineCap = "stroke-linecap"
|
||||
case strokeLineJoin = "stroke-linejoin"
|
||||
case strokeMiterLimit = "stroke-miterlimit"
|
||||
case transform
|
||||
}
|
||||
|
||||
public extension PresentationAttributes {
|
||||
var presentationDescription: String {
|
||||
var attributes: [String] = []
|
||||
|
||||
if let fillColor {
|
||||
attributes.append("\(PresentationAttributesKeys.fillColor.rawValue)=\"\(fillColor)\"")
|
||||
}
|
||||
if let fillOpacity {
|
||||
attributes.append("\(PresentationAttributesKeys.fillOpacity.rawValue)=\"\(fillOpacity)\"")
|
||||
}
|
||||
if let fillRule {
|
||||
attributes.append("\(PresentationAttributesKeys.fillRule.rawValue)=\"\(fillRule.description)\"")
|
||||
}
|
||||
if let strokeColor {
|
||||
attributes.append("\(PresentationAttributesKeys.strokeColor.rawValue)=\"\(strokeColor)\"")
|
||||
}
|
||||
if let strokeWidth {
|
||||
attributes.append("\(PresentationAttributesKeys.strokeWidth.rawValue)=\"\(strokeWidth)\"")
|
||||
}
|
||||
if let strokeOpacity {
|
||||
attributes.append("\(PresentationAttributesKeys.strokeOpacity.rawValue)=\"\(strokeOpacity)\"")
|
||||
}
|
||||
if let strokeLineCap {
|
||||
attributes.append("\(PresentationAttributesKeys.strokeLineCap.rawValue)=\"\(strokeLineCap.description)\"")
|
||||
}
|
||||
if let strokeLineJoin {
|
||||
attributes.append("\(PresentationAttributesKeys.strokeLineJoin.rawValue)=\"\(strokeLineJoin.description)\"")
|
||||
}
|
||||
if let strokeMiterLimit {
|
||||
attributes.append("\(PresentationAttributesKeys.strokeMiterLimit.rawValue)=\"\(strokeMiterLimit)\"")
|
||||
}
|
||||
if let transform {
|
||||
attributes.append("\(PresentationAttributesKeys.transform.rawValue)=\"\(transform)\"")
|
||||
}
|
||||
|
||||
return attributes.joined(separator: " ")
|
||||
}
|
||||
|
||||
var transformations: [Transformation] {
|
||||
let value = transform?.replacingOccurrences(of: " ", with: "") ?? ""
|
||||
guard !value.isEmpty else {
|
||||
return []
|
||||
}
|
||||
|
||||
let values = value.split(separator: ")").map { $0.appending(")") }
|
||||
return values.compactMap { Transformation($0) }
|
||||
}
|
||||
|
||||
var fill: Fill? {
|
||||
get {
|
||||
if fillColor == nil, fillOpacity == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var fill = Fill()
|
||||
fill.color = fillColor ?? "black"
|
||||
fill.opacity = fillOpacity ?? 1.0
|
||||
return fill
|
||||
}
|
||||
set {
|
||||
fillColor = newValue?.color
|
||||
fillOpacity = newValue?.opacity
|
||||
fillRule = newValue?.rule
|
||||
}
|
||||
}
|
||||
|
||||
var stroke: Stroke? {
|
||||
get {
|
||||
if strokeColor == nil, strokeOpacity == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var stroke = Stroke()
|
||||
stroke.color = strokeColor ?? "black"
|
||||
stroke.opacity = strokeOpacity ?? 1.0
|
||||
stroke.width = strokeWidth ?? 1.0
|
||||
stroke.lineCap = strokeLineCap ?? .butt
|
||||
stroke.lineJoin = strokeLineJoin ?? .miter
|
||||
stroke.miterLimit = strokeMiterLimit
|
||||
return stroke
|
||||
}
|
||||
set {
|
||||
strokeColor = newValue?.color
|
||||
strokeOpacity = newValue?.opacity
|
||||
strokeWidth = newValue?.width
|
||||
strokeLineCap = newValue?.lineCap
|
||||
strokeLineJoin = newValue?.lineJoin
|
||||
strokeMiterLimit = newValue?.miterLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
115
third-party/SwiftSVG/Sources/Rectangle.swift
vendored
Normal file
115
third-party/SwiftSVG/Sources/Rectangle.swift
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import Swift2D
|
||||
import XMLCoder
|
||||
|
||||
/// Basic SVG shape that draws rectangles, defined by their position, width, and height.
|
||||
///
|
||||
/// The values used for the x- and y-axis rounded corner radii are determined implicitly
|
||||
/// if the ‘rx’ or ‘ry’ attributes (or both) are not specified, or are specified but with invalid values.
|
||||
///
|
||||
/// ## Documentation
|
||||
/// [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect)
|
||||
/// | [W3](https://www.w3.org/TR/SVG11/shapes.html#RectElement)
|
||||
public struct Rectangle: Element {
|
||||
|
||||
/// The x-axis coordinate of the side of the rectangle which
|
||||
/// has the smaller x-axis coordinate value.
|
||||
public var x: Double = 0.0
|
||||
/// The y-axis coordinate of the side of the rectangle which
|
||||
/// has the smaller y-axis coordinate value
|
||||
public var y: Double = 0.0
|
||||
/// The width of the rectangle.
|
||||
public var width: Double = 0.0
|
||||
/// The height of the rectangle.
|
||||
public var height: Double = 0.0
|
||||
/// For rounded rectangles, the x-axis radius of the ellipse used
|
||||
/// to round off the corners of the rectangle.
|
||||
public var rx: Double?
|
||||
/// For rounded rectangles, the y-axis radius of the ellipse used
|
||||
/// to round off the corners of the rectangle.
|
||||
public var ry: Double?
|
||||
|
||||
// MARK: CoreAttributes
|
||||
|
||||
public var id: String?
|
||||
|
||||
// MARK: PresentationAttributes
|
||||
|
||||
public var fillColor: String?
|
||||
public var fillOpacity: Double?
|
||||
public var fillRule: Fill.Rule?
|
||||
public var strokeColor: String?
|
||||
public var strokeWidth: Double?
|
||||
public var strokeOpacity: Double?
|
||||
public var strokeLineCap: Stroke.LineCap?
|
||||
public var strokeLineJoin: Stroke.LineJoin?
|
||||
public var strokeMiterLimit: Double?
|
||||
public var transform: String?
|
||||
|
||||
// MARK: StylingAttributes
|
||||
|
||||
public var style: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case x
|
||||
case y
|
||||
case width
|
||||
case height
|
||||
case rx
|
||||
case ry
|
||||
case id
|
||||
case fillColor = "fill"
|
||||
case fillOpacity = "fill-opacity"
|
||||
case fillRule = "fill-rule"
|
||||
case strokeColor = "stroke"
|
||||
case strokeWidth = "stroke-width"
|
||||
case strokeOpacity = "stroke-opacity"
|
||||
case strokeLineCap = "stroke-linecap"
|
||||
case strokeLineJoin = "stroke-linejoin"
|
||||
case strokeMiterLimit = "stroke-miterlimit"
|
||||
case transform
|
||||
case style
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(x: Double, y: Double, width: Double, height: Double, rx: Double? = nil, ry: Double? = nil) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.rx = rx
|
||||
self.ry = ry
|
||||
}
|
||||
}
|
||||
|
||||
extension Rectangle: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var desc = "<rect x=\"\(x)\" y=\"\(y)\" width=\"\(width)\" height=\"\(height)\""
|
||||
if let rx {
|
||||
desc.append(" rx=\"\(rx)\"")
|
||||
}
|
||||
if let ry {
|
||||
desc.append(" ry=\"\(ry)\"")
|
||||
}
|
||||
|
||||
return desc + " \(attributeDescription) />"
|
||||
}
|
||||
}
|
||||
|
||||
extension Rectangle: DirectionalCommandRepresentable {
|
||||
public func commands(clockwise: Bool) throws -> [Path.Command] {
|
||||
RectangleProcessor(rectangle: self).commands(clockwise: clockwise)
|
||||
}
|
||||
}
|
||||
|
||||
extension Rectangle: DynamicNodeDecoding {
|
||||
public static func nodeDecoding(for key: any CodingKey) -> XMLDecoder.NodeDecoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
|
||||
extension Rectangle: DynamicNodeEncoding {
|
||||
public static func nodeEncoding(for key: any CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
62
third-party/SwiftSVG/Sources/SVG+Swift2D.swift
vendored
Normal file
62
third-party/SwiftSVG/Sources/SVG+Swift2D.swift
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import Foundation
|
||||
import Swift2D
|
||||
|
||||
public extension SVG {
|
||||
/// Original size of the document image.
|
||||
///
|
||||
/// Primarily uses the `viewBox` attribute, and will fallback to the 'pixelSize'
|
||||
var originalSize: Size {
|
||||
(viewBoxSize ?? pixelSize) ?? .zero
|
||||
}
|
||||
|
||||
/// Size of the design in a square 'viewBox'.
|
||||
///
|
||||
/// All paths created by this framework are outputted in a 'square'.
|
||||
var outputSize: Size {
|
||||
let size = (pixelSize ?? viewBoxSize) ?? .zero
|
||||
let maxDimension = max(size.width, size.height)
|
||||
return Size(width: maxDimension, height: maxDimension)
|
||||
}
|
||||
|
||||
/// Size derived from the `viewBox` document attribute
|
||||
var viewBoxSize: Size? {
|
||||
guard let viewBox else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let components = viewBox.components(separatedBy: .whitespaces)
|
||||
guard components.count == 4 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let width = Double(components[2]) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let height = Double(components[3]) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Size(width: width, height: height)
|
||||
}
|
||||
|
||||
/// Size derived from the 'width' & 'height' document attributes
|
||||
var pixelSize: Size? {
|
||||
guard let width, !width.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let height, !height.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let widthRawValue = width.replacingOccurrences(of: "px", with: "", options: .caseInsensitive, range: nil)
|
||||
let heightRawValue = height.replacingOccurrences(of: "px", with: "", options: .caseInsensitive, range: nil)
|
||||
|
||||
guard let w = Double(widthRawValue), let h = Double(heightRawValue) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Size(width: w, height: h)
|
||||
}
|
||||
}
|
||||
160
third-party/SwiftSVG/Sources/SVG.swift
vendored
Normal file
160
third-party/SwiftSVG/Sources/SVG.swift
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import Foundation
|
||||
import XMLCoder
|
||||
|
||||
/// SVG is a language for describing two-dimensional graphics in XML.
|
||||
///
|
||||
/// The svg element is a container that defines a new coordinate system and viewport. It is used as the outermost
|
||||
/// element of SVG documents, but it can also be used to embed a SVG fragment inside an SVG or HTML document.
|
||||
///
|
||||
/// ## Documentation
|
||||
/// [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)
|
||||
/// | [W3](https://www.w3.org/TR/SVG11/)
|
||||
public struct SVG: Container {
|
||||
|
||||
public var viewBox: String?
|
||||
public var width: String?
|
||||
public var height: String?
|
||||
public var title: String?
|
||||
public var desc: String?
|
||||
|
||||
// Container
|
||||
public var circles: [Circle]?
|
||||
public var ellipses: [Ellipse]?
|
||||
public var groups: [Group]?
|
||||
public var lines: [Line]?
|
||||
public var paths: [Path]?
|
||||
public var polygons: [Polygon]?
|
||||
public var polylines: [Polyline]?
|
||||
public var rectangles: [Rectangle]?
|
||||
public var texts: [Text]?
|
||||
|
||||
/// A non-optional, non-spaced representation of the `title`.
|
||||
public var name: String {
|
||||
let name = title ?? "SVG Document"
|
||||
let newTitle = name.components(separatedBy: .punctuationCharacters).joined(separator: "_")
|
||||
return newTitle.replacingOccurrences(of: " ", with: "_")
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case width
|
||||
case height
|
||||
case viewBox
|
||||
case title
|
||||
case desc
|
||||
case circles = "circle"
|
||||
case ellipses = "ellipse"
|
||||
case groups = "g"
|
||||
case lines = "line"
|
||||
case paths = "path"
|
||||
case polylines = "polyline"
|
||||
case polygons = "polygon"
|
||||
case rectangles = "rect"
|
||||
case texts = "text"
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(width: Int, height: Int) {
|
||||
self.width = "\(width)px"
|
||||
self.height = "\(height)px"
|
||||
viewBox = "0 0 \(width) \(height)"
|
||||
}
|
||||
|
||||
public static func make(from url: URL) throws -> SVG {
|
||||
guard FileManager.default.fileExists(atPath: url.path) else {
|
||||
throw CocoaError(.fileNoSuchFile)
|
||||
}
|
||||
|
||||
let data = try Data(contentsOf: url)
|
||||
return try make(with: data)
|
||||
}
|
||||
|
||||
public static func make(with data: Data, decoder: XMLDecoder = XMLDecoder()) throws -> SVG {
|
||||
try decoder.decode(SVG.self, from: data)
|
||||
}
|
||||
|
||||
/// A collection of all `Path`s in the document.
|
||||
public func subpaths() throws -> [Path] {
|
||||
var output: [Path] = []
|
||||
let _transformations: [Transformation] = []
|
||||
|
||||
if let circles {
|
||||
try output.append(contentsOf: circles.compactMap { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let ellipses {
|
||||
try output.append(contentsOf: ellipses.compactMap { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let rectangles {
|
||||
try output.append(contentsOf: rectangles.compactMap { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let polygons {
|
||||
try output.append(contentsOf: polygons.compactMap { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let polylines {
|
||||
try output.append(contentsOf: polylines.compactMap { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let paths {
|
||||
try output.append(contentsOf: paths.map { try $0.path(applying: _transformations) })
|
||||
}
|
||||
|
||||
if let groups {
|
||||
try groups.forEach {
|
||||
try output.append(contentsOf: $0.subpaths(applying: _transformations))
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
/// A singular path that represents all of the `Command`s within the document.
|
||||
public func coalescedPath() throws -> Path {
|
||||
let paths = try subpaths()
|
||||
let commands = try paths.flatMap { try $0.commands() }
|
||||
return Path(commands: commands)
|
||||
}
|
||||
}
|
||||
|
||||
extension SVG: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var contents: String = ""
|
||||
|
||||
if let title {
|
||||
contents.append("\n<title>\(title)</title>")
|
||||
}
|
||||
|
||||
if let desc {
|
||||
contents.append("\n<desc>\(desc)</desc>")
|
||||
}
|
||||
|
||||
contents.append(containerDescription)
|
||||
|
||||
return "<svg viewBox=\"\(viewBox ?? "")\" width=\"\(width ?? "")\" height=\"\(height ?? "")\">\(contents)\n</svg>"
|
||||
}
|
||||
}
|
||||
|
||||
extension SVG: DynamicNodeDecoding {
|
||||
public static func nodeDecoding(for key: any CodingKey) -> XMLDecoder.NodeDecoding {
|
||||
switch key {
|
||||
case CodingKeys.width, CodingKeys.height, CodingKeys.viewBox:
|
||||
.attribute
|
||||
default:
|
||||
.element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SVG: DynamicNodeEncoding {
|
||||
public static func nodeEncoding(for key: any CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
switch key {
|
||||
case CodingKeys.width, CodingKeys.height, CodingKeys.viewBox:
|
||||
.attribute
|
||||
default:
|
||||
.element
|
||||
}
|
||||
}
|
||||
}
|
||||
64
third-party/SwiftSVG/Sources/Stroke.swift
vendored
Normal file
64
third-party/SwiftSVG/Sources/Stroke.swift
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import Swift2D
|
||||
|
||||
public struct Stroke {
|
||||
|
||||
public var color: String?
|
||||
public var width: Double?
|
||||
public var opacity: Double?
|
||||
public var lineCap: LineCap = .butt
|
||||
public var lineJoin: LineJoin = .miter
|
||||
public var miterLimit: Double?
|
||||
|
||||
public init() {}
|
||||
|
||||
/// Presentation attribute defining the shape to be used at the end of open subpaths when they are stroked.
|
||||
///
|
||||
/// The default `LineCap` is `.butt`
|
||||
public enum LineCap: String, Sendable, Codable, CaseIterable {
|
||||
/// The stroke for each subpath does not extend beyond its two endpoints.
|
||||
case butt
|
||||
/// The end of each subpath the stroke will be extended by a half circle with a diameter equal to the stroke
|
||||
/// width.
|
||||
case round
|
||||
/// The end of each subpath the stroke will be extended by a rectangle with a width equal to half the width of
|
||||
/// the stroke and a height equal to the width of the stroke.
|
||||
case square
|
||||
}
|
||||
|
||||
/// Presentation attribute defining the shape to be used at the corners of paths when they are stroked.
|
||||
///
|
||||
/// The default `LineJoin` is `.miter`
|
||||
public enum LineJoin: String, Sendable, Codable, CaseIterable {
|
||||
/// An arcs corner is to be used to join path segments.
|
||||
///
|
||||
/// The arcs shape is formed by extending the outer edges of the stroke at the join point with arcs that have
|
||||
/// the same curvature as the outer edges at the join point.
|
||||
case arcs
|
||||
/// The bevel value indicates that a bevelled corner is to be used to join path segments.
|
||||
case bevel
|
||||
/// Indicates that a sharp corner is to be used to join path segments.
|
||||
///
|
||||
/// The corner is formed by extending the outer edges of the stroke at the tangents of the path segments until
|
||||
/// they intersect.
|
||||
case miter
|
||||
/// A sharp corner is to be used to join path segments.
|
||||
///
|
||||
/// The corner is formed by extending the outer edges of the stroke at the tangents of the path segments until
|
||||
/// they intersect.
|
||||
case miterClip = "miter-clip"
|
||||
/// The round value indicates that a round corner is to be used to join path segments.
|
||||
case round
|
||||
}
|
||||
}
|
||||
|
||||
extension Stroke.LineCap: CustomStringConvertible {
|
||||
public var description: String {
|
||||
rawValue
|
||||
}
|
||||
}
|
||||
|
||||
extension Stroke.LineJoin: CustomStringConvertible {
|
||||
public var description: String {
|
||||
rawValue
|
||||
}
|
||||
}
|
||||
17
third-party/SwiftSVG/Sources/StylingAttributes.swift
vendored
Normal file
17
third-party/SwiftSVG/Sources/StylingAttributes.swift
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
public protocol StylingAttributes {
|
||||
var style: String? { get set }
|
||||
}
|
||||
|
||||
enum StylingAttributesKeys: String, CodingKey {
|
||||
case style
|
||||
}
|
||||
|
||||
public extension StylingAttributes {
|
||||
var stylingDescription: String {
|
||||
if let style {
|
||||
"\(StylingAttributesKeys.style.rawValue)=\"\(style)\""
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
110
third-party/SwiftSVG/Sources/Text.swift
vendored
Normal file
110
third-party/SwiftSVG/Sources/Text.swift
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import Foundation
|
||||
import XMLCoder
|
||||
|
||||
/// Graphics element consisting of text
|
||||
///
|
||||
/// It's possible to apply a gradient, pattern, clipping path, mask, or filter to `Text`, like any other SVG graphics element.
|
||||
///
|
||||
/// ## Documentation
|
||||
/// [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text)
|
||||
/// | [W3](https://www.w3.org/TR/SVG11/text.html#TextElement)
|
||||
public struct Text: Element {
|
||||
|
||||
public var value: String = ""
|
||||
public var x: Double?
|
||||
public var y: Double?
|
||||
public var dx: Double?
|
||||
public var dy: Double?
|
||||
|
||||
// MARK: CoreAttributes
|
||||
|
||||
public var id: String?
|
||||
|
||||
// MARK: PresentationAttributes
|
||||
|
||||
public var fillColor: String?
|
||||
public var fillOpacity: Double?
|
||||
public var fillRule: Fill.Rule?
|
||||
public var strokeColor: String?
|
||||
public var strokeWidth: Double?
|
||||
public var strokeOpacity: Double?
|
||||
public var strokeLineCap: Stroke.LineCap?
|
||||
public var strokeLineJoin: Stroke.LineJoin?
|
||||
public var strokeMiterLimit: Double?
|
||||
public var transform: String?
|
||||
|
||||
// MARK: StylingAttributes
|
||||
|
||||
public var style: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case value = ""
|
||||
case x
|
||||
case y
|
||||
case dx
|
||||
case dy
|
||||
case id
|
||||
case fillColor = "fill"
|
||||
case fillOpacity = "fill-opacity"
|
||||
case fillRule = "fill-rule"
|
||||
case strokeColor = "stroke"
|
||||
case strokeWidth = "stroke-width"
|
||||
case strokeOpacity = "stroke-opacity"
|
||||
case strokeLineCap = "stroke-linecap"
|
||||
case strokeLineJoin = "stroke-linejoin"
|
||||
case strokeMiterLimit = "stroke-miterlimit"
|
||||
case transform
|
||||
case style
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(value: String) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
extension Text: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var components: [String] = []
|
||||
|
||||
if let x, !x.isNaN, !x.isZero {
|
||||
components.append(String(format: "x=\"%.5f\"", x))
|
||||
}
|
||||
if let y, !y.isNaN, !y.isZero {
|
||||
components.append(String(format: "y=\"%.5f\"", y))
|
||||
}
|
||||
if let dx, !dx.isNaN, !dx.isZero {
|
||||
components.append(String(format: "dx=\"%.5f\"", dx))
|
||||
}
|
||||
if let dy, !dy.isNaN, !dy.isZero {
|
||||
components.append(String(format: "dy=\"%.5f\"", dy))
|
||||
}
|
||||
|
||||
components.append(attributeDescription)
|
||||
|
||||
return "<text " + components.joined(separator: " ") + " >\(value)</text>"
|
||||
}
|
||||
}
|
||||
|
||||
extension Text: DynamicNodeDecoding {
|
||||
public static func nodeDecoding(for key: any CodingKey) -> XMLDecoder.NodeDecoding {
|
||||
switch key {
|
||||
case CodingKeys.value:
|
||||
.element
|
||||
default:
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Text: DynamicNodeEncoding {
|
||||
public static func nodeEncoding(for key: any CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
switch key {
|
||||
case CodingKeys.value:
|
||||
.element
|
||||
default:
|
||||
.attribute
|
||||
}
|
||||
}
|
||||
}
|
||||
113
third-party/SwiftSVG/Sources/Transformation.swift
vendored
Normal file
113
third-party/SwiftSVG/Sources/Transformation.swift
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import Foundation
|
||||
import Swift2D
|
||||
|
||||
/// A modification that should be applied to an element and its children.
|
||||
///
|
||||
/// If a list of transforms is provided, then the net effect is as if each transform had been specified separately in
|
||||
/// the order provided.
|
||||
///
|
||||
/// For example,
|
||||
/// ```
|
||||
/// <g transform="translate(-10,-20) scale(2) rotate(45) translate(5,10)">
|
||||
/// <!-- graphics elements go here -->
|
||||
/// </g>
|
||||
/// ```
|
||||
/// is functionally equivalent to:
|
||||
/// ```
|
||||
/// <g transform="translate(-10,-20)">
|
||||
/// <g transform="scale(2)">
|
||||
/// <g transform="rotate(45)">
|
||||
/// <g transform="translate(5,10)">
|
||||
/// <!-- graphics elements go here -->
|
||||
/// </g>
|
||||
/// </g>
|
||||
/// </g>
|
||||
/// </g>
|
||||
/// ```
|
||||
///
|
||||
/// The ‘transform’ attribute is applied to an element before processing any other coordinate or length values supplied
|
||||
/// for that element.
|
||||
///
|
||||
/// ## Documentation
|
||||
/// [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform)
|
||||
/// | [W3](https://www.w3.org/TR/SVG11/coords.html#TransformAttribute)
|
||||
public enum Transformation {
|
||||
/// Moves an object by x & y. (Y is assumed to be '0' if not provided)
|
||||
case translate(x: Double, y: Double)
|
||||
/// Specifies a transformation in the form of a transformation matrix of six values.
|
||||
case matrix(a: Double, b: Double, c: Double, d: Double, e: Double, f: Double)
|
||||
|
||||
public enum Prefix: String, CaseIterable {
|
||||
case translate
|
||||
case matrix
|
||||
}
|
||||
|
||||
/// Initializes a new `Transformation` with a raw SVG transformation string.
|
||||
public init?(_ string: String) {
|
||||
guard let prefix = Prefix.allCases.first(where: { string.lowercased().hasPrefix($0.rawValue) }) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch prefix {
|
||||
case .translate:
|
||||
guard let start = string.firstIndex(of: "(") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let stop = string.lastIndex(of: ")") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var substring = String(string[start ... stop])
|
||||
substring = substring.replacingOccurrences(of: "(", with: "")
|
||||
substring = substring.replacingOccurrences(of: ")", with: "")
|
||||
|
||||
var components = substring.split(separator: " ", omittingEmptySubsequences: true).map { String($0) }
|
||||
components = components.flatMap { $0.components(separatedBy: ",") }
|
||||
|
||||
let values = components.compactMap { Double($0) }.map { Double($0) }
|
||||
guard values.count > 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if values.count > 1 {
|
||||
self = .translate(x: values[0], y: values[1])
|
||||
} else {
|
||||
self = .translate(x: values[0], y: 0.0)
|
||||
}
|
||||
case .matrix:
|
||||
guard let start = string.firstIndex(of: "(") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let stop = string.lastIndex(of: ")") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var substring = String(string[start ... stop])
|
||||
substring = substring.replacingOccurrences(of: "(", with: "")
|
||||
substring = substring.replacingOccurrences(of: ")", with: "")
|
||||
|
||||
var components = substring.split(separator: " ", omittingEmptySubsequences: true).map { String($0) }
|
||||
components = components.flatMap { $0.components(separatedBy: ",") }
|
||||
|
||||
let values = components.compactMap { Double($0) }.map { Double($0) }
|
||||
guard values.count > 5 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self = .matrix(a: values[0], b: values[1], c: values[2], d: values[3], e: values[4], f: values[5])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Transformation: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .translate(let x, let y):
|
||||
"translate(\(x), \(y))"
|
||||
case .matrix(let a, let b, let c, let d, let e, let f):
|
||||
"matrix(\(a), \(b), \(c), \(d), \(e), \(f))"
|
||||
}
|
||||
}
|
||||
}
|
||||
19
third-party/VectorPlus/BUILD
vendored
Normal file
19
third-party/VectorPlus/BUILD
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "VectorPlus",
|
||||
module_name = "VectorPlus",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//third-party/SwiftColor",
|
||||
"//third-party/SwiftSVG",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
72
third-party/VectorPlus/Sources/CoreGraphics/CGContext+Render.swift
vendored
Normal file
72
third-party/VectorPlus/Sources/CoreGraphics/CGContext+Render.swift
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import Swift2D
|
||||
import SwiftColor
|
||||
import SwiftSVG
|
||||
#if canImport(CoreGraphics)
|
||||
import CoreGraphics
|
||||
|
||||
public extension CGContext {
|
||||
func render(path: Path, from: Rect, to: Rect) throws {
|
||||
saveGState()
|
||||
|
||||
let cgPath = CGMutablePath()
|
||||
|
||||
let commands = (try? path.commands()) ?? []
|
||||
for (idx, command) in commands.enumerated() {
|
||||
let previous: Point? = if idx > 0 {
|
||||
commands[idx - 1].previousPoint
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
|
||||
cgPath.addCommand(command, from: from, to: to, previousPoint: previous)
|
||||
}
|
||||
|
||||
if let fill = path.fill {
|
||||
let _color = Pigment(fill.color ?? "black")
|
||||
if _color.alpha != 0.0 {
|
||||
let cgColor = CGColor.make(_color)
|
||||
let color = cgColor.copy(alpha: CGFloat(fill.opacity ?? 1.0)) ?? cgColor
|
||||
let rule = fill.rule.cgFillRule
|
||||
|
||||
setFillColor(color)
|
||||
addPath(cgPath)
|
||||
fillPath(using: rule)
|
||||
}
|
||||
}
|
||||
|
||||
if let stroke = path.stroke {
|
||||
let _color = Pigment(stroke.color ?? "black")
|
||||
if _color.alpha != 0.0 {
|
||||
let cgColor = CGColor.make(_color)
|
||||
let color = cgColor.copy(alpha: CGFloat(stroke.opacity ?? 1.0)) ?? cgColor
|
||||
let width = stroke.width ?? 1.0
|
||||
let lineWidth = width * (to.size.width / from.size.width)
|
||||
|
||||
setLineWidth(CGFloat(lineWidth))
|
||||
setStrokeColor(color)
|
||||
setLineCap(stroke.lineCap.cgLineCap)
|
||||
setLineJoin(stroke.lineJoin.cgLineJoin)
|
||||
if let miterLimit = stroke.miterLimit, stroke.lineJoin == .miter {
|
||||
setMiterLimit(CGFloat(miterLimit))
|
||||
}
|
||||
addPath(cgPath)
|
||||
strokePath()
|
||||
}
|
||||
}
|
||||
|
||||
if path.fill == nil, path.stroke == nil {
|
||||
let color = CGColor(srgbRed: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
|
||||
setFillColor(color)
|
||||
addPath(cgPath)
|
||||
fillPath(using: path.fill?.rule.cgFillRule ?? .winding)
|
||||
}
|
||||
|
||||
restoreGState()
|
||||
}
|
||||
|
||||
func render(path: Path, in rect: Rect) throws {
|
||||
try render(path: path, from: rect, to: rect)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
45
third-party/VectorPlus/Sources/CoreGraphics/CGMutablePath+Instruction.swift
vendored
Normal file
45
third-party/VectorPlus/Sources/CoreGraphics/CGMutablePath+Instruction.swift
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import Swift2D
|
||||
import SwiftSVG
|
||||
#if canImport(CoreGraphics)
|
||||
import CoreGraphics
|
||||
|
||||
public extension CGMutablePath {
|
||||
/// Adds a `Path.Command` to a _CoreGraphics path_, using the provided rectangles to correctly scale the parameters.
|
||||
///
|
||||
/// - parameter command: The `Path.Command` to append
|
||||
/// - parameter from: The `Rect` which originally had the instruction. This is typically the `Document.originalSize`.
|
||||
/// - parameter to: The `Rect` defining the new size.
|
||||
/// - parameter previousPoint: The last `Point`, used for Elliptical Arc calculations
|
||||
func addCommand(_ command: Path.Command, from: Rect, to: Rect, previousPoint: Point? = nil) {
|
||||
let translated = command.translate(from: from, to: to)
|
||||
switch translated {
|
||||
case .moveTo(let point):
|
||||
move(to: CGPoint(point))
|
||||
case .lineTo(let point):
|
||||
addLine(to: CGPoint(point))
|
||||
case .cubicBezierCurve(let cp1, let cp2, let point):
|
||||
addCurve(to: CGPoint(point), control1: CGPoint(cp1), control2: CGPoint(cp2))
|
||||
case .quadraticBezierCurve(let cp, let point):
|
||||
addQuadCurve(to: CGPoint(point), control: CGPoint(cp))
|
||||
case .ellipticalArcCurve(_, _, _, _, _, let point):
|
||||
guard let previousPoint else {
|
||||
addLine(to: CGPoint(point))
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let curves = try command.convertToCubicBezierCurves(with: previousPoint)
|
||||
for curve in curves {
|
||||
addCommand(curve, from: from, to: to)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
addLine(to: CGPoint(point))
|
||||
}
|
||||
case .closePath:
|
||||
closeSubpath()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
14
third-party/VectorPlus/Sources/CoreGraphics/Fill+CoreGraphics.swift
vendored
Normal file
14
third-party/VectorPlus/Sources/CoreGraphics/Fill+CoreGraphics.swift
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import Foundation
|
||||
import SwiftSVG
|
||||
#if canImport(CoreGraphics)
|
||||
import CoreGraphics
|
||||
|
||||
public extension Fill.Rule {
|
||||
var cgFillRule: CGPathFillRule {
|
||||
switch self {
|
||||
case .evenOdd: .evenOdd
|
||||
case .nonZero: .winding
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
37
third-party/VectorPlus/Sources/CoreGraphics/SVG+CGPath.swift
vendored
Normal file
37
third-party/VectorPlus/Sources/CoreGraphics/SVG+CGPath.swift
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import Swift2D
|
||||
import SwiftSVG
|
||||
#if canImport(CoreGraphics)
|
||||
import CoreGraphics
|
||||
|
||||
public extension SVG {
|
||||
func path(size: Size) -> CGPath {
|
||||
guard size.height > 0.0, size.width > 0.0 else {
|
||||
return CGMutablePath()
|
||||
}
|
||||
|
||||
guard let paths = try? subpaths() else {
|
||||
return CGMutablePath()
|
||||
}
|
||||
|
||||
let from = Rect(origin: .zero, size: originalSize)
|
||||
let to = Rect(origin: .zero, size: size)
|
||||
|
||||
let path = CGMutablePath()
|
||||
|
||||
for p in paths {
|
||||
let commands = (try? p.commands()) ?? []
|
||||
for (idx, command) in commands.enumerated() {
|
||||
let previous: Point? = if idx > 0 {
|
||||
commands[idx - 1].previousPoint
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
|
||||
path.addCommand(command, from: from, to: to, previousPoint: previous)
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
#endif
|
||||
25
third-party/VectorPlus/Sources/CoreGraphics/Stroke+CoreGraphics.swift
vendored
Normal file
25
third-party/VectorPlus/Sources/CoreGraphics/Stroke+CoreGraphics.swift
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import Foundation
|
||||
import SwiftSVG
|
||||
#if canImport(CoreGraphics)
|
||||
import CoreGraphics
|
||||
|
||||
public extension Stroke.LineCap {
|
||||
var cgLineCap: CGLineCap {
|
||||
switch self {
|
||||
case .butt: .butt
|
||||
case .round: .round
|
||||
case .square: .square
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Stroke.LineJoin {
|
||||
var cgLineJoin: CGLineJoin {
|
||||
switch self {
|
||||
case .bevel: .bevel
|
||||
case .arcs, .miter, .miterClip: .miter
|
||||
case .round: .round
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
29
third-party/VectorPlus/Sources/Fill+VectorPlus.swift
vendored
Normal file
29
third-party/VectorPlus/Sources/Fill+VectorPlus.swift
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import SwiftColor
|
||||
import SwiftSVG
|
||||
|
||||
public extension Fill {
|
||||
@available(*, deprecated, renamed: "pigment")
|
||||
var swiftColor: Pigment? { pigment }
|
||||
|
||||
var pigment: Pigment? {
|
||||
guard let color, !color.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let _color = Pigment(color)
|
||||
guard _color.alpha != 0.0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return _color
|
||||
}
|
||||
}
|
||||
|
||||
public extension Fill.Rule {
|
||||
var coreGraphicsDescription: String {
|
||||
switch self {
|
||||
case .evenOdd: ".evenOdd"
|
||||
case .nonZero: ".winding"
|
||||
}
|
||||
}
|
||||
}
|
||||
34
third-party/VectorPlus/Sources/Path+VectorPlus.swift
vendored
Normal file
34
third-party/VectorPlus/Sources/Path+VectorPlus.swift
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import Swift2D
|
||||
import SwiftSVG
|
||||
|
||||
public extension Path {
|
||||
func asCoreGraphicsDescription(variable: String = "path", originalSize: Size) throws -> String {
|
||||
var outputs: [String] = []
|
||||
let commands = (try? commands()) ?? []
|
||||
for (idx, command) in commands.enumerated() {
|
||||
let previous: Point? = if idx > 0 {
|
||||
commands[idx - 1].previousPoint
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
|
||||
let method = command.coreGraphicsDescription(originalSize: originalSize, previousPoint: previous)
|
||||
let code = "\(variable)\(method)"
|
||||
outputs.append(code)
|
||||
}
|
||||
return outputs.joined(separator: "\n ")
|
||||
}
|
||||
}
|
||||
|
||||
public extension Path.Command {
|
||||
var previousPoint: Point {
|
||||
switch self {
|
||||
case .moveTo(let point): point
|
||||
case .lineTo(let point): point
|
||||
case .cubicBezierCurve(_, _, let point): point
|
||||
case .quadraticBezierCurve(_, let point): point
|
||||
case .ellipticalArcCurve(_, _, _, _, _, let point): point
|
||||
case .closePath: .zero
|
||||
}
|
||||
}
|
||||
}
|
||||
216
third-party/VectorPlus/Sources/Path.Command+VectorPlus.swift
vendored
Normal file
216
third-party/VectorPlus/Sources/Path.Command+VectorPlus.swift
vendored
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
import Foundation
|
||||
import Swift2D
|
||||
import SwiftSVG
|
||||
|
||||
public extension Path.Command {
|
||||
/// Uses the _Power of Math_ to translate a commands controls/points from one `Rect` to another `Rect`.
|
||||
func translate(from: Rect, to: Rect) -> Path.Command {
|
||||
switch self {
|
||||
case .moveTo(let point):
|
||||
let _point = VectorPoint(point: point, in: from).translate(to: to)
|
||||
return .moveTo(point: _point)
|
||||
case .lineTo(let point):
|
||||
let _point = VectorPoint(point: point, in: from).translate(to: to)
|
||||
return .lineTo(point: _point)
|
||||
case .cubicBezierCurve(let cp1, let cp2, let point):
|
||||
let _cp1 = VectorPoint(point: cp1, in: from).translate(to: to)
|
||||
let _cp2 = VectorPoint(point: cp2, in: from).translate(to: to)
|
||||
let _point = VectorPoint(point: point, in: from).translate(to: to)
|
||||
return .cubicBezierCurve(cp1: _cp1, cp2: _cp2, point: _point)
|
||||
case .quadraticBezierCurve(let cp, let point):
|
||||
let _cp = VectorPoint(point: cp, in: from).translate(to: to)
|
||||
let _point = VectorPoint(point: point, in: from).translate(to: to)
|
||||
return .quadraticBezierCurve(cp: _cp, point: _point)
|
||||
case .ellipticalArcCurve(let rx, let ry, let angle, let largeArc, let clockwise, let point):
|
||||
let _rx = rx * (from.size.maxRadius / to.size.minRadius)
|
||||
let _ry = ry * (from.size.maxRadius / to.size.minRadius)
|
||||
let _point = VectorPoint(point: point, in: from).translate(to: to)
|
||||
return .ellipticalArcCurve(rx: _rx, ry: _ry, angle: angle, largeArc: largeArc, clockwise: clockwise, point: _point)
|
||||
case .closePath:
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Path.Command {
|
||||
func coreGraphicsDescription(originalSize: Size, previousPoint: Point? = nil) -> String {
|
||||
let rect = Rect(origin: .zero, size: originalSize)
|
||||
|
||||
switch self {
|
||||
case .moveTo(let point):
|
||||
let _point = VectorPoint(point: point, in: rect)
|
||||
return ".move(to: \(_point.coreGraphicsDescription))"
|
||||
case .lineTo(let point):
|
||||
let _point = VectorPoint(point: point, in: rect)
|
||||
return ".addLine(to: \(_point.coreGraphicsDescription))"
|
||||
case .cubicBezierCurve(let cp1, let cp2, let point):
|
||||
let _cp1 = VectorPoint(point: cp1, in: rect)
|
||||
let _cp2 = VectorPoint(point: cp2, in: rect)
|
||||
let _point = VectorPoint(point: point, in: rect)
|
||||
return ".addCurve(to: \(_point.coreGraphicsDescription), control1: \(_cp1.coreGraphicsDescription), control2: \(_cp2.coreGraphicsDescription))"
|
||||
case .quadraticBezierCurve(let cp, let point):
|
||||
let _cp = VectorPoint(point: cp, in: rect)
|
||||
let _point = VectorPoint(point: point, in: rect)
|
||||
return ".addQuadCurve(to: \(_point.coreGraphicsDescription), control: \(_cp.coreGraphicsDescription))"
|
||||
case .ellipticalArcCurve(_, _, _, _, _, let point):
|
||||
guard let previousPoint else {
|
||||
return Path.Command.lineTo(point: point).coreGraphicsDescription(originalSize: originalSize)
|
||||
}
|
||||
|
||||
do {
|
||||
let curves = try convertToCubicBezierCurves(with: previousPoint)
|
||||
return curves.map { $0.coreGraphicsDescription(originalSize: originalSize) }.joined(separator: "\n")
|
||||
} catch {
|
||||
print(error)
|
||||
return Path.Command.lineTo(point: point).coreGraphicsDescription(originalSize: originalSize)
|
||||
}
|
||||
case .closePath:
|
||||
return ".closeSubpath()"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Path.Command {
|
||||
/// Converts an `.ellipticalArcCurve` into one or more `.cubicBezierCurve`s.
|
||||
/// https://github.com/colinmeinke/svg-arc-to-cubic-bezier/blob/master/src/index.js
|
||||
func convertToCubicBezierCurves(with previousPoint: Point) throws -> [Path.Command] {
|
||||
guard case let .ellipticalArcCurve(rx, ry, angle, largeArg, clockwise, point) = self else {
|
||||
throw Path.Command.Error.message("\(#function); Only .ellipticalArcCurve is allowed.")
|
||||
}
|
||||
|
||||
var curves: [Path.Command] = []
|
||||
|
||||
guard rx > 0.0, ry > 0.0 else {
|
||||
throw Path.Command.Error.message("\(#function); rx/ry must be greater than 0.0 (zero).")
|
||||
}
|
||||
|
||||
let sinφ = sin(angle * (.pi * 2.0) / 360.0)
|
||||
let cosφ = cos(angle * (.pi * 2.0) / 360.0)
|
||||
|
||||
let pxp = cosφ * (previousPoint.x - point.x) / 2 + sinφ * (previousPoint.y - point.y) / 2.0
|
||||
let pyp = -sinφ * (previousPoint.x - point.x) / 2 + cosφ * (previousPoint.y - point.y) / 2.0
|
||||
|
||||
guard pxp != 0.0, pyp != 0.0 else {
|
||||
throw Path.Command.Error.message("\(#function); math")
|
||||
}
|
||||
|
||||
var _rx = abs(rx)
|
||||
var _ry = abs(ry)
|
||||
|
||||
let λ = pow(pxp, 2.0) / pow(_rx, 2.0) + pow(pyp, 2.0) / pow(_ry, 2.0)
|
||||
|
||||
if λ > 1.0 {
|
||||
_rx *= sqrt(λ)
|
||||
_ry *= sqrt(λ)
|
||||
}
|
||||
|
||||
let _arcCenter = arcCenter(previousPoint: previousPoint, point: point, rx: _rx, ry: _ry, largeArc: largeArg, clockwise: clockwise, sinφ: sinφ, cosφ: cosφ, pxp: pxp, pyp: pyp)
|
||||
let center = _arcCenter.center
|
||||
var angle1 = _arcCenter.angle1
|
||||
var angle2 = _arcCenter.angle2
|
||||
|
||||
var ratio = abs(angle2) / ((.pi * 2.0) / 4.0)
|
||||
if abs(1.0 - ratio) < 0.0000001 {
|
||||
ratio = 1.0
|
||||
}
|
||||
|
||||
let segments = max(ceil(ratio), 1)
|
||||
|
||||
angle2 /= segments
|
||||
|
||||
var rawCurves: [(Point, Point, Point)] = []
|
||||
for _ in 0 ... Int(segments) {
|
||||
rawCurves.append(approximateUnitArc(angle1: angle1, angle2: angle2))
|
||||
angle1 += angle2
|
||||
}
|
||||
|
||||
for rawCurf in rawCurves {
|
||||
let _cp1 = mapToEllipse(point: rawCurf.0, rx: _rx, ry: _ry, sinφ: sinφ, cosφ: cosφ, center: center)
|
||||
let _cp2 = mapToEllipse(point: rawCurf.1, rx: _rx, ry: _ry, sinφ: sinφ, cosφ: cosφ, center: center)
|
||||
let _point = mapToEllipse(point: rawCurf.2, rx: _rx, ry: _ry, sinφ: sinφ, cosφ: cosφ, center: center)
|
||||
curves.append(.cubicBezierCurve(cp1: _cp1, cp2: _cp2, point: _point))
|
||||
}
|
||||
|
||||
return curves
|
||||
}
|
||||
}
|
||||
|
||||
private func arcCenter(previousPoint: Point, point: Point, rx: Double, ry: Double, largeArc: Bool, clockwise: Bool, sinφ: Double, cosφ: Double, pxp: Double, pyp: Double) ->
|
||||
(center: Point, angle1: Double, angle2: Double)
|
||||
{
|
||||
|
||||
let rxsq = pow(rx, 2.0)
|
||||
let rysq = pow(ry, 2.0)
|
||||
let pxpsq = pow(pxp, 2.0)
|
||||
let pypsq = pow(pyp, 2.0)
|
||||
|
||||
var radicant = (rxsq * rysq) - (rxsq * pypsq) - (rysq * pxpsq)
|
||||
if radicant < 0.0 {
|
||||
radicant = 0.0
|
||||
}
|
||||
|
||||
radicant /= (rxsq * pypsq) + (rysq * pxpsq)
|
||||
radicant = sqrt(radicant) * (largeArc == clockwise ? -1.0 : 1.0)
|
||||
|
||||
let centerxp = radicant * rx / ry * pyp
|
||||
let centeryp = radicant * -ry / rx * pxp
|
||||
|
||||
let centerx = cosφ * centerxp - sinφ * centeryp + (previousPoint.x + point.x) / 2.0
|
||||
let centery = sinφ * centerxp + cosφ * centeryp + (previousPoint.x + point.x) / 2.0
|
||||
|
||||
let vx1 = (pxp - centerxp) / rx
|
||||
let vy1 = (pyp - centeryp) / ry
|
||||
let vx2 = (-pxp - centerxp) / rx
|
||||
let vy2 = (-pyp - centeryp) / ry
|
||||
|
||||
let angle1 = vectorAngle(u: Point(x: 1, y: 0), v: Point(x: vx1, y: vy1))
|
||||
var angle2 = vectorAngle(u: Point(x: vx1, y: vy1), v: Point(x: vx2, y: vy2))
|
||||
|
||||
if clockwise == false, angle2 > 0.0 {
|
||||
angle2 -= (.pi * 2.0)
|
||||
} else if clockwise == true, angle2 < 0.0 {
|
||||
angle2 += (.pi * 2.0)
|
||||
}
|
||||
|
||||
return (Point(x: centerx, y: centery), angle1, angle2)
|
||||
}
|
||||
|
||||
private func vectorAngle(u: Point, v: Point) -> Double {
|
||||
let sign: Double = ((u.x * v.y - u.y * v.x) < 0.0) ? -1.0 : 1.0
|
||||
var dot = u.x * v.x + u.y * v.y
|
||||
if dot > 1.0 {
|
||||
dot = 1.0
|
||||
} else if dot < -1.0 {
|
||||
dot = -1.0
|
||||
}
|
||||
|
||||
return sign * acos(dot)
|
||||
}
|
||||
|
||||
private func approximateUnitArc(angle1: Double, angle2: Double) -> (Point, Point, Point) {
|
||||
// If 90 degree circular arc, use a constant
|
||||
// as derived from http://spencermortensen.com/articles/bezier-circle
|
||||
let a: Double = switch angle2 {
|
||||
case 1.5707963267948966:
|
||||
0.551915024494
|
||||
case -1.5707963267948966:
|
||||
-0.551915024494
|
||||
default:
|
||||
4.0 / 3.0 * tan(angle2 / 4.0)
|
||||
}
|
||||
|
||||
let x1 = cos(angle1)
|
||||
let y1 = sin(angle1)
|
||||
let x2 = cos(angle1 + angle2)
|
||||
let y2 = sin(angle1 + angle2)
|
||||
|
||||
return (Point(x: x1 - y1 * a, y: y1 + x1 * a), Point(x: x2 + y2 * a, y: y2 - x2 * 1), Point(x: x2, y: y2))
|
||||
}
|
||||
|
||||
private func mapToEllipse(point: Point, rx: Double, ry: Double, sinφ: Double, cosφ: Double, center: Point) -> Point {
|
||||
let x = point.x * rx
|
||||
let y = point.y * ry
|
||||
let xp = cosφ * x - sinφ * y
|
||||
let yp = sinφ * x + cosφ * y
|
||||
return Point(x: xp + center.x, y: yp + center.y)
|
||||
}
|
||||
7
third-party/VectorPlus/Sources/Pigment+VectorPlus.swift
vendored
Normal file
7
third-party/VectorPlus/Sources/Pigment+VectorPlus.swift
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import SwiftColor
|
||||
|
||||
public extension Pigment {
|
||||
var coreGraphicsDescription: String {
|
||||
"CGColor(srgbRed: \(red), green: \(green), blue: \(blue), alpha: \(alpha))"
|
||||
}
|
||||
}
|
||||
7
third-party/VectorPlus/Sources/Point+VectorPlus.swift
vendored
Normal file
7
third-party/VectorPlus/Sources/Point+VectorPlus.swift
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Swift2D
|
||||
|
||||
public extension Point {
|
||||
var coreGraphicsDescription: String {
|
||||
"CGPoint(x: \(x), y: \(y))"
|
||||
}
|
||||
}
|
||||
7
third-party/VectorPlus/Sources/Rect+VectorPlus.swift
vendored
Normal file
7
third-party/VectorPlus/Sources/Rect+VectorPlus.swift
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Swift2D
|
||||
|
||||
public extension Rect {
|
||||
var coreGraphicsDescription: String {
|
||||
"CGRect(origin: \(origin.coreGraphicsDescription), size: \(size.coreGraphicsDescription))"
|
||||
}
|
||||
}
|
||||
324
third-party/VectorPlus/Sources/SVG+AppleSymbols.swift
vendored
Normal file
324
third-party/VectorPlus/Sources/SVG+AppleSymbols.swift
vendored
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
import Foundation
|
||||
import Swift2D
|
||||
import SwiftSVG
|
||||
import XMLCoder
|
||||
|
||||
public extension SVG {
|
||||
|
||||
static func encodeDocument(_ document: SVG, encoder: XMLEncoder = XMLEncoder()) throws -> Data {
|
||||
let rootAttributes: [String: String] = [
|
||||
"version": "1.1",
|
||||
"xmlns": "http://www.w3.org/2000/svg",
|
||||
"xmlns:xlink": "http://www.w3.org/1999/xlink",
|
||||
]
|
||||
let header = XMLHeader(version: 1.0, encoding: "UTF-8", standalone: nil)
|
||||
return try encoder.encode(document, withRootKey: "svg", rootAttributes: rootAttributes, header: header)
|
||||
}
|
||||
|
||||
static func appleSymbols(path: Path, in rect: Rect) throws -> SVG {
|
||||
var document = SVG(width: 3300, height: 2200)
|
||||
document.groups = try [.appleSymbolsNotes, .appleSymbolsGuides, .appleSymbols(path: path, in: rect)]
|
||||
return document
|
||||
}
|
||||
}
|
||||
|
||||
public extension Group {
|
||||
static var appleSymbolsNotes: Group {
|
||||
var group = Group()
|
||||
|
||||
group.id = "Notes"
|
||||
group.rectangles = []
|
||||
group.lines = []
|
||||
group.texts = []
|
||||
group.groups = []
|
||||
|
||||
var artboard = Rectangle(x: 0, y: 0, width: 3300, height: 2200)
|
||||
artboard.id = "artboard"
|
||||
artboard.style = "fill:white;opacity:1"
|
||||
group.rectangles?.append(artboard)
|
||||
|
||||
var topLine = Line(x1: 263, y1: 292, x2: 3036, y2: 292)
|
||||
topLine.style = "fill:none;stroke:black;opacity:1;stroke-width:0.5;"
|
||||
group.lines?.append(topLine)
|
||||
|
||||
var bottomLine = Line(x1: 263, y1: 1903, x2: 3036, y2: 1903)
|
||||
bottomLine.style = "fill:none;stroke:black;opacity:1;stroke-width:0.5;"
|
||||
group.lines?.append(bottomLine)
|
||||
|
||||
var text = Text()
|
||||
text.style = "stroke:none;fill:black;font-family:-apple-system,\"SF Pro Display\",\"SF Pro Text\",Helvetica,sans-serif;font-weight:bold;"
|
||||
text.transform = "matrix(1 0 0 1 263 322)"
|
||||
text.value = "Weight/Scale Variations"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.style = "stroke:none;fill:black;font-family:-apple-system,\"SF Pro Display\",\"SF Pro Text\",Helvetica,sans-serif;text-anchor:middle"
|
||||
text.transform = "matrix(1 0 0 1 559.711 322)"
|
||||
text.value = "Ultralight"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 856.422 322)"
|
||||
text.value = "Thin"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 1153.13 322)"
|
||||
text.value = "Light"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 1449.84 322)"
|
||||
text.value = "Regular"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 1746.56 322)"
|
||||
text.value = "Medium"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 2043.27 322)"
|
||||
text.value = "Semibold"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 2339.98 322)"
|
||||
text.value = "Bold"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 2636.69 322)"
|
||||
text.value = "Heavy"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 2933.4 322)"
|
||||
text.value = "Black"
|
||||
group.texts?.append(text)
|
||||
|
||||
var path = Path(data: "M 9.24805 0.830078 C 13.5547 0.830078 17.1387 -2.74414 17.1387 -7.05078 C 17.1387 -11.3574 13.5449 -14.9316 9.23828 -14.9316 C 4.94141 -14.9316 1.36719 -11.3574 1.36719 -7.05078 C 1.36719 -2.74414 4.95117 0.830078 9.24805 0.830078 Z M 9.24805 -0.654297 C 5.70312 -0.654297 2.87109 -3.49609 2.87109 -7.05078 C 2.87109 -10.6055 5.69336 -13.4473 9.23828 -13.4473 C 12.793 -13.4473 15.6348 -10.6055 15.6445 -7.05078 C 15.6543 -3.49609 12.8027 -0.654297 9.24805 -0.654297 Z M 9.22852 -3.42773 C 9.69727 -3.42773 9.9707 -3.74023 9.9707 -4.25781 L 9.9707 -6.31836 L 12.1973 -6.31836 C 12.6953 -6.31836 13.0371 -6.57227 13.0371 -7.04102 C 13.0371 -7.51953 12.7148 -7.7832 12.1973 -7.7832 L 9.9707 -7.7832 L 9.9707 -10.0098 C 9.9707 -10.5273 9.69727 -10.8496 9.22852 -10.8496 C 8.75977 -10.8496 8.50586 -10.5078 8.50586 -10.0098 L 8.50586 -7.7832 L 6.29883 -7.7832 C 5.78125 -7.7832 5.44922 -7.51953 5.44922 -7.04102 C 5.44922 -6.57227 5.80078 -6.31836 6.29883 -6.31836 L 8.50586 -6.31836 L 8.50586 -4.25781 C 8.50586 -3.75977 8.75977 -3.42773 9.22852 -3.42773 Z")
|
||||
var subGroup = Group("", path: path, transform: "matrix(1 0 0 1 263 1933)")
|
||||
group.groups?.append(subGroup)
|
||||
|
||||
path.data = "M 11.709 2.91016 C 17.1582 2.91016 21.6699 -1.60156 21.6699 -7.05078 C 21.6699 -12.4902 17.1484 -17.0117 11.6992 -17.0117 C 6.25977 -17.0117 1.74805 -12.4902 1.74805 -7.05078 C 1.74805 -1.60156 6.26953 2.91016 11.709 2.91016 Z M 11.709 1.25 C 7.09961 1.25 3.41797 -2.44141 3.41797 -7.05078 C 3.41797 -11.6504 7.08984 -15.3516 11.6992 -15.3516 C 16.3086 -15.3516 20 -11.6504 20.0098 -7.05078 C 20.0195 -2.44141 16.3184 1.25 11.709 1.25 Z M 11.6895 -2.41211 C 12.207 -2.41211 12.5195 -2.77344 12.5195 -3.33984 L 12.5195 -6.23047 L 15.5762 -6.23047 C 16.123 -6.23047 16.5039 -6.51367 16.5039 -7.03125 C 16.5039 -7.55859 16.1426 -7.86133 15.5762 -7.86133 L 12.5195 -7.86133 L 12.5195 -10.9277 C 12.5195 -11.5039 12.207 -11.8555 11.6895 -11.8555 C 11.1719 -11.8555 10.8789 -11.4844 10.8789 -10.9277 L 10.8789 -7.86133 L 7.83203 -7.86133 C 7.26562 -7.86133 6.89453 -7.55859 6.89453 -7.03125 C 6.89453 -6.51367 7.28516 -6.23047 7.83203 -6.23047 L 10.8789 -6.23047 L 10.8789 -3.33984 C 10.8789 -2.79297 11.1719 -2.41211 11.6895 -2.41211 Z"
|
||||
subGroup.paths = [path]
|
||||
subGroup.transform = "matrix(1 0 0 1 281.506 1933)"
|
||||
group.groups?.append(subGroup)
|
||||
|
||||
path.data = "M 14.9707 5.67383 C 21.9336 5.67383 27.6953 -0.078125 27.6953 -7.04102 C 27.6953 -14.0039 21.9238 -19.7559 14.9609 -19.7559 C 8.00781 -19.7559 2.25586 -14.0039 2.25586 -7.04102 C 2.25586 -0.078125 8.01758 5.67383 14.9707 5.67383 Z M 14.9707 3.85742 C 8.93555 3.85742 4.08203 -1.00586 4.08203 -7.04102 C 4.08203 -13.0762 8.92578 -17.9395 14.9609 -17.9395 C 21.0059 -17.9395 25.8594 -13.0762 25.8691 -7.04102 C 25.8789 -1.00586 21.0156 3.85742 14.9707 3.85742 Z M 14.9512 -1.06445 C 15.5176 -1.06445 15.8691 -1.45508 15.8691 -2.06055 L 15.8691 -6.13281 L 20.1074 -6.13281 C 20.6934 -6.13281 21.1133 -6.46484 21.1133 -7.02148 C 21.1133 -7.59766 20.7227 -7.93945 20.1074 -7.93945 L 15.8691 -7.93945 L 15.8691 -12.1875 C 15.8691 -12.8027 15.5176 -13.1934 14.9512 -13.1934 C 14.3848 -13.1934 14.0625 -12.7832 14.0625 -12.1875 L 14.0625 -7.93945 L 9.83398 -7.93945 C 9.21875 -7.93945 8.80859 -7.59766 8.80859 -7.02148 C 8.80859 -6.46484 9.23828 -6.13281 9.83398 -6.13281 L 14.0625 -6.13281 L 14.0625 -2.06055 C 14.0625 -1.47461 14.3848 -1.06445 14.9512 -1.06445 Z"
|
||||
subGroup.paths = [path]
|
||||
subGroup.transform = "matrix(1 0 0 1 304.924 1933)"
|
||||
group.groups?.append(subGroup)
|
||||
|
||||
text.style = "stroke:none;fill:black;font-family:-apple-system,\"SF Pro Display\",\"SF Pro Text\",Helvetica,sans-serif;font-weight:bold;"
|
||||
text.transform = "matrix(1 0 0 1 263 1953)"
|
||||
text.value = "Design Variations"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.style = "none;fill:black;font-family:-apple-system,\"SF Pro Display\",\"SF Pro Text\",Helvetica,sans-serif;"
|
||||
text.transform = "matrix(1 0 0 1 263 1971)"
|
||||
text.value = "Symbols are supported in up to nine weights and three scales."
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 263 1989)"
|
||||
text.value = "For optimal layout with text and other symbols, vertically align"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 263 2007)"
|
||||
text.value = "symbols with the adjacent text."
|
||||
group.texts?.append(text)
|
||||
|
||||
var rect = Rectangle(x: 776, y: 1919, width: 3, height: 14)
|
||||
rect.style = "fill:#00AEEF;stroke:none;opacity:0.4;"
|
||||
group.rectangles?.append(rect)
|
||||
|
||||
path.data = "M 10.5273 0 L 12.373 0 L 7.17773 -14.0918 L 5.43945 -14.0918 L 0.244141 0 L 2.08984 0 L 3.50586 -4.0332 L 9.11133 -4.0332 Z M 6.2793 -11.9531 L 6.33789 -11.9531 L 8.59375 -5.52734 L 4.02344 -5.52734 Z"
|
||||
subGroup.paths = [path]
|
||||
subGroup.transform = "matrix(1 0 0 1 779 1933)"
|
||||
group.groups?.append(subGroup)
|
||||
|
||||
rect.x = 791.617
|
||||
group.rectangles?.append(rect)
|
||||
|
||||
text.style = "stroke:none;fill:black;font-family:-apple-system,\"SF Pro Display\",\"SF Pro Text\",Helvetica,sans-serif;font-weight:bold;"
|
||||
text.transform = "matrix(1 0 0 1 776 1953)"
|
||||
text.value = "Margins"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.style = "stroke:none;fill:black;font-family:-apple-system,\"SF Pro Display\",\"SF Pro Text\",Helvetica,sans-serif;"
|
||||
text.transform = "matrix(1 0 0 1 776 1971)"
|
||||
text.value = "Leading and trailing margins on the left and right side of each symbol"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 776 1989)"
|
||||
text.value = "can be adjusted by modifying the width of the blue rectangles."
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 776 2007)"
|
||||
text.value = "Modifications are automatically applied proportionally to all"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 776 2025)"
|
||||
text.value = "scales and weights."
|
||||
group.texts?.append(text)
|
||||
|
||||
path.data = "M 2.83203 3.11523 L 4.375 4.6582 C 5.22461 5.48828 6.19141 5.41992 7.06055 4.46289 L 17.2754 -6.66016 C 17.7051 -6.36719 18.0957 -6.37695 18.5645 -6.47461 L 19.6094 -6.68945 L 20.3027 -5.99609 L 20.2539 -5.47852 C 20.1855 -4.95117 20.3516 -4.53125 20.8496 -4.0332 L 21.6602 -3.22266 C 22.168 -2.71484 22.8223 -2.68555 23.3008 -3.16406 L 26.5527 -6.41602 C 27.0312 -6.89453 27.0117 -7.54883 26.5039 -8.05664 L 25.6836 -8.87695 C 25.1855 -9.375 24.7754 -9.55078 24.2383 -9.47266 L 23.7109 -9.41406 L 23.0566 -10.0781 L 23.3398 -11.2207 C 23.4863 -11.7871 23.3398 -12.2559 22.7148 -12.8613 L 20.3027 -15.2539 C 16.7578 -18.7793 12.2266 -18.6719 9.11133 -15.5371 C 8.69141 -15.1074 8.64258 -14.5215 8.91602 -14.0918 C 9.15039 -13.7207 9.62891 -13.4961 10.2734 -13.6621 C 11.7871 -14.043 13.3008 -13.9258 14.7852 -12.9199 L 14.1602 -11.3379 C 13.9258 -10.752 13.9453 -10.2734 14.1797 -9.83398 L 3.01758 0.439453 C 2.08008 1.30859 1.97266 2.25586 2.83203 3.11523 Z M 10.6738 -15.1465 C 13.3398 -17.1387 16.6504 -16.8262 19.0527 -14.4141 L 21.6797 -11.8066 C 21.9141 -11.5723 21.9434 -11.3867 21.8848 -11.0938 L 21.5039 -9.53125 L 23.0762 -7.95898 L 24.043 -8.04688 C 24.3262 -8.07617 24.4141 -8.05664 24.6387 -7.83203 L 25.2637 -7.20703 L 22.5098 -4.46289 L 21.8848 -5.07812 C 21.6602 -5.30273 21.6406 -5.40039 21.6699 -5.68359 L 21.7578 -6.64062 L 20.1953 -8.20312 L 18.5742 -7.89062 C 18.291 -7.83203 18.1445 -7.83203 17.9102 -8.07617 L 15.7324 -10.2539 C 15.5078 -10.4883 15.4785 -10.625 15.6055 -10.9473 L 16.5527 -13.2227 C 14.9512 -14.7559 12.8418 -15.6055 10.8008 -14.9512 C 10.7129 -14.9219 10.6445 -14.9414 10.6152 -14.9805 C 10.5859 -15.0293 10.5859 -15.0781 10.6738 -15.1465 Z M 4.10156 2.41211 C 3.61328 1.91406 3.78906 1.61133 4.12109 1.30859 L 15.0781 -8.80859 L 16.3086 -7.57812 L 6.15234 3.34961 C 5.84961 3.68164 5.46875 3.7793 5.06836 3.37891 Z"
|
||||
subGroup.paths = [path]
|
||||
subGroup.transform = "matrix(1 0 0 1 1289 1933)"
|
||||
group.groups?.append(subGroup)
|
||||
|
||||
text.style = "stroke:none;fill:black;font-family:-apple-system,\"SF Pro Display\",\"SF Pro Text\",Helvetica,sans-serif;font-weight:bold;"
|
||||
text.transform = "matrix(1 0 0 1 1289 1953)"
|
||||
text.value = "Exporting"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.style = "stroke:none;fill:black;font-family:-apple-system,\"SF Pro Display\",\"SF Pro Text\",Helvetica,sans-serif;"
|
||||
text.transform = "matrix(1 0 0 1 1289 1971)"
|
||||
text.value = "Symbols should be outlined when exporting to ensure the"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 1289 1989)"
|
||||
text.value = "design is preserved when submitting to Xcode."
|
||||
group.texts?.append(text)
|
||||
|
||||
text.id = "template-version"
|
||||
text.style = "stroke:none;fill:black;font-family:-apple-system,\"SF Pro Display\",\"SF Pro Text\",Helvetica,sans-serif;text-anchor:end;"
|
||||
text.transform = "matrix(1 0 0 1 3036 1933)"
|
||||
text.value = "Template v.2.0"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.id = nil
|
||||
text.transform = "matrix(1 0 0 1 3036 1969)"
|
||||
text.value = "Typeset at 100 points"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.style = "stroke:none;fill:black;font-family:-apple-system,\"SF Pro Display\",\"SF Pro Text\",Helvetica,sans-serif;"
|
||||
text.transform = "matrix(1 0 0 1 263 726)"
|
||||
text.value = "Small"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 263 1156)"
|
||||
text.value = "Medium"
|
||||
group.texts?.append(text)
|
||||
|
||||
text.transform = "matrix(1 0 0 1 263 1586)"
|
||||
text.value = "Large"
|
||||
group.texts?.append(text)
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
static var appleSymbolsGuides: Group {
|
||||
var group = Group()
|
||||
|
||||
group.id = "Guides"
|
||||
group.groups = []
|
||||
group.lines = []
|
||||
|
||||
var hRef = Group()
|
||||
hRef.id = "H-reference"
|
||||
hRef.style = "fill:#27AAE1;stroke:none;"
|
||||
hRef.transform = "matrix(1 0 0 1 339 696)"
|
||||
hRef.paths = [Path(data: "M 54.9316 0 L 57.666 0 L 30.5664 -70.459 L 28.0762 -70.459 L 0.976562 0 L 3.66211 0 L 12.9395 -24.4629 L 45.7031 -24.4629 Z M 29.1992 -67.0898 L 29.4434 -67.0898 L 44.8242 -26.709 L 13.8184 -26.709 Z")]
|
||||
group.groups?.append(hRef)
|
||||
|
||||
var baseline = Line(x1: 263, y1: 696, x2: 3036, y2: 696)
|
||||
baseline.id = "Baseline-S"
|
||||
baseline.style = "fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.577;"
|
||||
group.lines?.append(baseline)
|
||||
|
||||
var capline = Line(x1: 263, y1: 625.541, x2: 3036, y2: 625.541)
|
||||
capline.id = "Capline-S"
|
||||
capline.style = "fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.577;"
|
||||
group.lines?.append(capline)
|
||||
|
||||
hRef.transform = "matrix(1 0 0 1 339 1126)"
|
||||
hRef.paths = [Path(data: "M 54.9316 0 L 57.666 0 L 30.5664 -70.459 L 28.0762 -70.459 L 0.976562 0 L 3.66211 0 L 12.9395 -24.4629 L 45.7031 -24.4629 Z M 29.1992 -67.0898 L 29.4434 -67.0898 L 44.8242 -26.709 L 13.8184 -26.709 Z")]
|
||||
group.groups?.append(hRef)
|
||||
|
||||
baseline.id = "Baseline-M"
|
||||
baseline.y1 = 1126
|
||||
baseline.y2 = 1126
|
||||
group.lines?.append(baseline)
|
||||
|
||||
capline.id = "Capline-M"
|
||||
capline.y1 = 1055.54
|
||||
capline.y2 = 1055.54
|
||||
group.lines?.append(capline)
|
||||
|
||||
hRef.transform = "matrix(1 0 0 1 339 1556)"
|
||||
hRef.paths = [Path(data: "M 54.9316 0 L 57.666 0 L 30.5664 -70.459 L 28.0762 -70.459 L 0.976562 0 L 3.66211 0 L 12.9395 -24.4629 L 45.7031 -24.4629 Z M 29.1992 -67.0898 L 29.4434 -67.0898 L 44.8242 -26.709 L 13.8184 -26.709 Z")]
|
||||
group.groups?.append(hRef)
|
||||
|
||||
baseline.id = "Baseline-L"
|
||||
baseline.y1 = 1556
|
||||
baseline.y2 = 1556
|
||||
group.lines?.append(baseline)
|
||||
|
||||
capline.id = "Capline-L"
|
||||
capline.y1 = 1485.54
|
||||
capline.y2 = 1485.54
|
||||
group.lines?.append(capline)
|
||||
|
||||
var margin = Line(x1: 1399.72, y1: 1030.79, x2: 1399.72, y2: 1150.12)
|
||||
margin.id = "left-margin"
|
||||
margin.style = "fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;"
|
||||
group.lines?.append(margin)
|
||||
|
||||
margin = Line(x1: 1499.97, y1: 1030.79, x2: 1499.97, y2: 1150.12)
|
||||
margin.id = "right-margin"
|
||||
margin.style = "fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;"
|
||||
group.lines?.append(margin)
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
static func appleSymbols(path: Path, in rect: Rect) throws -> Group {
|
||||
var group = Group()
|
||||
|
||||
group.id = "Symbols"
|
||||
|
||||
let translations: [(name: String, size: Float, center: Point)] = [
|
||||
("Ultralight-S", 0.75, .init(x: 559.0, y: 661.0)),
|
||||
("Thin-S", 0.76, .init(x: 857.0, y: 661.0)),
|
||||
("Light-S", 0.78, .init(x: 1153.0, y: 661.0)),
|
||||
("Regular-S", 0.79, .init(x: 1449.5, y: 661.0)),
|
||||
("Medium-S", 0.80, .init(x: 1747.0, y: 661.0)),
|
||||
("Semibold-S", 0.81, .init(x: 2043.0, y: 661.0)),
|
||||
("Bold-S", 0.82, .init(x: 2340.0, y: 661.0)),
|
||||
("Heavy-S", 0.85, .init(x: 2636.5, y: 661.0)),
|
||||
("Black-S", 0.86, .init(x: 2933.0, y: 661.0)),
|
||||
("Ultralight-M", 0.95, .init(x: 559.0, y: 1091.0)),
|
||||
("Thin-M", 0.96, .init(x: 857.0, y: 1091.0)),
|
||||
("Light-M", 0.98, .init(x: 1153.0, y: 1091.0)),
|
||||
("Regular-M", 1.00, .init(x: 1449.5, y: 1091.0)),
|
||||
("Medium-M", 1.02, .init(x: 1747.0, y: 1091.0)),
|
||||
("Semibold-M", 1.03, .init(x: 2043.0, y: 1091.0)),
|
||||
("Bold-M", 1.05, .init(x: 2340.0, y: 1091.0)),
|
||||
("Heavy-M", 1.07, .init(x: 2636.5, y: 1091.0)),
|
||||
("Black-M", 1.10, .init(x: 2933.0, y: 1091.0)),
|
||||
("Ultralight-L", 1.22, .init(x: 559.0, y: 1521.0)),
|
||||
("Thin-L", 1.24, .init(x: 857.0, y: 1521.0)),
|
||||
("Light-L", 1.26, .init(x: 1153.0, y: 1521.0)),
|
||||
("Regular-L", 1.28, .init(x: 1449.5, y: 1521.0)),
|
||||
("Medium-L", 1.30, .init(x: 1747.0, y: 1521.0)),
|
||||
("Semibold-L", 1.31, .init(x: 2043.0, y: 1521.0)),
|
||||
("Bold-L", 1.33, .init(x: 2340.0, y: 1521.0)),
|
||||
("Heavy-L", 1.36, .init(x: 2636.5, y: 1521.0)),
|
||||
("Black-L", 1.39, .init(x: 2933.0, y: 1521.0)),
|
||||
]
|
||||
|
||||
group.groups = try translations.map { symbol -> Group in
|
||||
let size = Size(width: 100.0 * symbol.size, height: 100.0 * symbol.size)
|
||||
let to = Rect(origin: .zero, size: size)
|
||||
let commands = try path.commands().map { $0.translate(from: rect, to: to) }
|
||||
let p = Path(commands: commands)
|
||||
let matrixOrigin = Point(x: symbol.center.x - size.width / 2.0, y: symbol.center.y - size.height / 2.0)
|
||||
let matrix: Transformation = .matrix(a: 1, b: 0, c: 0, d: 1, e: matrixOrigin.x, f: matrixOrigin.y)
|
||||
return Group(symbol.name, path: p, transform: matrix.description)
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
}
|
||||
|
||||
private extension Group {
|
||||
init(_ id: String, path: Path, transform: String) {
|
||||
self.init()
|
||||
self.id = id
|
||||
self.transform = transform
|
||||
paths = [path]
|
||||
}
|
||||
}
|
||||
56
third-party/VectorPlus/Sources/SVG+Template.swift
vendored
Normal file
56
third-party/VectorPlus/Sources/SVG+Template.swift
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import Foundation
|
||||
import Swift2D
|
||||
import SwiftSVG
|
||||
|
||||
public extension SVG {
|
||||
func asImageViewSubclass() throws -> String {
|
||||
let instructions = try asCoreGraphicsDescription()
|
||||
let renders = try asCGContextDescription()
|
||||
|
||||
return imageViewSubclassTemplate
|
||||
.replacingOccurrences(of: "{{name}}", with: name)
|
||||
.replacingOccurrences(of: "{{width}}", with: String(format: "%.1f", originalSize.width))
|
||||
.replacingOccurrences(of: "{{height}}", with: String(format: "%.1f", originalSize.height))
|
||||
.replacingOccurrences(of: "{{instructions}}", with: instructions)
|
||||
.replacingOccurrences(of: "{{render}}", with: renders)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SVG {
|
||||
func asCoreGraphicsDescription(variable: String = "path") throws -> String {
|
||||
try subpaths().map { try $0.asCoreGraphicsDescription(variable: variable, originalSize: originalSize) }.joined(separator: "\n ")
|
||||
}
|
||||
|
||||
func asCGContextDescription() throws -> String {
|
||||
var outputs: [String] = []
|
||||
|
||||
let paths = try subpaths()
|
||||
try paths.forEach { path in
|
||||
let instructions = try path.asCoreGraphicsDescription(variable: "path", originalSize: originalSize)
|
||||
let fillColor = path.fill?.pigment?.coreGraphicsDescription ?? "nil"
|
||||
let fillOpacity = (path.fillOpacity != nil) ? "\(path.fillOpacity!)" : "nil"
|
||||
let fillRule = (path.fillRule ?? .nonZero).coreGraphicsDescription
|
||||
let strokeColor = path.stroke?.pigment?.coreGraphicsDescription ?? "nil"
|
||||
let strokeOpacity = (path.strokeOpacity != nil) ? "\(path.strokeOpacity!)" : "nil"
|
||||
let strokeWidth = (path.strokeWidth != nil) ? "\(path.strokeWidth!) * (size.width / width)" : "nil"
|
||||
let strokeLineCap = (path.strokeLineCap != nil) ? "\(path.strokeLineCap!.coreGraphicsDescription)" : "nil"
|
||||
let strokeLineJoin = (path.strokeLineJoin != nil) ? "\(path.strokeLineJoin!.coreGraphicsDescription)" : "nil"
|
||||
let strokeMiterLimit = (path.strokeMiterLimit != nil) ? "\(path.strokeMiterLimit!)" : "nil"
|
||||
|
||||
outputs.append(contextTemplate
|
||||
.replacingOccurrences(of: "{{instructions}}", with: instructions)
|
||||
.replacingOccurrences(of: "{{fillColor}}", with: fillColor)
|
||||
.replacingOccurrences(of: "{{fillOpacity}}", with: fillOpacity)
|
||||
.replacingOccurrences(of: "{{fillRule}}", with: fillRule)
|
||||
.replacingOccurrences(of: "{{strokeColor}}", with: strokeColor)
|
||||
.replacingOccurrences(of: "{{strokeOpacity}}", with: strokeOpacity)
|
||||
.replacingOccurrences(of: "{{strokeWidth}}", with: strokeWidth)
|
||||
.replacingOccurrences(of: "{{strokeLineCap}}", with: strokeLineCap)
|
||||
.replacingOccurrences(of: "{{strokeLineJoin}}", with: strokeLineJoin)
|
||||
.replacingOccurrences(of: "{{strokeMiterLimit}}", with: strokeMiterLimit)
|
||||
)
|
||||
}
|
||||
|
||||
return outputs.joined(separator: "\n ")
|
||||
}
|
||||
}
|
||||
7
third-party/VectorPlus/Sources/Size+VectorPlus.swift
vendored
Normal file
7
third-party/VectorPlus/Sources/Size+VectorPlus.swift
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Swift2D
|
||||
|
||||
public extension Size {
|
||||
var coreGraphicsDescription: String {
|
||||
"CGSize(width: \(width), height: \(height))"
|
||||
}
|
||||
}
|
||||
40
third-party/VectorPlus/Sources/Stroke+VectorPlus.swift
vendored
Normal file
40
third-party/VectorPlus/Sources/Stroke+VectorPlus.swift
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import SwiftColor
|
||||
import SwiftSVG
|
||||
|
||||
public extension Stroke {
|
||||
@available(*, deprecated, renamed: "pigment")
|
||||
var swiftColor: Pigment? { pigment }
|
||||
|
||||
var pigment: Pigment? {
|
||||
guard let color, !color.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let _color = Pigment(color)
|
||||
guard _color.alpha != 0.0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return _color
|
||||
}
|
||||
}
|
||||
|
||||
public extension Stroke.LineCap {
|
||||
var coreGraphicsDescription: String {
|
||||
switch self {
|
||||
case .butt: ".butt"
|
||||
case .round: ".round"
|
||||
case .square: ".square"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Stroke.LineJoin {
|
||||
var coreGraphicsDescription: String {
|
||||
switch self {
|
||||
case .bevel: ".bevel"
|
||||
case .arcs, .miter, .miterClip: ".miter"
|
||||
case .round: ".round"
|
||||
}
|
||||
}
|
||||
}
|
||||
177
third-party/VectorPlus/Sources/Template+UIImageView.swift
vendored
Normal file
177
third-party/VectorPlus/Sources/Template+UIImageView.swift
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
let imageViewSubclassTemplate: String = """
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
public class {{name}}: UIImageView {
|
||||
|
||||
public static let width: CGFloat = {{width}}
|
||||
public static let height: CGFloat = {{height}}
|
||||
public let width: CGFloat = {{width}}
|
||||
public let height: CGFloat = {{height}}
|
||||
|
||||
public var widthToHeightAspectRatio: CGFloat {
|
||||
guard width != .nan, width > 0.0 else {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
guard height != .nan, height > 0.0 else {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
return width / height
|
||||
}
|
||||
|
||||
public var heightToWidthAspectRatio: CGFloat {
|
||||
guard height != .nan, height > 0.0 else {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
guard width != .nan, width > 0.0 else {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
return height / width
|
||||
}
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
updateSubviews()
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
updateSubviews()
|
||||
}
|
||||
|
||||
public override var intrinsicContentSize: CGSize {
|
||||
return CGSize(width: width, height: height)
|
||||
}
|
||||
|
||||
public override var bounds: CGRect {
|
||||
didSet {
|
||||
updateSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
public override func prepareForInterfaceBuilder() {
|
||||
super.prepareForInterfaceBuilder()
|
||||
updateSubviews()
|
||||
}
|
||||
|
||||
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
updateSubviews()
|
||||
}
|
||||
|
||||
public func updateSubviews() {
|
||||
image = Self.image(size: bounds.size)
|
||||
}
|
||||
|
||||
public static func path(size: CGSize) -> CGPath {
|
||||
guard size.height > 0.0 && size.width > 0.0 else {
|
||||
return CGMutablePath()
|
||||
}
|
||||
|
||||
let radius = max(size.width / 2.0, size.height / 2.0)
|
||||
let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
|
||||
let path = CGMutablePath()
|
||||
{{instructions}}
|
||||
return path
|
||||
}
|
||||
|
||||
public static func image(size: CGSize) -> UIImage? {
|
||||
guard size.height > 0.0 && size.width > 0.0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let radius = max(size.width / 2.0, size.height / 2.0)
|
||||
let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
|
||||
defer {
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
{{render}}
|
||||
|
||||
return UIGraphicsGetImageFromCurrentImageContext()
|
||||
}
|
||||
|
||||
private static func radians(_ degree: Float) -> CGFloat {
|
||||
return CGFloat(degree) * (.pi / CGFloat(180))
|
||||
}
|
||||
}
|
||||
|
||||
private extension CGContext {
|
||||
func rendering(_ block: (CGContext) -> Void) {
|
||||
block(self)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
"""
|
||||
|
||||
let contextTemplate: String = """
|
||||
context.rendering { (ctx) in
|
||||
ctx.saveGState()
|
||||
|
||||
let path = CGMutablePath()
|
||||
{{instructions}}
|
||||
|
||||
let defaultColor: CGColor = CGColor(srgbRed: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
|
||||
let pathFillColor: CGColor? = {{fillColor}}
|
||||
let pathFillOpacity: CGFloat? = {{fillOpacity}}
|
||||
let pathFillRule: CGPathFillRule = {{fillRule}}
|
||||
let pathStrokeColor: CGColor? = {{strokeColor}}
|
||||
let pathStrokeOpacity: CGFloat? = {{strokeOpacity}}
|
||||
let pathStrokeWidth: CGFloat? = {{strokeWidth}}
|
||||
let pathStrokeLineCap: CGLineCap? = {{strokeLineCap}}
|
||||
let pathStrokeLineJoin: CGLineJoin? = {{strokeLineJoin}}
|
||||
let pathStrokeMiterLimit: CGFloat? = {{strokeMiterLimit}}
|
||||
|
||||
if pathFillColor != nil && pathFillOpacity != nil {
|
||||
let opacity = pathFillOpacity ?? 1.0
|
||||
let color = (pathFillColor ?? defaultColor).copy(alpha: opacity) ?? defaultColor
|
||||
|
||||
ctx.setFillColor(color)
|
||||
ctx.addPath(path)
|
||||
ctx.fillPath(using: pathFillRule)
|
||||
}
|
||||
|
||||
if pathStrokeColor != nil && pathStrokeOpacity != nil {
|
||||
let opacity = pathStrokeOpacity ?? 1.0
|
||||
let color = (pathStrokeColor ?? defaultColor).copy(alpha: opacity) ?? defaultColor
|
||||
let lineWidth = pathStrokeWidth ?? 1.0
|
||||
|
||||
ctx.setLineWidth(lineWidth)
|
||||
ctx.setStrokeColor(color)
|
||||
if let lineCap = pathStrokeLineCap {
|
||||
ctx.setLineCap(lineCap)
|
||||
}
|
||||
if let lineJoin = pathStrokeLineJoin {
|
||||
ctx.setLineJoin(lineJoin)
|
||||
if let miterLimit = pathStrokeMiterLimit, lineJoin == .miter {
|
||||
ctx.setMiterLimit(miterLimit)
|
||||
}
|
||||
}
|
||||
ctx.addPath(path)
|
||||
ctx.strokePath()
|
||||
}
|
||||
|
||||
if (pathFillColor == nil && pathFillOpacity == nil) && (pathStrokeColor == nil && pathStrokeOpacity == nil) {
|
||||
ctx.setFillColor(defaultColor)
|
||||
ctx.addPath(path)
|
||||
ctx.fillPath(using: pathFillRule)
|
||||
}
|
||||
|
||||
ctx.restoreGState()
|
||||
}
|
||||
"""
|
||||
43
third-party/VectorPlus/Sources/UIKit/SVG+UIImage.swift
vendored
Normal file
43
third-party/VectorPlus/Sources/UIKit/SVG+UIImage.swift
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#if canImport(UIKit)
|
||||
import Swift2D
|
||||
import SwiftSVG
|
||||
import UIKit
|
||||
|
||||
public extension SVG {
|
||||
func uiImage(size: Size) -> UIImage? {
|
||||
guard size.height > 0.0, size.width > 0.0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let from = Rect(origin: .zero, size: originalSize)
|
||||
let to = Rect(origin: .zero, size: size)
|
||||
|
||||
let paths: [Path]
|
||||
do {
|
||||
paths = try subpaths()
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer {
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(CGSize(size), false, 0.0)
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
for path in paths {
|
||||
try? context.render(path: path, from: from, to: to)
|
||||
}
|
||||
|
||||
return UIGraphicsGetImageFromCurrentImageContext()
|
||||
}
|
||||
|
||||
func pngData(size: Size) -> Data? {
|
||||
uiImage(size: size)?.pngData()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
81
third-party/VectorPlus/Sources/UIKit/SVGImageView.swift
vendored
Normal file
81
third-party/VectorPlus/Sources/UIKit/SVGImageView.swift
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import Swift2D
|
||||
import SwiftSVG
|
||||
#if canImport(UIKit) && !os(watchOS)
|
||||
import UIKit
|
||||
|
||||
@IBDesignable open class SVGImageView: UIImageView {
|
||||
|
||||
public var width: CGFloat {
|
||||
CGFloat(svg.originalSize.width)
|
||||
}
|
||||
|
||||
public var height: CGFloat {
|
||||
CGFloat(svg.originalSize.height)
|
||||
}
|
||||
|
||||
open var svg: SVG = SVG() {
|
||||
didSet {
|
||||
updateSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
public var widthToHeightAspectRatio: CGFloat {
|
||||
guard !width.isNaN, width > 0.0 else {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
guard !height.isNaN, height > 0.0 else {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
return width / height
|
||||
}
|
||||
|
||||
public var heightToWidthAspectRatio: CGFloat {
|
||||
guard !height.isNaN, height > 0.0 else {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
guard !width.isNaN, width > 0.0 else {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
return height / width
|
||||
}
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
updateSubviews()
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
updateSubviews()
|
||||
}
|
||||
|
||||
override public var intrinsicContentSize: CGSize {
|
||||
CGSize(width: width, height: height)
|
||||
}
|
||||
|
||||
override public var bounds: CGRect {
|
||||
didSet {
|
||||
updateSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
override public func prepareForInterfaceBuilder() {
|
||||
super.prepareForInterfaceBuilder()
|
||||
updateSubviews()
|
||||
}
|
||||
|
||||
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
updateSubviews()
|
||||
}
|
||||
|
||||
public func updateSubviews() {
|
||||
image = svg.uiImage(size: Size(bounds.size))
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
126
third-party/VectorPlus/Sources/VectorPoint.swift
vendored
Normal file
126
third-party/VectorPlus/Sources/VectorPoint.swift
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import Swift2D
|
||||
|
||||
/// A cartesian-based struct that describes the relationship of any particular `Point` to the _origin_ of a `Rect`.
|
||||
public struct VectorPoint {
|
||||
public enum Sign: String {
|
||||
case plus = "+"
|
||||
case minus = "-"
|
||||
}
|
||||
|
||||
public typealias Offset = (sign: Sign, multiplier: Double)
|
||||
|
||||
public var x: Offset
|
||||
public var y: Offset
|
||||
|
||||
public init(x: Offset, y: Offset) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
}
|
||||
|
||||
/// Initializes a `VectorPoint` for a given `Point` container in the provided `Rect`.
|
||||
public init(point: Point, in rect: Rect) {
|
||||
let radius = rect.size.maxRadius
|
||||
let cartesianPoint = Self.cartesianPoint(for: point, in: rect)
|
||||
|
||||
if cartesianPoint.x < 0 {
|
||||
x = (.minus, abs(cartesianPoint.x) / radius)
|
||||
} else {
|
||||
x = (.plus, cartesianPoint.x / radius)
|
||||
}
|
||||
|
||||
if cartesianPoint.y < 0 {
|
||||
y = (.plus, abs(cartesianPoint.y) / radius)
|
||||
} else {
|
||||
y = (.minus, cartesianPoint.y / radius)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CustomStringConvertible
|
||||
|
||||
extension VectorPoint: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"VectorPoint(x: (\(x.sign.rawValue), \(x.multiplier)), y: (\(y.sign.rawValue), \(y.multiplier)))"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
extension VectorPoint: Equatable {
|
||||
public static func == (lhs: VectorPoint, rhs: VectorPoint) -> Bool {
|
||||
guard lhs.x.sign == rhs.x.sign else {
|
||||
return false
|
||||
}
|
||||
guard lhs.x.multiplier == rhs.x.multiplier else {
|
||||
return false
|
||||
}
|
||||
guard lhs.y.sign == rhs.y.sign else {
|
||||
return false
|
||||
}
|
||||
guard lhs.y.multiplier == rhs.y.multiplier else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public extension VectorPoint {
|
||||
/// Translates the provided point within the `Rect` from using the top-left
|
||||
/// as the _origin_, to using the center as the _origin_.
|
||||
///
|
||||
/// For example: Given `Rect(x: 0, y: 0, width: 100, height: 100)`, the point
|
||||
/// `Point(x: 25, y: 25)` would translate to `Point(x: -25, y: 25)`.
|
||||
static func cartesianPoint(for point: Point, in rect: Rect) -> Point {
|
||||
let origin = Point(x: rect.size.width / 2.0, y: rect.size.height / 2.0)
|
||||
var cartesianPoint: Point = .zero
|
||||
|
||||
if point.x < origin.x {
|
||||
cartesianPoint = cartesianPoint.x(-(origin.x - point.x))
|
||||
} else if point.x > origin.x {
|
||||
cartesianPoint = cartesianPoint.x(point.x - origin.x)
|
||||
}
|
||||
|
||||
if point.y > origin.y {
|
||||
cartesianPoint = cartesianPoint.y(-(point.y - origin.y))
|
||||
} else if point.y < origin.y {
|
||||
cartesianPoint = cartesianPoint.y(origin.y - point.y)
|
||||
}
|
||||
|
||||
return cartesianPoint
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Instance Functionality
|
||||
|
||||
public extension VectorPoint {
|
||||
/// Calculates the `Point` for this instance in the specified `Rect`.
|
||||
func translate(to rect: Rect) -> Point {
|
||||
translate(to: rect.size)
|
||||
}
|
||||
|
||||
/// Calculates the `Point` in the desired output size
|
||||
func translate(to outputSize: Size) -> Point {
|
||||
let center = outputSize.center
|
||||
let radius = outputSize.minRadius
|
||||
|
||||
switch (x.sign, y.sign) {
|
||||
case (.plus, .plus):
|
||||
return Point(x: center.x + (radius * x.multiplier), y: center.y + (radius * y.multiplier))
|
||||
case (.plus, .minus):
|
||||
return Point(x: center.x + (radius * x.multiplier), y: center.y - (radius * y.multiplier))
|
||||
case (.minus, .plus):
|
||||
return Point(x: center.x - (radius * x.multiplier), y: center.y + (radius * y.multiplier))
|
||||
case (.minus, .minus):
|
||||
return Point(x: center.x - (radius * x.multiplier), y: center.y - (radius * y.multiplier))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension VectorPoint {
|
||||
var coreGraphicsDescription: String {
|
||||
"CGPoint(x: center.x \(x.sign.rawValue) (radius * \(x.multiplier)), y: center.y \(y.sign.rawValue) (radius * \(y.multiplier)))"
|
||||
}
|
||||
}
|
||||
17
third-party/XMLCoder/BUILD
vendored
Normal file
17
third-party/XMLCoder/BUILD
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "XMLCoder",
|
||||
module_name = "XMLCoder",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-suppress-warnings",
|
||||
],
|
||||
deps = [
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
99
third-party/XMLCoder/Sources/Auxiliaries/Attribute.swift
vendored
Normal file
99
third-party/XMLCoder/Sources/Auxiliaries/Attribute.swift
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// XMLAttribute.swift
|
||||
// XMLCoder
|
||||
//
|
||||
// Created by Benjamin Wetherfield on 6/3/20.
|
||||
//
|
||||
|
||||
protocol XMLAttributeProtocol {}
|
||||
|
||||
/** Property wrapper specifying that a given property should be encoded and decoded as an XML attribute.
|
||||
|
||||
For example, this type
|
||||
```swift
|
||||
struct Book: Codable {
|
||||
@Attribute var id: Int
|
||||
}
|
||||
```
|
||||
|
||||
will encode value `Book(id: 42)` as `<Book id="42"></Book>`. And vice versa,
|
||||
it will decode the former into the latter.
|
||||
*/
|
||||
@propertyWrapper
|
||||
public struct Attribute<Value>: XMLAttributeProtocol {
|
||||
public var wrappedValue: Value
|
||||
|
||||
public init(_ wrappedValue: Value) {
|
||||
self.wrappedValue = wrappedValue
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: Codable where Value: Codable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
try wrappedValue.encode(to: encoder)
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
try wrappedValue = .init(from: decoder)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: Equatable where Value: Equatable {}
|
||||
extension Attribute: Hashable where Value: Hashable {}
|
||||
extension Attribute: Sendable where Value: Sendable {}
|
||||
|
||||
extension Attribute: ExpressibleByIntegerLiteral where Value: ExpressibleByIntegerLiteral {
|
||||
public typealias IntegerLiteralType = Value.IntegerLiteralType
|
||||
|
||||
public init(integerLiteral value: Value.IntegerLiteralType) {
|
||||
wrappedValue = Value(integerLiteral: value)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: ExpressibleByUnicodeScalarLiteral where Value: ExpressibleByUnicodeScalarLiteral {
|
||||
public init(unicodeScalarLiteral value: Value.UnicodeScalarLiteralType) {
|
||||
wrappedValue = Value(unicodeScalarLiteral: value)
|
||||
}
|
||||
|
||||
public typealias UnicodeScalarLiteralType = Value.UnicodeScalarLiteralType
|
||||
}
|
||||
|
||||
extension Attribute: ExpressibleByExtendedGraphemeClusterLiteral where Value: ExpressibleByExtendedGraphemeClusterLiteral {
|
||||
public typealias ExtendedGraphemeClusterLiteralType = Value.ExtendedGraphemeClusterLiteralType
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: Value.ExtendedGraphemeClusterLiteralType) {
|
||||
wrappedValue = Value(extendedGraphemeClusterLiteral: value)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: ExpressibleByStringLiteral where Value: ExpressibleByStringLiteral {
|
||||
public typealias StringLiteralType = Value.StringLiteralType
|
||||
|
||||
public init(stringLiteral value: Value.StringLiteralType) {
|
||||
wrappedValue = Value(stringLiteral: value)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: ExpressibleByBooleanLiteral where Value: ExpressibleByBooleanLiteral {
|
||||
public typealias BooleanLiteralType = Value.BooleanLiteralType
|
||||
|
||||
public init(booleanLiteral value: Value.BooleanLiteralType) {
|
||||
wrappedValue = Value(booleanLiteral: value)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: ExpressibleByNilLiteral where Value: ExpressibleByNilLiteral {
|
||||
public init(nilLiteral: ()) {
|
||||
wrappedValue = Value(nilLiteral: ())
|
||||
}
|
||||
}
|
||||
|
||||
protocol XMLOptionalAttributeProtocol: XMLAttributeProtocol {
|
||||
init()
|
||||
}
|
||||
|
||||
extension Attribute: XMLOptionalAttributeProtocol where Value: AnyOptional {
|
||||
init() {
|
||||
wrappedValue = Value()
|
||||
}
|
||||
}
|
||||
53
third-party/XMLCoder/Sources/Auxiliaries/Box/BoolBox.swift
vendored
Normal file
53
third-party/XMLCoder/Sources/Auxiliaries/Box/BoolBox.swift
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 12/17/18.
|
||||
//
|
||||
|
||||
struct BoolBox: Equatable {
|
||||
typealias Unboxed = Bool
|
||||
|
||||
let unboxed: Unboxed
|
||||
|
||||
init(_ unboxed: Unboxed) {
|
||||
self.unboxed = unboxed
|
||||
}
|
||||
|
||||
init?(xmlString: String) {
|
||||
switch xmlString.lowercased() {
|
||||
case "false", "0", "n", "no": self.init(false)
|
||||
case "true", "1", "y", "yes": self.init(true)
|
||||
case _: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BoolBox: Box {
|
||||
var isNull: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/// # Lexical representation
|
||||
/// Boolean has a lexical representation consisting of the following
|
||||
/// legal literals {`true`, `false`, `1`, `0`}.
|
||||
///
|
||||
/// # Canonical representation
|
||||
/// The canonical representation for boolean is the set of literals {`true`, `false`}.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// [Schema definition](https://www.w3.org/TR/xmlschema-2/#boolean)
|
||||
var xmlString: String? {
|
||||
return (unboxed) ? "true" : "false"
|
||||
}
|
||||
}
|
||||
|
||||
extension BoolBox: SimpleBox {}
|
||||
|
||||
extension BoolBox: CustomStringConvertible {
|
||||
var description: String {
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
32
third-party/XMLCoder/Sources/Auxiliaries/Box/Box.swift
vendored
Normal file
32
third-party/XMLCoder/Sources/Auxiliaries/Box/Box.swift
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 12/17/18.
|
||||
//
|
||||
|
||||
protocol Box {
|
||||
var isNull: Bool { get }
|
||||
var xmlString: String? { get }
|
||||
}
|
||||
|
||||
/// A box that only describes a single atomic value.
|
||||
protocol SimpleBox: Box {
|
||||
// A simple tagging protocol, for now.
|
||||
}
|
||||
|
||||
protocol TypeErasedSharedBoxProtocol {
|
||||
func typeErasedUnbox() -> Box
|
||||
}
|
||||
|
||||
protocol SharedBoxProtocol: TypeErasedSharedBoxProtocol {
|
||||
associatedtype B: Box
|
||||
func unbox() -> B
|
||||
}
|
||||
|
||||
extension SharedBoxProtocol {
|
||||
func typeErasedUnbox() -> Box {
|
||||
return unbox()
|
||||
}
|
||||
}
|
||||
41
third-party/XMLCoder/Sources/Auxiliaries/Box/ChoiceBox.swift
vendored
Normal file
41
third-party/XMLCoder/Sources/Auxiliaries/Box/ChoiceBox.swift
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2019-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by James Bean on 7/18/19.
|
||||
//
|
||||
|
||||
/// A `Box` which represents an element which is known to contain an XML choice element.
|
||||
struct ChoiceBox {
|
||||
var key: String = ""
|
||||
var element: Box = NullBox()
|
||||
}
|
||||
|
||||
extension ChoiceBox: Box {
|
||||
var isNull: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var xmlString: String? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension ChoiceBox: SimpleBox {}
|
||||
|
||||
extension ChoiceBox {
|
||||
init?(_ keyedBox: KeyedBox) {
|
||||
guard
|
||||
let firstKey = keyedBox.elements.keys.first,
|
||||
let firstElement = keyedBox.elements[firstKey].first
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
self.init(key: firstKey, element: firstElement)
|
||||
}
|
||||
|
||||
init(_ singleKeyedBox: SingleKeyedBox) {
|
||||
self.init(key: singleKeyedBox.key, element: singleKeyedBox.element)
|
||||
}
|
||||
}
|
||||
57
third-party/XMLCoder/Sources/Auxiliaries/Box/DataBox.swift
vendored
Normal file
57
third-party/XMLCoder/Sources/Auxiliaries/Box/DataBox.swift
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 12/19/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct DataBox: Equatable {
|
||||
enum Format: Equatable {
|
||||
case base64
|
||||
}
|
||||
|
||||
typealias Unboxed = Data
|
||||
|
||||
let unboxed: Unboxed
|
||||
let format: Format
|
||||
|
||||
init(_ unboxed: Unboxed, format: Format) {
|
||||
self.unboxed = unboxed
|
||||
self.format = format
|
||||
}
|
||||
|
||||
init?(base64 string: String) {
|
||||
guard let data = Data(base64Encoded: string) else {
|
||||
return nil
|
||||
}
|
||||
self.init(data, format: .base64)
|
||||
}
|
||||
|
||||
func xmlString(format: Format) -> String {
|
||||
switch format {
|
||||
case .base64:
|
||||
return unboxed.base64EncodedString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DataBox: Box {
|
||||
var isNull: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var xmlString: String? {
|
||||
return xmlString(format: format)
|
||||
}
|
||||
}
|
||||
|
||||
extension DataBox: SimpleBox {}
|
||||
|
||||
extension DataBox: CustomStringConvertible {
|
||||
var description: String {
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
99
third-party/XMLCoder/Sources/Auxiliaries/Box/DateBox.swift
vendored
Normal file
99
third-party/XMLCoder/Sources/Auxiliaries/Box/DateBox.swift
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 12/18/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct DateBox: Equatable, Sendable {
|
||||
enum Format: Equatable {
|
||||
case secondsSince1970
|
||||
case millisecondsSince1970
|
||||
case iso8601
|
||||
case formatter(DateFormatter)
|
||||
}
|
||||
|
||||
typealias Unboxed = Date
|
||||
|
||||
let unboxed: Unboxed
|
||||
let format: Format
|
||||
|
||||
init(_ unboxed: Unboxed, format: Format) {
|
||||
self.unboxed = unboxed
|
||||
self.format = format
|
||||
}
|
||||
|
||||
init?(secondsSince1970 string: String) {
|
||||
guard let seconds = TimeInterval(string) else {
|
||||
return nil
|
||||
}
|
||||
let unboxed = Date(timeIntervalSince1970: seconds)
|
||||
self.init(unboxed, format: .secondsSince1970)
|
||||
}
|
||||
|
||||
init?(millisecondsSince1970 string: String) {
|
||||
guard let milliseconds = TimeInterval(string) else {
|
||||
return nil
|
||||
}
|
||||
let unboxed = Date(timeIntervalSince1970: milliseconds / 1000.0)
|
||||
self.init(unboxed, format: .millisecondsSince1970)
|
||||
}
|
||||
|
||||
init?(iso8601 string: String) {
|
||||
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
|
||||
guard let unboxed = ISO8601DateFormatter.xmlCoderFormatter().date(from: string) else {
|
||||
return nil
|
||||
}
|
||||
self.init(unboxed, format: .iso8601)
|
||||
} else {
|
||||
fatalError("ISO8601DateFormatter is unavailable on this platform.")
|
||||
}
|
||||
}
|
||||
|
||||
init?(xmlString: String, formatter: DateFormatter) {
|
||||
guard let date = formatter.date(from: xmlString) else {
|
||||
return nil
|
||||
}
|
||||
self.init(date, format: .formatter(formatter))
|
||||
}
|
||||
|
||||
func xmlString(format: Format) -> String {
|
||||
switch format {
|
||||
case .secondsSince1970:
|
||||
let seconds = unboxed.timeIntervalSince1970
|
||||
return seconds.description
|
||||
case .millisecondsSince1970:
|
||||
let milliseconds = unboxed.timeIntervalSince1970 * 1000.0
|
||||
return milliseconds.description
|
||||
case .iso8601:
|
||||
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
|
||||
return ISO8601DateFormatter.xmlCoderFormatter().string(from: self.unboxed)
|
||||
} else {
|
||||
fatalError("ISO8601DateFormatter is unavailable on this platform.")
|
||||
}
|
||||
case let .formatter(formatter):
|
||||
return formatter.string(from: unboxed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DateBox: Box {
|
||||
var isNull: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var xmlString: String? {
|
||||
return xmlString(format: format)
|
||||
}
|
||||
}
|
||||
|
||||
extension DateBox: SimpleBox {}
|
||||
|
||||
extension DateBox: CustomStringConvertible {
|
||||
var description: String {
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
62
third-party/XMLCoder/Sources/Auxiliaries/Box/DecimalBox.swift
vendored
Normal file
62
third-party/XMLCoder/Sources/Auxiliaries/Box/DecimalBox.swift
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 12/17/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct DecimalBox: Equatable {
|
||||
typealias Unboxed = Decimal
|
||||
|
||||
let unboxed: Unboxed
|
||||
|
||||
init(_ unboxed: Unboxed) {
|
||||
self.unboxed = unboxed
|
||||
}
|
||||
|
||||
init?(xmlString: String) {
|
||||
guard let unboxed = Unboxed(string: xmlString) else {
|
||||
return nil
|
||||
}
|
||||
self.init(unboxed)
|
||||
}
|
||||
}
|
||||
|
||||
extension DecimalBox: Box {
|
||||
var isNull: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/// # Lexical representation
|
||||
/// Decimal has a lexical representation consisting of a finite-length sequence of
|
||||
/// decimal digits separated by a period as a decimal indicator.
|
||||
/// An optional leading sign is allowed. If the sign is omitted, `"+"` is assumed.
|
||||
/// Leading and trailing zeroes are optional. If the fractional part is zero,
|
||||
/// the period and following zero(es) can be omitted.
|
||||
/// For example: `-1.23`, `12678967.543233`, `+100000.00`, `210`.
|
||||
///
|
||||
/// # Canonical representation
|
||||
/// The canonical representation for decimal is defined by prohibiting certain
|
||||
/// options from the Lexical representation. Specifically, the preceding optional
|
||||
/// `"+"` sign is prohibited. The decimal point is required. Leading and trailing
|
||||
/// zeroes are prohibited subject to the following: there must be at least one
|
||||
/// digit to the right and to the left of the decimal point which may be a zero.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// [Schema definition](https://www.w3.org/TR/xmlschema-2/#decimal)
|
||||
var xmlString: String? {
|
||||
return "\(unboxed)"
|
||||
}
|
||||
}
|
||||
|
||||
extension DecimalBox: SimpleBox {}
|
||||
|
||||
extension DecimalBox: CustomStringConvertible {
|
||||
var description: String {
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
49
third-party/XMLCoder/Sources/Auxiliaries/Box/DoubleBox.swift
vendored
Normal file
49
third-party/XMLCoder/Sources/Auxiliaries/Box/DoubleBox.swift
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2019-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Max Desiatov on 05/10/2019.
|
||||
//
|
||||
|
||||
struct DoubleBox: Equatable, ValueBox {
|
||||
typealias Unboxed = Double
|
||||
|
||||
let unboxed: Unboxed
|
||||
|
||||
init(_ value: Unboxed) {
|
||||
unboxed = value
|
||||
}
|
||||
|
||||
init?(xmlString: String) {
|
||||
guard let unboxed = Double(xmlString) else { return nil }
|
||||
|
||||
self.init(unboxed)
|
||||
}
|
||||
}
|
||||
|
||||
extension DoubleBox: Box {
|
||||
var isNull: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var xmlString: String? {
|
||||
guard !unboxed.isNaN else {
|
||||
return "NaN"
|
||||
}
|
||||
|
||||
guard !unboxed.isInfinite else {
|
||||
return (unboxed > 0.0) ? "INF" : "-INF"
|
||||
}
|
||||
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
|
||||
extension DoubleBox: SimpleBox {}
|
||||
|
||||
extension DoubleBox: CustomStringConvertible {
|
||||
var description: String {
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
78
third-party/XMLCoder/Sources/Auxiliaries/Box/FloatBox.swift
vendored
Normal file
78
third-party/XMLCoder/Sources/Auxiliaries/Box/FloatBox.swift
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 12/17/18.
|
||||
//
|
||||
|
||||
struct FloatBox: Equatable, ValueBox {
|
||||
typealias Unboxed = Float
|
||||
|
||||
let unboxed: Unboxed
|
||||
|
||||
init<Float: BinaryFloatingPoint>(_ unboxed: Float) {
|
||||
self.unboxed = Unboxed(unboxed)
|
||||
}
|
||||
|
||||
init?(xmlString: String) {
|
||||
guard let unboxed = Unboxed(xmlString) else {
|
||||
return nil
|
||||
}
|
||||
self.init(unboxed)
|
||||
}
|
||||
}
|
||||
|
||||
extension FloatBox: Box {
|
||||
var isNull: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/// # Lexical representation
|
||||
/// float values have a lexical representation consisting of a mantissa followed, optionally,
|
||||
/// by the character `"E"` or `"e"`, followed by an exponent. The exponent **must** be an integer.
|
||||
/// The mantissa **must** be a decimal number. The representations for exponent and mantissa **must**
|
||||
/// follow the lexical rules for integer and decimal. If the `"E"` or `"e"` and the following
|
||||
/// exponent are omitted, an exponent value of `0` is assumed.
|
||||
///
|
||||
/// The special values positive and negative infinity and not-a-number have lexical
|
||||
/// representations `INF`, `-INF` and `NaN`, respectively. Lexical representations for zero
|
||||
/// may take a positive or negative sign.
|
||||
///
|
||||
/// For example, `-1E4`, `1267.43233E12`, `12.78e-2`, `12` , `-0`, `0` and `INF` are all
|
||||
/// legal literals for float.
|
||||
///
|
||||
/// # Canonical representation
|
||||
/// The canonical representation for float is defined by prohibiting certain options from the
|
||||
/// Lexical representation. Specifically, the exponent must be indicated by `"E"`.
|
||||
/// Leading zeroes and the preceding optional `"+"` sign are prohibited in the exponent.
|
||||
/// If the exponent is zero, it must be indicated by `"E0"`. For the mantissa, the preceding
|
||||
/// optional `"+"` sign is prohibited and the decimal point is required. Leading and trailing
|
||||
/// zeroes are prohibited subject to the following: number representations must be normalized
|
||||
/// such that there is a single digit which is non-zero to the left of the decimal point and
|
||||
/// at least a single digit to the right of the decimal point unless the value being represented
|
||||
/// is zero. The canonical representation for zero is `0.0E0`.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// [Schema definition](https://www.w3.org/TR/xmlschema-2/#float)
|
||||
var xmlString: String? {
|
||||
guard !unboxed.isNaN else {
|
||||
return "NaN"
|
||||
}
|
||||
|
||||
guard !unboxed.isInfinite else {
|
||||
return (unboxed > 0.0) ? "INF" : "-INF"
|
||||
}
|
||||
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
|
||||
extension FloatBox: SimpleBox {}
|
||||
|
||||
extension FloatBox: CustomStringConvertible {
|
||||
var description: String {
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
59
third-party/XMLCoder/Sources/Auxiliaries/Box/IntBox.swift
vendored
Normal file
59
third-party/XMLCoder/Sources/Auxiliaries/Box/IntBox.swift
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 12/17/18.
|
||||
//
|
||||
|
||||
struct IntBox: Equatable {
|
||||
typealias Unboxed = Int64
|
||||
|
||||
let unboxed: Unboxed
|
||||
|
||||
init<Integer: SignedInteger>(_ unboxed: Integer) {
|
||||
self.unboxed = Unboxed(unboxed)
|
||||
}
|
||||
|
||||
init?(xmlString: String) {
|
||||
guard let unboxed = Unboxed(xmlString) else {
|
||||
return nil
|
||||
}
|
||||
self.init(unboxed)
|
||||
}
|
||||
|
||||
func unbox<Integer: BinaryInteger>() -> Integer? {
|
||||
return Integer(exactly: unboxed)
|
||||
}
|
||||
}
|
||||
|
||||
extension IntBox: Box {
|
||||
var isNull: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/// # Lexical representation
|
||||
/// Integer has a lexical representation consisting of a finite-length sequence of
|
||||
/// decimal digits with an optional leading sign. If the sign is omitted, `"+"` is assumed.
|
||||
/// For example: `-1`, `0`, `12678967543233`, `+100000`.
|
||||
///
|
||||
/// # Canonical representation
|
||||
/// The canonical representation for integer is defined by prohibiting certain
|
||||
/// options from the Lexical representation. Specifically, the preceding optional
|
||||
/// `"+"` sign is prohibited and leading zeroes are prohibited.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// [Schema definition](https://www.w3.org/TR/xmlschema-2/#integer)
|
||||
var xmlString: String? {
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
|
||||
extension IntBox: SimpleBox {}
|
||||
|
||||
extension IntBox: CustomStringConvertible {
|
||||
var description: String {
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
57
third-party/XMLCoder/Sources/Auxiliaries/Box/KeyedBox.swift
vendored
Normal file
57
third-party/XMLCoder/Sources/Auxiliaries/Box/KeyedBox.swift
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 11/19/18.
|
||||
//
|
||||
|
||||
struct KeyedBox {
|
||||
typealias Key = String
|
||||
typealias Attribute = SimpleBox
|
||||
typealias Element = Box
|
||||
|
||||
typealias Attributes = KeyedStorage<Key, Attribute>
|
||||
typealias Elements = KeyedStorage<Key, Element>
|
||||
|
||||
var elements = Elements()
|
||||
var attributes = Attributes()
|
||||
|
||||
var unboxed: (elements: Elements, attributes: Attributes) {
|
||||
return (
|
||||
elements: elements,
|
||||
attributes: attributes
|
||||
)
|
||||
}
|
||||
|
||||
var value: SimpleBox? {
|
||||
return elements.values.first as? SimpleBox
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyedBox {
|
||||
init<E, A>(elements: E, attributes: A)
|
||||
where E: Sequence, E.Element == (Key, Element),
|
||||
A: Sequence, A.Element == (Key, Attribute)
|
||||
{
|
||||
let elements = Elements(elements)
|
||||
let attributes = Attributes(attributes)
|
||||
self.init(elements: elements, attributes: attributes)
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyedBox: Box {
|
||||
var isNull: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var xmlString: String? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyedBox: CustomStringConvertible {
|
||||
var description: String {
|
||||
return "{attributes: \(attributes), elements: \(elements)}"
|
||||
}
|
||||
}
|
||||
33
third-party/XMLCoder/Sources/Auxiliaries/Box/NullBox.swift
vendored
Normal file
33
third-party/XMLCoder/Sources/Auxiliaries/Box/NullBox.swift
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 12/17/18.
|
||||
//
|
||||
|
||||
struct NullBox {}
|
||||
|
||||
extension NullBox: Box {
|
||||
var isNull: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var xmlString: String? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension NullBox: SimpleBox {}
|
||||
|
||||
extension NullBox: Equatable {
|
||||
static func ==(_: NullBox, _: NullBox) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension NullBox: CustomStringConvertible {
|
||||
var description: String {
|
||||
return "null"
|
||||
}
|
||||
}
|
||||
35
third-party/XMLCoder/Sources/Auxiliaries/Box/SharedBox.swift
vendored
Normal file
35
third-party/XMLCoder/Sources/Auxiliaries/Box/SharedBox.swift
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 12/22/18.
|
||||
//
|
||||
|
||||
class SharedBox<Unboxed: Box> {
|
||||
private(set) var unboxed: Unboxed
|
||||
|
||||
init(_ wrapped: Unboxed) {
|
||||
unboxed = wrapped
|
||||
}
|
||||
|
||||
func withShared<T>(_ body: (inout Unboxed) throws -> T) rethrows -> T {
|
||||
return try body(&unboxed)
|
||||
}
|
||||
}
|
||||
|
||||
extension SharedBox: Box {
|
||||
var isNull: Bool {
|
||||
return unboxed.isNull
|
||||
}
|
||||
|
||||
var xmlString: String? {
|
||||
return unboxed.xmlString
|
||||
}
|
||||
}
|
||||
|
||||
extension SharedBox: SharedBoxProtocol {
|
||||
func unbox() -> Unboxed {
|
||||
return unboxed
|
||||
}
|
||||
}
|
||||
25
third-party/XMLCoder/Sources/Auxiliaries/Box/SingleKeyedBox.swift
vendored
Normal file
25
third-party/XMLCoder/Sources/Auxiliaries/Box/SingleKeyedBox.swift
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2019-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by James Bean on 7/15/19.
|
||||
//
|
||||
|
||||
/// A `Box` which contains a single `key` and `element` pair. This is useful for disambiguating elements which could either represent
|
||||
/// an element nested in a keyed or unkeyed container, or an choice between multiple known-typed values (implemented in Swift using
|
||||
/// enums with associated values).
|
||||
struct SingleKeyedBox: SimpleBox {
|
||||
var key: String
|
||||
var element: Box
|
||||
}
|
||||
|
||||
extension SingleKeyedBox: Box {
|
||||
var isNull: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var xmlString: String? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
39
third-party/XMLCoder/Sources/Auxiliaries/Box/StringBox.swift
vendored
Normal file
39
third-party/XMLCoder/Sources/Auxiliaries/Box/StringBox.swift
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 12/17/18.
|
||||
//
|
||||
|
||||
struct StringBox: Equatable {
|
||||
typealias Unboxed = String
|
||||
|
||||
let unboxed: Unboxed
|
||||
|
||||
init(_ unboxed: Unboxed) {
|
||||
self.unboxed = unboxed
|
||||
}
|
||||
|
||||
init(xmlString: Unboxed) {
|
||||
self.init(xmlString)
|
||||
}
|
||||
}
|
||||
|
||||
extension StringBox: Box {
|
||||
var isNull: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var xmlString: String? {
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
|
||||
extension StringBox: SimpleBox {}
|
||||
|
||||
extension StringBox: CustomStringConvertible {
|
||||
var description: String {
|
||||
return unboxed.description
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue