import NorgKit import SwiftUI /// Extensions to AttributedString to generate from NorgKit models. extension AttributedString { /// Builds a styled attributed string from parsed Norg inline spans. public init(norg spans: [InlineSpan], baseFont: Font = .body, theme: NorgTheme = .default) { var result = AttributedString() for span in spans { result.append(Self.render(span, baseFont: baseFont, theme: theme)) } self = result } private static func render(_ span: InlineSpan, baseFont: Font, theme: NorgTheme) -> AttributedString { var run = AttributedString(span.text) run.font = font(for: span.styles, baseFont: baseFont) applyDecorations(to: &run, styles: span.styles, theme: theme) if let link = span.link { applyLink(link, to: &run, theme: theme) } return run } private static func font(for styles: InlineStyle, baseFont: Font) -> Font { var font = baseFont if styles.contains(.verbatim) || styles.contains(.math) { font = font.monospaced() } if styles.contains(.bold) { font = font.bold() } if styles.contains(.italic) { font = font.italic() } return font } private static func applyDecorations( to run: inout AttributedString, styles: InlineStyle, theme: NorgTheme ) { if styles.contains(.underline) { run.underlineStyle = .single } if styles.contains(.strikethrough) { run.strikethroughStyle = .single } if styles.contains(.superscript) { run.baselineOffset = theme.superscriptOffset } if styles.contains(.subscript) { run.baselineOffset = theme.subscriptOffset } if styles.contains(.spoiler) { run.foregroundColor = theme.spoilerForeground run.backgroundColor = theme.spoilerBackground } if styles.contains(.verbatim) { run.foregroundColor = theme.verbatimColor } } private static func applyLink( _ link: InlineLink, to run: inout AttributedString, theme: NorgTheme ) { run.foregroundColor = theme.linkColor run.underlineStyle = .single run.link = url(for: link) } private static func url(for link: InlineLink) -> URL? { if link.kind == .link, let target = link.target, let external = externalURL(for: target) { return external } return NorgLinkURL.encode(link) } private static func externalURL(for target: String) -> URL? { let lower = target.lowercased() guard lower.hasPrefix("http://") || lower.hasPrefix("https://") || lower.hasPrefix("file://") else { return nil } return URL(string: target) } }