diff --git a/.gitignore b/.gitignore index 0023a53..4b2e945 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ DerivedData/ .swiftpm/configuration/registries.json .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .netrc +*.xcuserstate diff --git a/Package.resolved b/Package.resolved index 33bad59..4787cb3 100644 --- a/Package.resolved +++ b/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/tomasf/SwiftSCAD.git", "state" : { - "revision" : "d874d042c45046f1f87b4e27bc27ddd292fa874a", - "version" : "0.7.1" + "revision" : "06d0fa1ee58b340ce288ded167e91dda02e11119", + "version" : "0.8.1" } } ], diff --git a/Package.swift b/Package.swift index 60bd345..416f6dd 100644 --- a/Package.swift +++ b/Package.swift @@ -1,22 +1,21 @@ -// swift-tools-version: 5.9 -// The swift-tools-version declares the minimum version of Swift required to build this package. +// swift-tools-version: 6.0 import PackageDescription let package = Package( name: "RichText", - platforms: [.macOS(.v14)], + platforms: [.macOS(.v13)], products: [ .library(name: "RichText", targets: ["RichText"]), ], dependencies: [ - .package(url: "https://github.com/tomasf/SwiftSCAD.git", from: "0.7.1"), - .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), + .package(url: "https://github.com/tomasf/SwiftSCAD.git", .upToNextMinor(from: "0.8.1")), + .package(url: "https://github.com/apple/swift-log.git", from: "1.6.1"), ], targets: [ .target(name: "RichText", dependencies: [ - "SwiftSCAD", - .product(name: "Logging", package: "swift-log") - ]), + .product(name: "SwiftSCAD", package: "SwiftSCAD"), + .product(name: "Logging", package: "swift-log"), + ]) ] ) diff --git a/README.md b/README.md index 44d0c61..1b7df27 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,16 @@ RichText is a macOS-specific companion library for SwiftSCAD that adds TextKit-based text generation. This enables proper Unicode support, richer typography, attribute ranges, multi-line text, constrained layout, bounding boxes, glyph-level manipulation and more. This package also contains a `Geometry2D` extension for `CGPath` that can be used for other Core Graphics-related purposes. + +
+let package = Package(
+    name: "thingamajig",
+    dependencies: [
+        .package(url: "https://github.com/tomasf/SwiftSCAD.git", .upToNextMinor(from: "0.8.1")),
+        .package(url: "https://github.com/tomasf/RichText.git", from: "0.1.0")
+    ],
+    targets: [
+        .executableTarget(name: "thingamajig", dependencies: ["SwiftSCAD", "RichText"])
+    ]
+)
+
diff --git a/Sources/RichText/Environment/Environment+FillRule.swift b/Sources/RichText/Environment/Environment+FillRule.swift index b9951d1..1bbf791 100644 --- a/Sources/RichText/Environment/Environment+FillRule.swift +++ b/Sources/RichText/Environment/Environment+FillRule.swift @@ -1,11 +1,12 @@ import Foundation import SwiftSCAD -import QuartzCore +@preconcurrency import QuartzCore extension CGPath { static internal let fillRuleEnvironmentKey: Environment.ValueKey = .init(rawValue: "CGPath.FillRule") } + public extension Environment { var cgPathFillRule: CGPathFillRule { (self[CGPath.fillRuleEnvironmentKey] as? CGPathFillRule) ?? .evenOdd diff --git a/Sources/RichText/Environment/Environment+RichText.swift b/Sources/RichText/Environment/Environment+RichText.swift index cbb0625..34cae6e 100644 --- a/Sources/RichText/Environment/Environment+RichText.swift +++ b/Sources/RichText/Environment/Environment+RichText.swift @@ -46,7 +46,7 @@ public extension Geometry2D { withEnvironment { $0.withBaselineAlignment(alignment) } } - func usingTextAttribute(_ key: K.Type, value: K.Value?) -> any Geometry2D { + func usingTextAttribute(_ key: K.Type, value: K.Value?) -> any Geometry2D where K.Value: Sendable { withEnvironment { environment in var container = environment.textAttributeContainer container[K.self] = value @@ -64,7 +64,7 @@ public extension Geometry3D { withEnvironment { $0.withBaselineAlignment(alignment) } } - func usingTextAttribute(_ key: K.Type, value: K.Value?) -> any Geometry3D { + func usingTextAttribute(_ key: K.Type, value: K.Value?) -> any Geometry3D where K.Value: Sendable { withEnvironment { environment in var container = environment.textAttributeContainer container[K.self] = value diff --git a/Sources/RichText/Extensions/Geometry2D+CGPath.swift b/Sources/RichText/Extensions/Geometry2D+CGPath.swift index af2963f..9c7cb3b 100644 --- a/Sources/RichText/Extensions/Geometry2D+CGPath.swift +++ b/Sources/RichText/Extensions/Geometry2D+CGPath.swift @@ -4,9 +4,9 @@ import QuartzCore extension QuartzCore.CGPath: SwiftSCAD.Shape2D { public var body: any Geometry2D { - EnvironmentReader { environment in - self.componentsSeparated(using: environment.cgPathFillRule).map { component in - let (positive, negatives) = component.normalizedPolygons(using: environment.cgPathFillRule) + readEnvironment(\.cgPathFillRule) { fillRule in + self.componentsSeparated(using: fillRule).map { component in + let (positive, negatives) = component.normalizedPolygons(using: fillRule) return positive.subtracting { negatives } } } diff --git a/Sources/RichText/RichText.swift b/Sources/RichText/RichText.swift index 8b9ce51..80f849b 100644 --- a/Sources/RichText/RichText.swift +++ b/Sources/RichText/RichText.swift @@ -5,6 +5,9 @@ public struct RichText: Shape2D { internal let text: AttributedString internal let layout: Layout + @EnvironmentValue(\.self) var environment + @EnvironmentValue(\.textBoundaryType) var textBoundaryType + public init(_ text: AttributedString, layout: Layout = .free) { self.text = text self.layout = layout @@ -15,26 +18,23 @@ public struct RichText: Shape2D { } public var body: any Geometry2D { - EnvironmentReader { environment in - let lineFragments = lineFragments(in: environment) + let lineFragments = lineFragments(in: environment) - Union { - for fragment in lineFragments { - for glyph in fragment.glyphs { - glyph.shape.translated(glyph.location) - } - } - } - .modifyingBounds { box in - environment.textBoundaryType == .shape ? box : - .init(union: lineFragments.map(\.glyphBox)) + lineFragments.map { + $0.glyphs.map { $0.shape.translated($0.location) } + } + .modifyingBounds { box in + if let box, textBoundaryType == .shape { + box + } else { + .init(union: lineFragments.map(\.glyphBox)) } - .usingCGPathFillRule(.winding) } + .usingCGPathFillRule(.winding) } public func readingLineFragments(@UnionBuilder2D _ reader: @escaping ([LineFragment]) -> any Geometry2D) -> any Geometry2D { - EnvironmentReader { environment in + readEnvironment { environment in reader(lineFragments(in: environment)) .usingCGPathFillRule(.winding) } @@ -73,12 +73,12 @@ public extension RichText { } } - enum BaselineAlignment { + enum BaselineAlignment: Sendable { case first case last } - enum BoundaryType { + enum BoundaryType: Sendable { case shape case lineFragments }