diff --git a/Sources/OpenSwiftUICore/View/Text/Text/Text+Renderer.swift b/Sources/OpenSwiftUICore/View/Text/Text/Text+Renderer.swift index 60db75e2b..99e5ff63f 100644 --- a/Sources/OpenSwiftUICore/View/Text/Text/Text+Renderer.swift +++ b/Sources/OpenSwiftUICore/View/Text/Text/Text+Renderer.swift @@ -6,8 +6,12 @@ // Status: Empty // ID: 7F70C8A76EE0356881289646072938C0 (SwiftUICore) +#if canImport(CoreText) +import CoreText +#endif import OpenAttributeGraphShims public import OpenCoreGraphicsShims +import UIFoundation_Private // TODO @@ -207,6 +211,23 @@ extension Text { /// A single line in a text layout: a collection of runs of /// placed glyphs. public struct Line: RandomAccessCollection, Equatable { + private enum Line { + #if canImport(CoreText) + case ctLine(CTLine, AnyTextLayoutRenderer?) + #endif + case nsLine(NSTextLineFragment) + } + + private var _line: Text.Layout.Line.Line + + @inline(__always) + var attributedString: NSAttributedString? { + guard case let .nsLine(nSTextLineFragment) = _line else { + return nil + } + return nSTextLineFragment.attributedString + } + /// The origin of the line. public var origin: CGPoint @@ -248,6 +269,17 @@ extension Text { public var paragraphLayoutDirection: LayoutDirection { _openSwiftUIUnimplementedFailure() } + + public static func == (lhs: Text.Layout.Line, rhs: Text.Layout.Line) -> Bool { + guard lhs.origin == rhs.origin else { return false } + return switch (lhs._line, rhs._line) { + #if canImport(CoreText) + case let (.ctLine(lhsLine, _), .ctLine(rhsLine, _)): lhsLine === rhsLine + #endif + case let (.nsLine(lhsLine), .nsLine(rhsLine)): lhsLine === rhsLine + default: false + } + } } // MARK: - Text.Layout.Run [WIP] @@ -367,6 +399,8 @@ extension Text.Layout.RunSlice: Sendable {} // TODO: AnyTextLayoutRenderer +class AnyTextLayoutRenderer {} + // MARK: - Text.LayoutKey @available(OpenSwiftUI_v5_0, *) diff --git a/Sources/OpenSwiftUICore/View/Text/Text/Text+Suffix.swift b/Sources/OpenSwiftUICore/View/Text/Text/Text+Suffix.swift index 9d92dc030..a5d122fa8 100644 --- a/Sources/OpenSwiftUICore/View/Text/Text/Text+Suffix.swift +++ b/Sources/OpenSwiftUICore/View/Text/Text/Text+Suffix.swift @@ -6,6 +6,7 @@ // Status: Blocked by ResolvedStyledText // ID: 3A0E49913D84545BECD562BC22E4DF1C (SwiftUICore) +import Foundation import OpenAttributeGraphShims // MARK: - Text.Suffix @@ -74,6 +75,10 @@ package enum ResolvedTextSuffix: Equatable { case truncated(Text.Layout.Line, [ShapeStyle.Pack.Style]) case alwaysVisible(Text.Layout.Line, [ShapeStyle.Pack.Style]) + var accessibilityLine: NSAttributedString? { + line?.attributedString + } + package var line: Text.Layout.Line? { switch self { case .none: nil diff --git a/Sources/OpenSwiftUICore/View/Text/Text/TextNonDarwinShims.swift b/Sources/OpenSwiftUICore/View/Text/Text/TextNonDarwinShims.swift index 17db21fcd..c67a6a82d 100644 --- a/Sources/OpenSwiftUICore/View/Text/Text/TextNonDarwinShims.swift +++ b/Sources/OpenSwiftUICore/View/Text/Text/TextNonDarwinShims.swift @@ -13,4 +13,14 @@ extension NSMutableAttributedString { false } } + +package class NSTextLineFragment: NSObject { + package init(attributedString: NSAttributedString, range: NSRange) { + self.attributedString = attributedString + self.range = range + } + + private(set) package var attributedString: NSAttributedString + private var range: NSRange +} #endif diff --git a/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextLineFragment.h b/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextLineFragment.h new file mode 100644 index 000000000..d1e67bb5e --- /dev/null +++ b/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextLineFragment.h @@ -0,0 +1,66 @@ +// +// NSTextLineFragment.h +// OpenSwiftUI_SPI + +#pragma once + +#import "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_DARWIN + +// Modified based on iOS 18.5 SDK + +// +// NSTextLineFragment.h +// Text Kit +// +// Copyright (c) 2017-2024, Apple Inc. All rights reserved. +// + +#import +#import + +NS_HEADER_AUDIT_BEGIN(nullability, sendability) + +#pragma mark NSTextLineFragment +// NSTextLineFragment represents a single textual layout and rendering unit inside NSTextLayoutFragment. +API_AVAILABLE(macos(12.0), ios(15.0), tvos(15.0), visionos(1.0)) API_UNAVAILABLE(watchos) +@interface NSTextLineFragment : NSObject +#pragma mark Initialization +- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString range:(NSRange)range NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithString:(NSString *)string attributes:(NSDictionary *)attributes range:(NSRange)range; + +- (instancetype)init NS_UNAVAILABLE; + +#pragma mark Basic properties +// The source attributed string +@property (strong, readonly) NSAttributedString *attributedString; + +// The string range for the source attributed string corresponding to this line fragment +@property (readonly) NSRange characterRange; + +#pragma mark Typographic bounds +// The typographic bounds specifying the dimensions of the line fragment for laying out line fragments to each other. The origin value is offset from the beginning of the line fragment group belonging to the parent layout fragment. +@property (readonly) CGRect typographicBounds; + +#pragma mark Rendering +// Rendering origin for the left most glyph in the line fragment coordinate system +@property (readonly) CGPoint glyphOrigin; + +// Renders the line fragment contents at the rendering origin. The origin can be specified as (CGRectGetMinX(typographicBounds), CGRectGetMinY(typographicBounds)) relative to the parent layout fragment coordinate system. +- (void)drawAtPoint:(CGPoint)point inContext:(CGContextRef)context; + +#pragma mark Character and point mappings +// The location of the character at the specified index. It is on the upstream edge of the glyph. It is in the coordinate system relative to the line fragment origin +- (CGPoint)locationForCharacterAtIndex:(NSInteger)index; + +// The character index for point inside the line fragment coordinate system. The fraction of distance is from the upstream edge +- (NSInteger)characterIndexForPoint:(CGPoint)point; +- (CGFloat)fractionOfDistanceThroughGlyphForPoint:(CGPoint)point; + +@end +NS_HEADER_AUDIT_END(nullability, sendability) + +#endif