Skip to content

Commit dbc0248

Browse files
authored
Add Typesetting API for View and Text (#660)
1 parent 2d3de54 commit dbc0248

File tree

4 files changed

+350
-19
lines changed

4 files changed

+350
-19
lines changed

Sources/OpenSwiftUICore/View/Text/Resolve/ResolvedText.swift

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -137,27 +137,27 @@ extension Text {
137137
// MARK: - Text.Style [WIP]
138138

139139
package struct Style {
140-
private var baseFont: TextStyleFont
141-
private var fontModifiers: [AnyFontModifier]
142-
private var color: TextStyleColor
143-
private var backgroundColor: Color?
144-
private var baselineOffset: CGFloat?
145-
private var kerning: CGFloat?
146-
private var tracking: CGFloat?
147-
private var strikethrough: LineStyle
148-
private var underline: LineStyle
149-
// private var encapsulation: Text.Encapsulation?
150-
private var speech: AccessibilitySpeechAttributes?
140+
internal var baseFont: TextStyleFont
141+
internal var fontModifiers: [AnyFontModifier]
142+
internal var color: TextStyleColor
143+
internal var backgroundColor: Color?
144+
internal var baselineOffset: CGFloat?
145+
internal var kerning: CGFloat?
146+
internal var tracking: CGFloat?
147+
internal var strikethrough: LineStyle
148+
internal var underline: LineStyle
149+
internal var encapsulation: Text.Encapsulation?
150+
internal var speech: AccessibilitySpeechAttributes?
151151
package var accessibility: AccessibilityTextAttributes?
152-
// private var glyphInfo: CTGlyphInfo?
153-
// private var shadow: TextShadowModifier?
154-
// private var transition: TextTransitionModifier?
155-
// private var scale: Text.Scale?
156-
// private var superscript: Text.Superscript?
157-
// private var typesettingConfiguration: TypesettingConfiguration
158-
// private var customAttributes: [TextAttributeModifierBase]
152+
// internal var glyphInfo: CTGlyphInfo?
153+
// internal var shadow: TextShadowModifier?
154+
// internal var transition: TextTransitionModifier?
155+
// internal var scale: Text.Scale?
156+
// internal var superscript: Text.Superscript?
157+
internal var typesettingConfiguration: TypesettingConfiguration
158+
// internal var customAttributes: [TextAttributeModifierBase]
159159
#if canImport(Darwin)
160-
// private var adaptiveImageGlyph: AttributedString.AdaptiveImageGlyph?
160+
// internal var adaptiveImageGlyph: AttributedString.AdaptiveImageGlyph?
161161
#endif
162162
package var clearedFontModifiers: Set<ObjectIdentifier>
163163

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// TypesettingConfiguration.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
8+
// MARK: - TypesettingConfiguration
9+
10+
package struct TypesettingConfiguration: Equatable {
11+
package var language: TypesettingLanguage
12+
13+
package var languageAwareLineHeightRatio: TypesettingLanguageAwareLineHeightRatio
14+
15+
package init(
16+
language: TypesettingLanguage = .automatic,
17+
languageAwareLineHeightRatio: TypesettingLanguageAwareLineHeightRatio = .automatic
18+
) {
19+
self.language = language
20+
self.languageAwareLineHeightRatio = languageAwareLineHeightRatio
21+
}
22+
}
23+
24+
package struct TypesettingConfigurationKey: EnvironmentKey {
25+
package static let defaultValue: TypesettingConfiguration = .init()
26+
}
27+
28+
extension EnvironmentValues {
29+
package var typesettingConfiguration: TypesettingConfiguration {
30+
get { self[TypesettingConfigurationKey.self] }
31+
set { self[TypesettingConfigurationKey.self] = newValue }
32+
}
33+
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
//
2+
// TypesettingLanguage.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
8+
public import Foundation
9+
10+
// MARK: - TypesettingLanguage
11+
12+
/// Defines how typesetting language is determined for text.
13+
///
14+
/// Use a modifier like ``View/typesettingLanguage(_:isEnabled:)``
15+
/// to specify the typesetting language.
16+
@available(OpenSwiftUI_v5_0, *)
17+
public struct TypesettingLanguage: Sendable, Equatable {
18+
package struct Flags: OptionSet {
19+
package var rawValue: UInt8
20+
21+
package init(rawValue: UInt8) {
22+
self.rawValue = rawValue
23+
}
24+
25+
package static let modifyFont: TypesettingLanguage.Flags = .init(rawValue: 1 << 0)
26+
}
27+
28+
package enum Storage: Equatable {
29+
case automatic
30+
case contentAware
31+
case explicit(Locale.Language, TypesettingLanguage.Flags)
32+
}
33+
34+
package var storage: TypesettingLanguage.Storage
35+
36+
/// Automatic language behavior.
37+
///
38+
/// When determining the language to use for typesetting the current UI
39+
/// language and preferred languages will be considered. For example, if
40+
/// the current UI locale is for English and Thai is included in the
41+
/// preferred languages then line heights will be taller to accommodate the
42+
/// taller glyphs used by Thai.
43+
public static let automatic: TypesettingLanguage = .init(storage: .automatic)
44+
45+
/// Use explicit language.
46+
///
47+
/// An explicit language will be used for typesetting. For example, if used
48+
/// with Thai language the line heights will be as tall as needed to
49+
/// accommodate Thai.
50+
///
51+
/// - Parameters:
52+
/// - language: The language to use for typesetting.
53+
/// - Returns: A `TypesettingLanguage`.
54+
public static func explicit(_ language: Locale.Language) -> TypesettingLanguage {
55+
.init(storage: .explicit(language, .modifyFont))
56+
}
57+
}
58+
59+
@_spi(Private)
60+
@available(OpenSwiftUI_v5_0, *)
61+
extension TypesettingLanguage {
62+
@_spi(Private)
63+
@available(OpenSwiftUI_v5_0, *)
64+
public static let contentAware: TypesettingLanguage = .init(storage: .contentAware)
65+
}
66+
67+
// MARK: - View + typesettingLanguage
68+
69+
@available(OpenSwiftUI_v1_0, *)
70+
extension View {
71+
72+
/// Specifies the language for typesetting.
73+
///
74+
/// In some cases `Text` may contain text of a particular language which
75+
/// doesn't match the device UI language. In that case it's useful to
76+
/// specify a language so line height, line breaking and spacing will
77+
/// respect the script used for that language. For example:
78+
///
79+
/// Text(verbatim: "แอปเปิล")
80+
/// .typesettingLanguage(.init(languageCode: .thai))
81+
///
82+
/// Note: this language does not affect text localization.
83+
///
84+
/// - Parameters:
85+
/// - language: The explicit language to use for typesetting.
86+
/// - isEnabled: A Boolean value that indicates whether text language is
87+
/// added
88+
/// - Returns: A view with the typesetting language set to the value you
89+
/// supply.
90+
@available(OpenSwiftUI_v5_0, *)
91+
nonisolated public func typesettingLanguage(
92+
_ language: Locale.Language,
93+
isEnabled: Bool = true
94+
) -> some View {
95+
typesettingLanguage(.explicit(language), isEnabled: isEnabled)
96+
}
97+
98+
/// Specifies the language for typesetting.
99+
///
100+
/// In some cases `Text` may contain text of a particular language which
101+
/// doesn't match the device UI language. In that case it's useful to
102+
/// specify a language so line height, line breaking and spacing will
103+
/// respect the script used for that language. For example:
104+
///
105+
/// Text(verbatim: "แอปเปิล").typesettingLanguage(
106+
/// .explicit(.init(languageCode: .thai)))
107+
///
108+
/// Note: this language does not affect text localized localization.
109+
///
110+
/// - Parameters:
111+
/// - language: The language to use for typesetting.
112+
/// - isEnabled: A Boolean value that indicates whether text language is
113+
/// added
114+
/// - Returns: A view with the typesetting language set to the value you
115+
/// supply.
116+
@available(OpenSwiftUI_v5_0, *)
117+
nonisolated public func typesettingLanguage(
118+
_ language: TypesettingLanguage,
119+
isEnabled: Bool = true
120+
) -> some View {
121+
transformEnvironment(\.typesettingConfiguration) {
122+
if isEnabled {
123+
$0.language = language
124+
}
125+
}
126+
}
127+
}
128+
129+
// MARK: - LanguageTextModifier
130+
131+
class LanguageTextModifier: AnyTextModifier {
132+
let language: TypesettingLanguage
133+
134+
init(language: TypesettingLanguage) {
135+
self.language = language
136+
}
137+
138+
override func modify(style: inout Text.Style, environment: EnvironmentValues) {
139+
// NOTE: This also set languageAwareLineHeightRatio to automatic
140+
style.typesettingConfiguration = .init(language: language)
141+
}
142+
143+
override func isEqual(to other: AnyTextModifier) -> Bool {
144+
guard let other = other as? LanguageTextModifier else {
145+
return false
146+
}
147+
return language == other.language
148+
}
149+
}
150+
151+
// MARK: - Text + typesettingLanguage
152+
153+
@available(OpenSwiftUI_v1_0, *)
154+
extension Text {
155+
156+
/// Specifies the language for typesetting.
157+
///
158+
/// In some cases `Text` may contain text of a particular language which
159+
/// doesn't match the device UI language. In that case it's useful to
160+
/// specify a language so line height, line breaking and spacing will
161+
/// respect the script used for that language. For example:
162+
///
163+
/// Text(verbatim: "แอปเปิล")
164+
/// .typesettingLanguage(.init(languageCode: .thai))
165+
///
166+
/// Note: this language does not affect text localization.
167+
///
168+
/// - Parameters:
169+
/// - language: The explicit language to use for typesetting.
170+
/// - isEnabled: A Boolean value that indicates whether text language is
171+
/// added
172+
/// - Returns: Text with the typesetting language set to the value you
173+
/// supply.
174+
@available(OpenSwiftUI_v5_0, *)
175+
public func typesettingLanguage(
176+
_ language: Locale.Language,
177+
isEnabled: Bool = true
178+
) -> Text {
179+
typesettingLanguage(.explicit(language), isEnabled: isEnabled)
180+
}
181+
182+
/// Specifies the language for typesetting.
183+
///
184+
/// In some cases `Text` may contain text of a particular language which
185+
/// doesn't match the device UI language. In that case it's useful to
186+
/// specify a language so line height, line breaking and spacing will
187+
/// respect the script used for that language. For example:
188+
///
189+
/// Text(verbatim: "แอปเปิล").typesettingLanguage(
190+
/// .explicit(.init(languageCode: .thai)))
191+
///
192+
/// Note: this language does not affect text localized localization.
193+
///
194+
/// - Parameters:
195+
/// - language: The language to use for typesetting.
196+
/// - isEnabled: A Boolean value that indicates whether text language is
197+
/// added
198+
/// - Returns: Text with the typesetting language set to the value you
199+
/// supply.
200+
@available(OpenSwiftUI_v5_0, *)
201+
public func typesettingLanguage(
202+
_ language: TypesettingLanguage,
203+
isEnabled: Bool = true
204+
) -> Text {
205+
guard isEnabled else {
206+
return self
207+
}
208+
let modifier: Text.Modifier = .anyTextModifier(LanguageTextModifier(language: language))
209+
return modified(with: modifier)
210+
}
211+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//
2+
// TypesettingLanguageAwareLineHeightRatio.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
8+
// MARK: - TypesettingLanguageAwareLineHeightRatio
9+
10+
@_spi(Private)
11+
@available(OpenSwiftUI_v5_0, *)
12+
public struct TypesettingLanguageAwareLineHeightRatio: Sendable, Equatable {
13+
enum Storage: Equatable {
14+
case custom(Double)
15+
case automatic
16+
case disable
17+
case legacy
18+
}
19+
20+
var storage: TypesettingLanguageAwareLineHeightRatio.Storage
21+
22+
public static let automatic: TypesettingLanguageAwareLineHeightRatio = .init(storage: .automatic)
23+
24+
public static let disable: TypesettingLanguageAwareLineHeightRatio = .init(storage: .disable)
25+
26+
public static let legacy: TypesettingLanguageAwareLineHeightRatio = .init(storage: .legacy)
27+
28+
public static func custom(_ ratio: Double) -> TypesettingLanguageAwareLineHeightRatio {
29+
.init(storage: .custom(ratio.clamp(min: 0.0, max: 1.0)))
30+
}
31+
}
32+
33+
// MARK: - View + typesettingLanguageAwareLineHeightRatio
34+
35+
@available(OpenSwiftUI_v1_0, *)
36+
extension View {
37+
@_spi(Private)
38+
@available(OpenSwiftUI_v5_0, *)
39+
nonisolated public func typesettingLanguageAwareLineHeightRatio(
40+
_ ratio: TypesettingLanguageAwareLineHeightRatio,
41+
isEnabled: Bool = true
42+
) -> some View {
43+
transformEnvironment(\.typesettingConfiguration) {
44+
if isEnabled {
45+
$0.languageAwareLineHeightRatio = ratio
46+
}
47+
}
48+
}
49+
}
50+
51+
// MARK: - LanguageAwareLineHeightRatioTextModifier
52+
53+
class LanguageAwareLineHeightRatioTextModifier: AnyTextModifier {
54+
let ratio: TypesettingLanguageAwareLineHeightRatio
55+
56+
init(ratio: TypesettingLanguageAwareLineHeightRatio) {
57+
self.ratio = ratio
58+
}
59+
60+
override func modify(style: inout Text.Style, environment: EnvironmentValues) {
61+
style.typesettingConfiguration.languageAwareLineHeightRatio = ratio
62+
}
63+
64+
override func isEqual(to other: AnyTextModifier) -> Bool {
65+
guard let other = other as? LanguageAwareLineHeightRatioTextModifier else {
66+
return false
67+
}
68+
return ratio == other.ratio
69+
}
70+
}
71+
72+
// MARK: - Text + typesettingLanguageAwareLineHeightRatio
73+
74+
@available(OpenSwiftUI_v1_0, *)
75+
extension Text {
76+
@_spi(Private)
77+
@available(OpenSwiftUI_v5_0, *)
78+
public func typesettingLanguageAwareLineHeightRatio(
79+
_ ratio: TypesettingLanguageAwareLineHeightRatio,
80+
isEnabled: Bool = true
81+
) -> Text {
82+
guard isEnabled else {
83+
return self
84+
}
85+
return modified(with: .anyTextModifier(LanguageAwareLineHeightRatioTextModifier(ratio: ratio)))
86+
}
87+
}

0 commit comments

Comments
 (0)