diff --git a/Package.resolved b/Package.resolved index c200ce266..13a265af2 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "4b26e194c344de78e727de247c6c767bb827826c2340b7b32611453992a62d3d", + "originHash" : "394fa589a5a76e3ceafd3f71df512dd61ef2dc543eeef5abf1308cd86bf625c8", "pins" : [ { "identity" : "darwinprivateframeworks", diff --git a/Package.swift b/Package.swift index 8ec4fa3f0..484a2de56 100644 --- a/Package.swift +++ b/Package.swift @@ -194,6 +194,15 @@ let openSwiftUIBridgeTestTarget = Target.testTarget( sources: ["BridgeableTests.swift", bridgeFramework], swiftSettings: sharedSwiftSettings ) +let coreGraphicsShimsTestTarget = Target.testTarget( + name: "CoreGraphicsShimsTests", + dependencies: [ + "CoreGraphicsShims", + .product(name: "Numerics", package: "swift-numerics"), + ], + exclude: ["README.md"], + swiftSettings: sharedSwiftSettings +) // Workaround iOS CI build issue (We need to disable this on iOS CI) let supportMultiProducts: Bool = envEnable("OPENSWIFTUI_SUPPORT_MULTI_PRODUCTS", default: true) @@ -251,6 +260,7 @@ let package = Package( openSwiftUITestTarget, openSwiftUICompatibilityTestTarget, openSwiftUIBridgeTestTarget, + coreGraphicsShimsTestTarget, ] ) diff --git a/Sources/CoreGraphicsShims/CATransform3D.swift b/Sources/CoreGraphicsShims/CATransform3D.swift new file mode 100644 index 000000000..aea622fe3 --- /dev/null +++ b/Sources/CoreGraphicsShims/CATransform3D.swift @@ -0,0 +1,302 @@ +// +// CATransform3D.swift +// CoreGraphicsShims +// +// License: MIT +// Modified from https://github.com/flowkey/UIKit-cross-platform/blob/7e28dc4c62d20afe03e55bbba660076ec06fd79a/Sources/CATransform3D.swift + +#if !canImport(QuartzCore) +public import Foundation + +public struct CATransform3D { + public init( + m11: CGFloat, m12: CGFloat, m13: CGFloat, m14: CGFloat, + m21: CGFloat, m22: CGFloat, m23: CGFloat, m24: CGFloat, + m31: CGFloat, m32: CGFloat, m33: CGFloat, m34: CGFloat, + m41: CGFloat, m42: CGFloat, m43: CGFloat, m44: CGFloat + ) { + self.m11 = m11; self.m12 = m12; self.m13 = m13; self.m14 = m14; + self.m21 = m21; self.m22 = m22; self.m23 = m23; self.m24 = m24; + self.m31 = m31; self.m32 = m32; self.m33 = m33; self.m34 = m34; + self.m41 = m41; self.m42 = m42; self.m43 = m43; self.m44 = m44; + } + + public init() { + self.m11 = 0.0; self.m12 = 0.0; self.m13 = 0.0; self.m14 = 0.0; + self.m21 = 0.0; self.m22 = 0.0; self.m23 = 0.0; self.m24 = 0.0; + self.m31 = 0.0; self.m32 = 0.0; self.m33 = 0.0; self.m34 = 0.0; + self.m41 = 0.0; self.m42 = 0.0; self.m43 = 0.0; self.m44 = 0.0; + } + + public var m11: CGFloat; public var m12: CGFloat; public var m13: CGFloat; public var m14: CGFloat + public var m21: CGFloat; public var m22: CGFloat; public var m23: CGFloat; public var m24: CGFloat + public var m31: CGFloat; public var m32: CGFloat; public var m33: CGFloat; public var m34: CGFloat + public var m41: CGFloat; public var m42: CGFloat; public var m43: CGFloat; public var m44: CGFloat +} + +internal extension CATransform3D { + func transformingVector(x: CGFloat, y: CGFloat, z: CGFloat) -> (x: CGFloat, y: CGFloat, z: CGFloat) { + let newX = Double(m11) * Double(x) + Double(m21) * Double(y) + Double(m31) * Double(z) + Double(m41) + let newY = Double(m12) * Double(x) + Double(m22) * Double(y) + Double(m32) * Double(z) + Double(m42) + let newZ = Double(m13) * Double(x) + Double(m23) * Double(y) + Double(m33) * Double(z) + Double(m43) + let newW = Double(m14) * Double(x) + Double(m24) * Double(y) + Double(m34) * Double(z) + Double(m44) + + return ( + x: CGFloat(newX / newW), + y: CGFloat(newY / newW), + z: CGFloat(newZ / newW) + ) + } +} + +public let CATransform3DIdentity = CATransform3D( + m11: 1.0, m12: 0.0, m13: 0.0, m14: 0.0, + m21: 0.0, m22: 1.0, m23: 0.0, m24: 0.0, + m31: 0.0, m32: 0.0, m33: 1.0, m34: 0.0, + m41: 0.0, m42: 0.0, m43: 0.0, m44: 1.0 +) + +public func CATransform3DEqualToTransform(_ a: CATransform3D, _ b: CATransform3D) -> Bool { + return + a.m11 == b.m11 && a.m12 == b.m12 && a.m13 == b.m13 && a.m14 == b.m14 && + a.m21 == b.m21 && a.m22 == b.m22 && a.m23 == b.m23 && a.m24 == b.m24 && + a.m31 == b.m31 && a.m32 == b.m32 && a.m33 == b.m33 && a.m34 == b.m34 && + a.m41 == b.m41 && a.m42 == b.m42 && a.m43 == b.m43 && a.m44 == b.m44 +} + +extension CATransform3D: Equatable { + public static func == (_ lhs: CATransform3D, _ rhs: CATransform3D) -> Bool { + return CATransform3DEqualToTransform(lhs, rhs) + } + + // This isn't public API on iOS, although it'd probably be quite useful + internal static func * (_ lhs: CATransform3D, _ rhs: CATransform3D) -> CATransform3D { + return lhs.concat(rhs) + } +} + +extension CATransform3D: CustomStringConvertible { + public var description: String { + return """ + \(m11)\t\t\(m12)\t\t\(m13)\t\t\(m14) + \(m21)\t\t\(m22)\t\t\(m23)\t\t\(m24) + \(m31)\t\t\(m32)\t\t\(m33)\t\t\(m34) + \(m41)\t\t\(m42)\t\t\(m43)\t\t\(m44) + """ + } +} + + +// https://stackoverflow.com/a/5508486/3086440 +/* + | a b 0 | | a b 0 0 | + | d e 0 | => | d e 0 0 | + | g h 1 | | 0 0 1 0 | + | g h 0 1 | + */ +public func CATransform3DMakeAffineTransform(_ m: CGAffineTransform) -> CATransform3D { + return CATransform3D( + m11: m.a, m12: m.b, m13: 0.0, m14: 0.0, + m21: m.c, m22: m.d, m23: 0.0, m24: 0.0, + m31: 0.0, m32: 0.0, m33: 1.0, m34: 0.0, + m41: m.tx, m42: m.ty, m43: 0.0, m44: 1.0 + ) +} + +public func CATransform3DMakeScale(_ sx: CGFloat, _ sy: CGFloat, _ sz: CGFloat) -> CATransform3D { + return CATransform3D( + m11: sx, m12: 0, m13: 0, m14: 0, + m21: 0, m22: sy, m23: 0, m24: 0, + m31: 0, m32: 0, m33: sz, m34: 0, + m41: 0, m42: 0, m43: 0, m44: 1 + ) +} + +public func CATransform3DMakeTranslation(_ tx: CGFloat, _ ty: CGFloat, _ tz: CGFloat) -> CATransform3D { + return CATransform3D( + m11: 1, m12: 0, m13: 0, m14: 0, + m21: 0, m22: 1, m23: 0, m24: 0, + m31: 0, m32: 0, m33: 1, m34: 0, + m41: tx, m42: ty, m43: tz, m44: 1 + ) +} + +public func CATransform3DConcat(_ a: CATransform3D, _ b: CATransform3D) -> CATransform3D { + if a == CATransform3DIdentity { return b } + if b == CATransform3DIdentity { return a } + + var result = CATransform3D() + + result.m11 = a.m11 * b.m11 + a.m21 * b.m12 + a.m31 * b.m13 + a.m41 * b.m14 + result.m12 = a.m12 * b.m11 + a.m22 * b.m12 + a.m32 * b.m13 + a.m42 * b.m14 + result.m13 = a.m13 * b.m11 + a.m23 * b.m12 + a.m33 * b.m13 + a.m43 * b.m14 + result.m14 = a.m14 * b.m11 + a.m24 * b.m12 + a.m34 * b.m13 + a.m44 * b.m14 + + result.m21 = a.m11 * b.m21 + a.m21 * b.m22 + a.m31 * b.m23 + a.m41 * b.m24 + result.m22 = a.m12 * b.m21 + a.m22 * b.m22 + a.m32 * b.m23 + a.m42 * b.m24 + result.m23 = a.m13 * b.m21 + a.m23 * b.m22 + a.m33 * b.m23 + a.m43 * b.m24 + result.m24 = a.m14 * b.m21 + a.m24 * b.m22 + a.m34 * b.m23 + a.m44 * b.m24 + + result.m31 = a.m11 * b.m31 + a.m21 * b.m32 + a.m31 * b.m33 + a.m41 * b.m34 + result.m32 = a.m12 * b.m31 + a.m22 * b.m32 + a.m32 * b.m33 + a.m42 * b.m34 + result.m33 = a.m13 * b.m31 + a.m23 * b.m32 + a.m33 * b.m33 + a.m43 * b.m34 + result.m34 = a.m14 * b.m31 + a.m24 * b.m32 + a.m34 * b.m33 + a.m44 * b.m34 + + result.m41 = a.m11 * b.m41 + a.m21 * b.m42 + a.m31 * b.m43 + a.m41 * b.m44 + result.m42 = a.m12 * b.m41 + a.m22 * b.m42 + a.m32 * b.m43 + a.m42 * b.m44 + result.m43 = a.m13 * b.m41 + a.m23 * b.m42 + a.m33 * b.m43 + a.m43 * b.m44 + result.m44 = a.m14 * b.m41 + a.m24 * b.m42 + a.m34 * b.m43 + a.m44 * b.m44 + + return result +} + +extension CATransform3D { + func concat(_ other: CATransform3D) -> CATransform3D { + return CATransform3DConcat(self, other) + } +} + +public func CATransform3DGetAffineTransform(_ t: CATransform3D) -> CGAffineTransform { + return CGAffineTransform( + a: t.m11, b: t.m12, + c: t.m21, d: t.m22, + tx: t.m41, ty: t.m42 + ) +} + +/// Invert 't' and return the result. Returns the original matrix if 't' +/// has no inverse. +public func CATransform3DInvert(_ t: CATransform3D) -> CATransform3D { + // For identity matrix, return identity + if t == CATransform3DIdentity { + return t + } + + // Calculate determinant to check if matrix can be inverted + let det = t.m11 * (t.m22 * (t.m33 * t.m44 - t.m34 * t.m43) - + t.m23 * (t.m32 * t.m44 - t.m34 * t.m42) + + t.m24 * (t.m32 * t.m43 - t.m33 * t.m42)) - + t.m12 * (t.m21 * (t.m33 * t.m44 - t.m34 * t.m43) - + t.m23 * (t.m31 * t.m44 - t.m34 * t.m41) + + t.m24 * (t.m31 * t.m43 - t.m33 * t.m41)) + + t.m13 * (t.m21 * (t.m32 * t.m44 - t.m34 * t.m42) - + t.m22 * (t.m31 * t.m44 - t.m34 * t.m41) + + t.m24 * (t.m31 * t.m42 - t.m32 * t.m41)) - + t.m14 * (t.m21 * (t.m32 * t.m43 - t.m33 * t.m42) - + t.m22 * (t.m31 * t.m43 - t.m33 * t.m41) + + t.m23 * (t.m31 * t.m42 - t.m32 * t.m41)) + + // If determinant is too close to zero, matrix cannot be inverted + if abs(det) < 1e-6 { + return t + } + + let invDet = 1.0 / det + + // Calculate adjugate matrix and multiply by 1/determinant + var result = CATransform3D() + + // Row 1 + result.m11 = invDet * ( + t.m22 * (t.m33 * t.m44 - t.m34 * t.m43) - + t.m23 * (t.m32 * t.m44 - t.m34 * t.m42) + + t.m24 * (t.m32 * t.m43 - t.m33 * t.m42) + ) + + result.m12 = -invDet * ( + t.m12 * (t.m33 * t.m44 - t.m34 * t.m43) - + t.m13 * (t.m32 * t.m44 - t.m34 * t.m42) + + t.m14 * (t.m32 * t.m43 - t.m33 * t.m42) + ) + + result.m13 = invDet * ( + t.m12 * (t.m23 * t.m44 - t.m24 * t.m43) - + t.m13 * (t.m22 * t.m44 - t.m24 * t.m42) + + t.m14 * (t.m22 * t.m43 - t.m23 * t.m42) + ) + + result.m14 = -invDet * ( + t.m12 * (t.m23 * t.m34 - t.m24 * t.m33) - + t.m13 * (t.m22 * t.m34 - t.m24 * t.m32) + + t.m14 * (t.m22 * t.m33 - t.m23 * t.m32) + ) + + // Row 2 + result.m21 = -invDet * ( + t.m21 * (t.m33 * t.m44 - t.m34 * t.m43) - + t.m23 * (t.m31 * t.m44 - t.m34 * t.m41) + + t.m24 * (t.m31 * t.m43 - t.m33 * t.m41) + ) + + result.m22 = invDet * ( + t.m11 * (t.m33 * t.m44 - t.m34 * t.m43) - + t.m13 * (t.m31 * t.m44 - t.m34 * t.m41) + + t.m14 * (t.m31 * t.m43 - t.m33 * t.m41) + ) + + result.m23 = -invDet * ( + t.m11 * (t.m23 * t.m44 - t.m24 * t.m43) - + t.m13 * (t.m21 * t.m44 - t.m24 * t.m41) + + t.m14 * (t.m21 * t.m43 - t.m23 * t.m41) + ) + + result.m24 = invDet * ( + t.m11 * (t.m23 * t.m34 - t.m24 * t.m33) - + t.m13 * (t.m21 * t.m34 - t.m24 * t.m31) + + t.m14 * (t.m21 * t.m33 - t.m23 * t.m31) + ) + + // Row 3 + result.m31 = invDet * ( + t.m21 * (t.m32 * t.m44 - t.m34 * t.m42) - + t.m22 * (t.m31 * t.m44 - t.m34 * t.m41) + + t.m24 * (t.m31 * t.m42 - t.m32 * t.m41) + ) + + result.m32 = -invDet * ( + t.m11 * (t.m32 * t.m44 - t.m34 * t.m42) - + t.m12 * (t.m31 * t.m44 - t.m34 * t.m41) + + t.m14 * (t.m31 * t.m42 - t.m32 * t.m41) + ) + + result.m33 = invDet * ( + t.m11 * (t.m22 * t.m44 - t.m24 * t.m42) - + t.m12 * (t.m21 * t.m44 - t.m24 * t.m41) + + t.m14 * (t.m21 * t.m42 - t.m22 * t.m41) + ) + + result.m34 = -invDet * ( + t.m11 * (t.m22 * t.m34 - t.m24 * t.m32) - + t.m12 * (t.m21 * t.m34 - t.m24 * t.m31) + + t.m14 * (t.m21 * t.m32 - t.m22 * t.m31) + ) + + // Row 4 + result.m41 = -invDet * ( + t.m21 * (t.m32 * t.m43 - t.m33 * t.m42) - + t.m22 * (t.m31 * t.m43 - t.m33 * t.m41) + + t.m23 * (t.m31 * t.m42 - t.m32 * t.m41) + ) + + result.m42 = invDet * ( + t.m11 * (t.m32 * t.m43 - t.m33 * t.m42) - + t.m12 * (t.m31 * t.m43 - t.m33 * t.m41) + + t.m13 * (t.m31 * t.m42 - t.m32 * t.m41) + ) + + result.m43 = -invDet * ( + t.m11 * (t.m22 * t.m43 - t.m23 * t.m42) - + t.m12 * (t.m21 * t.m43 - t.m23 * t.m41) + + t.m13 * (t.m21 * t.m42 - t.m22 * t.m41) + ) + + result.m44 = invDet * ( + t.m11 * (t.m22 * t.m33 - t.m23 * t.m32) - + t.m12 * (t.m21 * t.m33 - t.m23 * t.m31) + + t.m13 * (t.m21 * t.m32 - t.m22 * t.m31) + ) + + return result +} + +#endif diff --git a/Sources/CoreGraphicsShims/CGAffineTransform.swift b/Sources/CoreGraphicsShims/CGAffineTransform.swift index 0e0c27752..deba2c741 100644 --- a/Sources/CoreGraphicsShims/CGAffineTransform.swift +++ b/Sources/CoreGraphicsShims/CGAffineTransform.swift @@ -5,8 +5,20 @@ #if !canImport(CoreGraphics) public import Foundation -// FIXME: Use Silica or other implementation public struct CGAffineTransform: Equatable { + public init(translationX tx: CGFloat, y ty: CGFloat) { + self.init(a: 1, b: 0, c: 0, d: 1, tx: Double(tx), ty: Double(ty)) + } + + public init(scaleX sx: CGFloat, y sy: CGFloat) { + self.init(a: Double(sx), b: 0, c: 0, d: Double(sy), tx: 0, ty: 0) + } + + public init(rotationAngle angle: CGFloat) { + let radians = Double(angle) + self.init(a: cos(radians), b: sin(radians), c: -sin(radians), d: cos(radians), tx: 0, ty: 0) + } + public init() { a = .zero b = .zero diff --git a/Sources/CoreGraphicsShims/Export.swift b/Sources/CoreGraphicsShims/Export.swift index 92457f173..54aa663ed 100644 --- a/Sources/CoreGraphicsShims/Export.swift +++ b/Sources/CoreGraphicsShims/Export.swift @@ -4,6 +4,10 @@ #if canImport(CoreGraphics) @_exported import CoreGraphics -#else -@_exported import CoreFoundation #endif + +#if canImport(QuartzCore) +@_exported import QuartzCore +#endif + +@_exported import CoreFoundation diff --git a/Sources/OpenSwiftUICore/Layout/Geometry/ProjectionTransform.swift b/Sources/OpenSwiftUICore/Layout/Geometry/ProjectionTransform.swift index 41a7be1ca..59c55c7ad 100644 --- a/Sources/OpenSwiftUICore/Layout/Geometry/ProjectionTransform.swift +++ b/Sources/OpenSwiftUICore/Layout/Geometry/ProjectionTransform.swift @@ -7,9 +7,6 @@ public import Foundation public import CoreGraphicsShims -#if canImport(QuartzCore) -public import QuartzCore -#endif @frozen public struct ProjectionTransform { @@ -37,7 +34,6 @@ public struct ProjectionTransform { @inlinable public init() {} - #if canImport(QuartzCore) @inlinable public init(_ m: CGAffineTransform) { m11 = m.a @@ -60,7 +56,6 @@ public struct ProjectionTransform { m32 = m.m42 m33 = m.m44 } - #endif @inlinable public var isIdentity: Bool { @@ -192,7 +187,6 @@ extension CGAffineTransform { } } -#if canImport(QuartzCore) extension CATransform3D { package init(_ m: ProjectionTransform) { self.init( @@ -203,7 +197,6 @@ extension CATransform3D { ) } } -#endif extension ProjectionTransform: ProtobufMessage { package func encode(to encoder: inout ProtobufEncoder) { diff --git a/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift b/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift index 7b2e23839..ce483cf39 100644 --- a/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift +++ b/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift @@ -9,6 +9,7 @@ public import Foundation package import OpenGraphShims +package import CoreGraphicsShims import OpenSwiftUI_SPI /// Namespace for view debug information. @@ -441,38 +442,28 @@ extension ViewTransform.Item: Encodable { case let .translation(size): try container.encode(size, forKey: .transform) case let .affineTransform(affineTransform, inverse): - #if canImport(QuartzCore) var transform3D = CATransform3DMakeAffineTransform(affineTransform) if inverse { transform3D = CATransform3DInvert(transform3D) } try container.encode(transform3D.elements, forKey: .affineTransform) - #else - preconditionFailure("CATransform3D is not available on this platform") - #endif case let .projectionTransform(projectionTransform, inverse): - #if canImport(QuartzCore) var transform3D = CATransform3D(projectionTransform) if inverse { transform3D = CATransform3DInvert(transform3D) } try container.encode(transform3D.elements, forKey: .projectionTransform) - #else - preconditionFailure("CATransform3D is not available on this platform") - #endif default: break } } } -#if canImport(QuartzCore) extension CATransform3D { fileprivate var elements: [CGFloat] { [m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44] } } -#endif package protocol ValueWrapper { var wrappedValue: Any? { get } diff --git a/Tests/CoreGraphicsShimsTests/CATransform3DTests.swift b/Tests/CoreGraphicsShimsTests/CATransform3DTests.swift new file mode 100644 index 000000000..ff07f120c --- /dev/null +++ b/Tests/CoreGraphicsShimsTests/CATransform3DTests.swift @@ -0,0 +1,85 @@ +// +// CATransform3DTests.swift +// CoreGraphicsShimsTests + +import Testing +import CoreGraphicsShims +import Numerics + +@Suite +struct CATransform3DTests { + + @Test + func identityInvert() { + let identity = CATransform3DIdentity + let inverted = CATransform3DInvert(identity) + + #expect(CATransform3DEqualToTransform(inverted, identity)) + } + + @Test + func translationInvert() { + let translation = CATransform3DMakeTranslation(10, 20, 30) + let inverted = CATransform3DInvert(translation) + let expected = CATransform3DMakeTranslation(-10, -20, -30) + + #expect(CATransform3DEqualToTransform(inverted, expected)) + } + + @Test + func scaleInvert() { + let scale = CATransform3DMakeScale(2, 3, 4) + let inverted = CATransform3DInvert(scale) + let expected = CATransform3DMakeScale(1/2, 1/3, 1/4) + + // Use approximation for floating-point comparison + #expect(inverted.m11.isApproximatelyEqual(to: expected.m11)) + #expect(inverted.m22.isApproximatelyEqual(to: expected.m22)) + #expect(inverted.m33.isApproximatelyEqual(to: expected.m33)) + } + + @Test + func invertMultiplicationProperty() { + // Create a complex transform + let translation = CATransform3DMakeTranslation(10, 20, 30) + let scale = CATransform3DMakeScale(2, 3, 4) + let transform = CATransform3DConcat(translation, scale) + + // Test that T * T^-1 = Identity + let inverted = CATransform3DInvert(transform) + let result = CATransform3DConcat(transform, inverted) + + // Check key elements of result against identity matrix + #expect(result.m11.isApproximatelyEqual(to: 1.0)) + #expect(result.m22.isApproximatelyEqual(to: 1.0)) + #expect(result.m33.isApproximatelyEqual(to: 1.0)) + #expect(result.m44.isApproximatelyEqual(to: 1.0)) + + // Check that off-diagonal elements are approximately zero + #expect(result.m12.isApproximatelyEqual(to: 0.0)) + #expect(result.m13.isApproximatelyEqual(to: 0.0)) + #expect(result.m14.isApproximatelyEqual(to: 0.0)) + #expect(result.m21.isApproximatelyEqual(to: 0.0)) + #expect(result.m23.isApproximatelyEqual(to: 0.0)) + #expect(result.m24.isApproximatelyEqual(to: 0.0)) + #expect(result.m31.isApproximatelyEqual(to: 0.0)) + #expect(result.m32.isApproximatelyEqual(to: 0.0)) + #expect(result.m34.isApproximatelyEqual(to: 0.0)) + #expect(result.m41.isApproximatelyEqual(to: 0.0)) + #expect(result.m42.isApproximatelyEqual(to: 0.0)) + #expect(result.m43.isApproximatelyEqual(to: 0.0)) + } + + @Test + func nonInvertibleMatrix() { + // Create a singular matrix (not invertible) + var singular = CATransform3DIdentity + singular.m11 = 0 + singular.m22 = 0 + + let result = CATransform3DInvert(singular) + + // Should return the same matrix for non-invertible input + #expect(CATransform3DEqualToTransform(result, singular)) + } +} diff --git a/Tests/CoreGraphicsShimsTests/README.md b/Tests/CoreGraphicsShimsTests/README.md new file mode 100644 index 000000000..8126d0753 --- /dev/null +++ b/Tests/CoreGraphicsShimsTests/README.md @@ -0,0 +1,7 @@ +## OpenCoreGraphicsShimsTests + +Test API of OpenCoreGraphicsShims + +```swift +import OpenCoreGraphicsShims +``` diff --git a/Tests/OpenSwiftUICoreTests/Layout/Geometry/ProjectionTransformTests.swift b/Tests/OpenSwiftUICoreTests/Layout/Geometry/ProjectionTransformTests.swift index 8a7bb0a4f..35d6a0540 100644 --- a/Tests/OpenSwiftUICoreTests/Layout/Geometry/ProjectionTransformTests.swift +++ b/Tests/OpenSwiftUICoreTests/Layout/Geometry/ProjectionTransformTests.swift @@ -2,13 +2,11 @@ // ProjectionTransformTests.swift // OpenSwiftUICoreTests -import Testing -import OpenSwiftUICore -import Numerics +import CoreGraphicsShims import Foundation -#if canImport(QuartzCore) -import QuartzCore -#endif +import Numerics +import OpenSwiftUICore +import Testing struct ProjectionTransformTests { // MARK: - Initialization Tests @@ -16,75 +14,67 @@ struct ProjectionTransformTests { @Test func defaultInit() { let transform = ProjectionTransform() - #expect(transform.m11 == 1.0) - #expect(transform.m12 == 0.0) - #expect(transform.m13 == 0.0) - #expect(transform.m21 == 0.0) - #expect(transform.m22 == 1.0) - #expect(transform.m23 == 0.0) - #expect(transform.m31 == 0.0) - #expect(transform.m32 == 0.0) - #expect(transform.m33 == 1.0) + #expect(transform.m11.isApproximatelyEqual(to: 1.0)) + #expect(transform.m12.isApproximatelyEqual(to: 0.0)) + #expect(transform.m13.isApproximatelyEqual(to: 0.0)) + #expect(transform.m21.isApproximatelyEqual(to: 0.0)) + #expect(transform.m22.isApproximatelyEqual(to: 1.0)) + #expect(transform.m23.isApproximatelyEqual(to: 0.0)) + #expect(transform.m31.isApproximatelyEqual(to: 0.0)) + #expect(transform.m32.isApproximatelyEqual(to: 0.0)) + #expect(transform.m33.isApproximatelyEqual(to: 1.0)) } - #if canImport(QuartzCore) @Test func cgAffineTransformInit() { let affine = CGAffineTransform(a: 2, b: 3, c: 4, d: 5, tx: 6, ty: 7) let transform = ProjectionTransform(affine) - #expect(transform.m11 == 2) - #expect(transform.m12 == 3) - #expect(transform.m21 == 4) - #expect(transform.m22 == 5) - #expect(transform.m31 == 6) - #expect(transform.m32 == 7) - #expect(transform.m13 == 0) - #expect(transform.m23 == 0) - #expect(transform.m33 == 1) + #expect(transform.m11.isApproximatelyEqual(to: 2)) + #expect(transform.m12.isApproximatelyEqual(to: 3)) + #expect(transform.m21.isApproximatelyEqual(to: 4)) + #expect(transform.m22.isApproximatelyEqual(to: 5)) + #expect(transform.m31.isApproximatelyEqual(to: 6)) + #expect(transform.m32.isApproximatelyEqual(to: 7)) + #expect(transform.m13.isApproximatelyEqual(to: 0)) + #expect(transform.m23.isApproximatelyEqual(to: 0)) + #expect(transform.m33.isApproximatelyEqual(to: 1)) } @Test func caTransform3DInit() { let t3d = CATransform3DMakeTranslation(1, 2, 3) let transform = ProjectionTransform(t3d) - #expect(transform.m11 == 1) - #expect(transform.m12 == 0) - #expect(transform.m13 == t3d.m14) - #expect(transform.m21 == 0) - #expect(transform.m22 == 1) - #expect(transform.m23 == t3d.m24) - #expect(transform.m31 == 1) - #expect(transform.m32 == 2) - #expect(transform.m33 == t3d.m44) + #expect(transform.m11.isApproximatelyEqual(to: 1)) + #expect(transform.m12.isApproximatelyEqual(to: 0)) + #expect(transform.m13.isApproximatelyEqual(to: t3d.m14)) + #expect(transform.m21.isApproximatelyEqual(to: 0)) + #expect(transform.m22.isApproximatelyEqual(to: 1)) + #expect(transform.m23.isApproximatelyEqual(to: t3d.m24)) + #expect(transform.m31.isApproximatelyEqual(to: 1)) + #expect(transform.m32.isApproximatelyEqual(to: 2)) + #expect(transform.m33.isApproximatelyEqual(to: t3d.m44)) } - #endif - + // MARK: - Property Tests @Test func isIdentity() { let identity = ProjectionTransform() #expect(identity.isIdentity) - - #if canImport(QuartzCore) let nonIdentity = ProjectionTransform(CGAffineTransform(translationX: 1, y: 1)) #expect(!nonIdentity.isIdentity) - #endif } @Test func isAffine() { - #if canImport(QuartzCore) let affine = ProjectionTransform(CGAffineTransform.identity) #expect(affine.isAffine) - #endif - + var nonAffine = ProjectionTransform() nonAffine.m13 = 0.5 #expect(!nonAffine.isAffine) } - #if canImport(QuartzCore) @Test( arguments: [ // Affine transforms @@ -113,38 +103,17 @@ struct ProjectionTransformTests { func determinant(transform: ProjectionTransform, expectedDet: CGFloat) { #expect(transform.determinant.isApproximatelyEqual(to: expectedDet)) } - #else - @Test( - arguments: [ - // Non-affine transforms with non-zero determinant - ( - ProjectionTransform( - m11: 1, m12: 2, m13: 3, - m21: 0, m22: 1, m23: 4, - m31: 5, m32: 6, m33: 0 - ), - 1.0 - ), - ] - ) - func determinant(transform: ProjectionTransform, expectedDet: CGFloat) { - #expect(transform.determinant.isApproximatelyEqual(to: expectedDet)) - } - #endif - // MARK: - Matrix Operation Tests @Test func invert() { - #if canImport(QuartzCore) var transform = ProjectionTransform(CGAffineTransform(scaleX: 2, y: 2)) let transformInvertResult = transform.invert() #expect(transformInvertResult == true) - #expect(transform.m11 == 0.5) - #expect(transform.m22 == 0.5) - #endif - + #expect(transform.m11.isApproximatelyEqual(to: 0.5)) + #expect(transform.m22.isApproximatelyEqual(to: 0.5)) + var singular = ProjectionTransform() singular.m11 = 0 singular.m22 = 0 @@ -154,96 +123,89 @@ struct ProjectionTransformTests { @Test func inverted() { - #if canImport(QuartzCore) let transform = ProjectionTransform(CGAffineTransform(scaleX: 2, y: 2)) let inverted = transform.inverted() - #expect(inverted.m11 == 0.5) - #expect(inverted.m22 == 0.5) - #endif + #expect(inverted.m11.isApproximatelyEqual(to: 0.5)) + #expect(inverted.m22.isApproximatelyEqual(to: 0.5)) } @Test func concatenating() { - #if canImport(QuartzCore) let t1 = ProjectionTransform(CGAffineTransform(translationX: 1, y: 0)) let t2 = ProjectionTransform(CGAffineTransform(translationX: 0, y: 2)) let result = t1.concatenating(t2) - #expect(result.m31 == 1) - #expect(result.m32 == 2) - #endif + #expect(result.m31.isApproximatelyEqual(to: 1)) + #expect(result.m32.isApproximatelyEqual(to: 2)) } // MARK: - Point Transform Tests @Test func applyingToPoint() { - #if canImport(QuartzCore) // Test affine transform let transform = ProjectionTransform(CGAffineTransform(translationX: 1, y: 2)) let point = CGPoint(x: 1, y: 1) let transformed = point.applying(transform) - #expect(transformed.x == 2) - #expect(transformed.y == 3) - #endif - + #expect(transformed.x.isApproximatelyEqual(to: 2)) + #expect(transformed.y.isApproximatelyEqual(to: 3)) + // Test perspective transform var perspective = ProjectionTransform() perspective.m13 = 0.5 let perspectivePoint = CGPoint(x: 2, y: 0).applying(perspective) - #expect(perspectivePoint.x != 2) // Point should be transformed by perspective + // Point should be transformed by perspective + #expect(!perspectivePoint.x.isApproximatelyEqual(to: 2)) } @Test func unapplyingToPoint() { - #if canImport(QuartzCore) // Test with invertible transform let transform = ProjectionTransform(CGAffineTransform(translationX: 1, y: 2)) let point = CGPoint(x: 2, y: 3) let untransformed = point.unapplying(transform) - #expect(untransformed.x == 1) - #expect(untransformed.y == 1) - #endif - + #expect(untransformed.x.isApproximatelyEqual(to: 1.0)) + #expect(untransformed.y.isApproximatelyEqual(to: 1.0)) + // Test with non-invertible transform var singular = ProjectionTransform() singular.m11 = 0 singular.m22 = 0 let originalPoint = CGPoint(x: 1, y: 1) let result = originalPoint.unapplying(singular) - #expect(result == originalPoint) // Should return original point for non-invertible transform + // Should return original point for non-invertible transform + #expect(result.x.isApproximatelyEqual(to: originalPoint.x)) + #expect(result.y.isApproximatelyEqual(to: originalPoint.y)) } - #if canImport(QuartzCore) // MARK: - Conversion Tests @Test func toCATransform3D() { let projection = ProjectionTransform() let transform3D = CATransform3D(projection) - #expect(transform3D.m11 == projection.m11) - #expect(transform3D.m12 == projection.m12) - #expect(transform3D.m14 == projection.m13) - #expect(transform3D.m21 == projection.m21) - #expect(transform3D.m22 == projection.m22) - #expect(transform3D.m24 == projection.m23) - #expect(transform3D.m41 == projection.m31) - #expect(transform3D.m42 == projection.m32) - #expect(transform3D.m44 == projection.m33) + #expect(transform3D.m11.isApproximatelyEqual(to: projection.m11)) + #expect(transform3D.m12.isApproximatelyEqual(to: projection.m12)) + #expect(transform3D.m14.isApproximatelyEqual(to: projection.m13)) + #expect(transform3D.m21.isApproximatelyEqual(to: projection.m21)) + #expect(transform3D.m22.isApproximatelyEqual(to: projection.m22)) + #expect(transform3D.m24.isApproximatelyEqual(to: projection.m23)) + #expect(transform3D.m41.isApproximatelyEqual(to: projection.m31)) + #expect(transform3D.m42.isApproximatelyEqual(to: projection.m32)) + #expect(transform3D.m44.isApproximatelyEqual(to: projection.m33)) } @Test func toCGAffineTransform() { let projection = ProjectionTransform() let affine = CGAffineTransform(projection) - #expect(affine.a == projection.m11) - #expect(affine.b == projection.m12) - #expect(affine.c == projection.m21) - #expect(affine.d == projection.m22) - #expect(affine.tx == projection.m31) - #expect(affine.ty == projection.m32) + #expect(affine.a.isApproximatelyEqual(to: projection.m11)) + #expect(affine.b.isApproximatelyEqual(to: projection.m12)) + #expect(affine.c.isApproximatelyEqual(to: projection.m21)) + #expect(affine.d.isApproximatelyEqual(to: projection.m22)) + #expect(affine.tx.isApproximatelyEqual(to: projection.m31)) + #expect(affine.ty.isApproximatelyEqual(to: projection.m32)) } - #endif - + // MARK: - ProtobufMessage Tests @Test(