@@ -94,6 +94,17 @@ public struct Text: View, Renderable, Equatable {
9494 modifiedView = textView
9595 }
9696
97+ // SKIP @bridge
98+ public init ( bridgedRuns runs: [ any View ] , colors: [ any View ] , fontSizes: [ Double ] , fontWeights: [ Int ] , flags: [ Int ] ) {
99+ #if SKIP
100+ textView = _Text ( verbatim: " " )
101+ modifiedView = _ConcatenatedText ( runs: runs, colors: colors, fontSizes: fontSizes, fontWeights: fontWeights, flags: flags)
102+ #else
103+ textView = _Text ( verbatim: " " )
104+ modifiedView = textView
105+ #endif
106+ }
107+
97108 init ( textView: _Text , modifiedView: any View ) {
98109 self . textView = textView
99110 // Don't copy view
@@ -597,6 +608,137 @@ struct _Text: View, Renderable, Equatable {
597608 #endif
598609}
599610
611+ #if SKIP
612+ /// Renders a concatenation of styled text runs (the result of `Text + Text`) as a single Compose
613+ /// `AnnotatedString`, applying each run's captured styling as a `SpanStyle`.
614+ ///
615+ /// SkipFuse constructs this via `Text.init(bridgedRuns:colors:fontSizes:fontWeights:flags:)`: the
616+ /// styling modifiers (`.foregroundColor`, `.font`, `.bold`, …) are captured as *data* on the Swift
617+ /// side — where they are applied — and bridged across as primitive per-run descriptors, because a
618+ /// styled `Text` applies its style as an environment modifier that cannot be read back out at render
619+ /// time to build per-run spans.
620+ struct _ConcatenatedText : View , Renderable {
621+ let runs : [ any View ] // each run's base text (a `Text`), used only for its localized string
622+ let colors : [ any View ] // each run's foreground color (a `Color`); honored only when the hasColor flag is set
623+ let fontSizes : [ Double ] // each run's point size; honored only when the hasFontSize flag is set
624+ let fontWeights : [ Int ] // each run's Compose font weight (100...900); honored only when the hasFontWeight flag is set
625+ let flags : [ Int ] // per-run style bitmask (see the constants below)
626+
627+ // Per-run style bitmask. Kept in sync with SkipSwiftUI's Text run capture.
628+ static let hasColorFlag = 1
629+ static let hasFontSizeFlag = 2
630+ static let hasFontWeightFlag = 4
631+ static let italicFlag = 8
632+ static let monospacedFlag = 16
633+ static let underlineFlag = 32
634+ static let strikethroughFlag = 64
635+
636+ // SKIP INSERT: @OptIn(ExperimentalTextApi::class)
637+ @Composable override func Render( context: ComposeContext ) {
638+ let textEnvironment = EnvironmentValues . shared. _textEnvironment
639+ let textDecoration = textEnvironment. textDecoration
640+ let textAlign = EnvironmentValues . shared. multilineTextAlignment. asTextAlign ( )
641+ let maxLines = max ( 1 , EnvironmentValues . shared. lineLimit ?? Int . MAX_VALUE)
642+ let truncationMode = EnvironmentValues . shared. truncationMode
643+ let hasLineLimit = ( EnvironmentValues . shared. lineLimit != nil )
644+ let reservesSpace = EnvironmentValues . shared. _lineLimitReservesSpace ?? false
645+ let minLines = reservesSpace ? maxLines : 1
646+ let redaction = EnvironmentValues . shared. redactionReasons
647+ let styleInfo = Text . styleInfo ( textEnvironment: textEnvironment, redaction: redaction, context: context)
648+ let animatable = styleInfo. style. asAnimatable ( context: context)
649+
650+ // Resolve each run's localized string and foreground color here, in the @Composable scope:
651+ // `localizedTextString()` and `asComposeColor()` are @Composable and cannot be called from
652+ // inside the (non-composable) `buildAnnotatedString` builder lambda below.
653+ var runStrings : [ String ] = [ ]
654+ var runColors : [ androidx . compose . ui . graphics . Color ? ] = [ ]
655+ for i in 0 ..< runs. count {
656+ if let text = runs [ i] as? Text {
657+ runStrings. append ( text. localizedTextString ( ) )
658+ } else {
659+ runStrings. append ( " " )
660+ }
661+ let flag = i < flags. count ? flags [ i] : 0
662+ if ( flag & _ConcatenatedText. hasColorFlag) != 0 , let color = colors [ i] as? Color {
663+ runColors. append ( color. asComposeColor ( ) )
664+ } else {
665+ runColors. append ( nil )
666+ }
667+ }
668+
669+ let annotatedText = buildAnnotatedString {
670+ appendRuns ( to: self , strings: runStrings, resolvedColors: runColors)
671+ }
672+
673+ var modifier = Modifier . flexibleWidth ( max: Float . flexibleUnknownNonExpanding) . then ( context. modifier)
674+ if EnvironmentValues . shared. _layoutAxis == . horizontal {
675+ modifier = modifier. applyHStackTextBaselineAlignment ( EnvironmentValues . shared. _horizontalStackVerticalAlignmentKey)
676+ }
677+ var options = Material3TextOptions ( annotatedText: annotatedText, modifier: modifier, color: styleInfo. color ?? androidx. compose. ui. graphics. Color. Unspecified, maxLines: maxLines, minLines: minLines, style: animatable. value, textDecoration: textDecoration, textAlign: textAlign, onTextLayout: { _ in } )
678+ if let tracking = textEnvironment. tracking {
679+ options = options. copy ( letterSpacing: tracking. sp)
680+ }
681+ if let lineSpacing = textEnvironment. lineSpacing {
682+ let lineHeightEm = 1.0 + ( lineSpacing / 16.0 )
683+ options = options. copy ( lineHeight: lineHeightEm. em)
684+ }
685+ if hasLineLimit {
686+ options = options. copy (
687+ overflow: {
688+ let singleLine = ( maxLines == 1 ) || options. softWrap == false
689+ switch truncationMode {
690+ case . tail:
691+ return TextOverflow . Ellipsis
692+ case . head:
693+ return singleLine ? TextOverflow . StartEllipsis : TextOverflow . Ellipsis
694+ case . middle:
695+ return singleLine ? TextOverflow . MiddleEllipsis : TextOverflow . Ellipsis
696+ default :
697+ return TextOverflow . Clip
698+ }
699+ } ( )
700+ )
701+ }
702+ if let updateOptions = EnvironmentValues . shared. _material3Text {
703+ options = updateOptions ( options)
704+ }
705+ if let annotatedText = options. annotatedText {
706+ androidx. compose. material3. Text ( text: annotatedText, modifier: options. modifier, color: options. color, autoSize: options. autoSize, fontSize: options. fontSize, fontStyle: options. fontStyle, fontWeight: options. fontWeight, fontFamily: options. fontFamily, letterSpacing: options. letterSpacing, textDecoration: options. textDecoration, textAlign: options. textAlign, lineHeight: options. lineHeight, overflow: options. overflow, softWrap: options. softWrap, maxLines: options. maxLines, minLines: options. minLines, onTextLayout: options. onTextLayout ?? { _ in } , style: options. style)
707+ }
708+ }
709+
710+ /// Append the styled runs to `builder`. Non-composable: the strings and colors are pre-resolved
711+ /// in `Render`. Mirrors the markdown builder's `append(markdown:to:)` helper pattern; `self` at
712+ /// the call site (`buildAnnotatedString { appendRuns(to: self, …) }`) is the builder receiver.
713+ private func appendRuns( to builder: AnnotatedString . Builder , strings: [ String ] , resolvedColors: [ androidx . compose . ui . graphics . Color ? ] ) {
714+ for i in 0 ..< strings. count {
715+ let flag = i < flags. count ? flags [ i] : 0
716+ let underline = ( flag & _ConcatenatedText. underlineFlag) != 0
717+ let strikethrough = ( flag & _ConcatenatedText. strikethroughFlag) != 0
718+ var decoration : TextDecoration ? = nil
719+ if underline, strikethrough {
720+ decoration = TextDecoration . Underline + TextDecoration. LineThrough
721+ } else if underline {
722+ decoration = TextDecoration . Underline
723+ } else if strikethrough {
724+ decoration = TextDecoration . LineThrough
725+ }
726+ let span = SpanStyle (
727+ color: resolvedColors [ i] ?? androidx. compose. ui. graphics. Color. Unspecified,
728+ fontSize: ( flag & _ConcatenatedText. hasFontSizeFlag) != 0 ? fontSizes [ i] . sp : TextUnit . Unspecified,
729+ fontWeight: ( flag & _ConcatenatedText. hasFontWeightFlag) != 0 ? FontWeight ( fontWeights [ i] ) : nil ,
730+ fontStyle: ( flag & _ConcatenatedText. italicFlag) != 0 ? FontStyle . Italic : nil ,
731+ fontFamily: ( flag & _ConcatenatedText. monospacedFlag) != 0 ? FontFamily . Monospace : nil ,
732+ textDecoration: decoration
733+ )
734+ builder. pushStyle ( span)
735+ builder. append ( strings [ i] )
736+ builder. pop ( )
737+ }
738+ }
739+ }
740+ #endif
741+
600742public enum TextAlignment : Int , Hashable , CaseIterable {
601743 case leading = 0 // For bridging
602744 case center = 1 // For bridging
0 commit comments