Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -714,35 +714,3 @@ extension Dictionary where Key == NSAttributedString.Key, Value == Any {
#endif
}
}

// FIXME
@available(OpenSwiftUI_v1_0, *)
extension Text {
package struct Superscript: Hashable, Sendable {
package static let `default`: Text.Superscript = .init(0)

package var value: Int

package init(_ value: Int) {
self.value = value
}
}

@_spi(Private)
@available(OpenSwiftUI_v5_0, *)
public struct CustomAttributes: @unchecked Sendable, Hashable {
public init() {}

package var attributes: [TextAttributeModifierBase] = []
}
}

package class TextAttributeModifierBase: AnyTextModifier, Hashable {
package static func == (lhs: TextAttributeModifierBase, rhs: TextAttributeModifierBase) -> Bool {
lhs === rhs
}

package func hash(into hasher: inout Hasher) {
_openSwiftUIUnimplementedWarning()
}
}
93 changes: 90 additions & 3 deletions Sources/OpenSwiftUICore/View/Text/Text/Text+Renderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Empty
// Status: WIP
// ID: 7F70C8A76EE0356881289646072938C0 (SwiftUICore)

#if canImport(CoreText)
Expand All @@ -13,7 +13,7 @@ import OpenAttributeGraphShims
public import OpenCoreGraphicsShims
import UIFoundation_Private

// TODO
// TODO: TextRenderer

/// A proxy for a text view that custom text renderers use.
@available(OpenSwiftUI_v6_0, *)
Expand All @@ -32,9 +32,96 @@ public struct TextProxy {
}
}

// TODO: View + textRenderer

// MARK: - TextAttribute

/// A value that you can attach to text views and that text renderers can query.
@available(OpenSwiftUI_v5_0, *)
public protocol TextAttribute {}
public protocol TextAttribute: Hashable {}

// MARK: - Text + customAttribute

@available(OpenSwiftUI_v5_0, *)
extension Text {
/// Adds a custom attribute to the text view.
///
/// Only one attribute of each type may be attached to each text
/// view, with inner attributes taking precedence.
///
/// - Parameter value: The attribute to attach.
///
/// - Returns: A version of the text view with `value` attached.
public func customAttribute<T>(_ value: T) -> Text where T: TextAttribute {
modified(with: .anyTextModifier(TextAttributeModifier(value: value)))
}
}

// MARK: - TextAttributeModifierBase

package class TextAttributeModifierBase: AnyTextModifier, Hashable {
package func hash(into hasher: inout Hasher) {
_openSwiftUIBaseClassAbstractMethod()
}

package static func == (lhs: TextAttributeModifierBase, rhs: TextAttributeModifierBase) -> Bool {
lhs.isEqual(to: rhs)
}
}

// MARK: - TextAttributeModifier

private final class TextAttributeModifier<Value>: TextAttributeModifierBase where Value: TextAttribute {
let attribute: Value

init(value: Value) {
attribute = value
}

override func modify(style: inout Text.Style, environment: EnvironmentValues) {
style.customAttributes.append(self)
}

override func isEqual(to other: AnyTextModifier) -> Bool {
guard let other = other as? TextAttributeModifier<Value> else {
return false
}
return attribute == other.attribute
}

override package func hash(into hasher: inout Hasher) {
attribute.hash(into: &hasher)
}
}

// MARK: Text + CustomAttributes

@_spi(Private)
@available(OpenSwiftUI_v5_0, *)
extension Text {
public struct CustomAttributes: @unchecked Sendable, Hashable {
var attributes: [TextAttributeModifierBase] = []

public init() {
_openSwiftUIEmptyStub()
}

public mutating func add<T>(_ value: T) where T: TextAttribute {
attributes.append(TextAttributeModifier(value: value))
}

public subscript<T>(_ key: T.Type) -> T? where T: TextAttribute {

@augmentcode augmentcode Bot Jun 7, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment on customAttribute says “Only one attribute of each type … with inner attributes taking precedence”, but CustomAttributes.subscript returns the first match in attributes order and add/modify only append; if multiple values of the same TextAttribute type are present this will likely return the outer/earlier one instead of the inner/later one.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

for attribute in attributes {
if let modifier = attribute as? TextAttributeModifier<T> {
return modifier.attribute
}
}
return nil
}
}
}

// TODO: _TextRendererViewModifier

// MARK: - TextRendererBoxBase

Expand Down
12 changes: 12 additions & 0 deletions Sources/OpenSwiftUICore/View/Text/Text/Text+Superscript.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// Text+Superscript.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete

extension Text {
package struct Superscript: Sendable, Hashable {
package static let `default`: Text.Superscript = .init()
}
}
Loading