diff --git a/Sources/MarkdownText/Models/MarkdownRenderConfig.swift b/Sources/MarkdownText/Models/MarkdownRenderConfig.swift index 20620fe..2ee1dac 100644 --- a/Sources/MarkdownText/Models/MarkdownRenderConfig.swift +++ b/Sources/MarkdownText/Models/MarkdownRenderConfig.swift @@ -111,11 +111,11 @@ public struct MarkdownRenderConfig: Hashable, Sendable { /// Foreground color applied to bold-emphasis runs. public let boldTextColor: Color /// Font used for link runs. - public let linkTextFont: MDFont + public var linkTextFont: MDFont { internalLinkTextFont.font } /// Foreground color applied to link runs. public let linkTextColor: Color /// Font used for inline code spans. - public let codeTextFont: MDFont + public var codeTextFont: MDFont { internalCodeTextFont.font } /// Foreground color applied to inline code spans. public let codeTextColor: Color /// Background fill behind inline code spans. @@ -123,12 +123,15 @@ public struct MarkdownRenderConfig: Hashable, Sendable { /// Underline color drawn beneath inline code spans. public let codeUnderlineColor: Color + private let internalLinkTextFont: SendableFont + private let internalCodeTextFont: SendableFont + /// Create an inline text style with the supplied fonts and color palette. public init(boldTextColor: Color, linkTextFont: MDFont, linkTextColor: Color, codeTextFont: MDFont, codeTextColor: Color, codeBackgroundColor: Color, codeUnderlineColor: Color) { self.boldTextColor = boldTextColor - self.linkTextFont = linkTextFont + self.internalLinkTextFont = SendableFont(linkTextFont) self.linkTextColor = linkTextColor - self.codeTextFont = codeTextFont + self.internalCodeTextFont = SendableFont(codeTextFont) self.codeTextColor = codeTextColor self.codeBackgroundColor = codeBackgroundColor self.codeUnderlineColor = codeUnderlineColor @@ -142,12 +145,14 @@ public struct MarkdownRenderConfig: Hashable, Sendable { /// Encoder/decoder used to embed citation payloads into the markdown. public let coder: CitationCoder /// Font applied to the rendered citation chip. - public let font: MDFont + public var font: MDFont { internalFont.font } /// Foreground color of the citation chip text. public let textColor: Color /// Background fill of the citation chip. public let backgroundColor: Color + private let internalFont: SendableFont + /// Create a citation configuration. /// - Parameters: /// - isEnabled: See `isEnabled`. Defaults to `true`. @@ -164,7 +169,7 @@ public struct MarkdownRenderConfig: Hashable, Sendable { ) { self.isEnabled = isEnabled self.coder = coder - self.font = font + self.internalFont = SendableFont(font) self.textColor = textColor self.backgroundColor = backgroundColor } diff --git a/Sources/MarkdownText/Style/TextFonts.swift b/Sources/MarkdownText/Style/TextFonts.swift index e8ef08e..b832b2e 100644 --- a/Sources/MarkdownText/Style/TextFonts.swift +++ b/Sources/MarkdownText/Style/TextFonts.swift @@ -10,25 +10,30 @@ import SwiftUI /// to style a run of text. public struct TextFonts: Hashable, Sendable { /// Regular variant. Always required. - public let normal: MDFont + public var normal: MDFont { internalNormal.font } /// Italic variant, or `nil` to fall back to `normal` for emphasis. - public let italic: MDFont? + public var italic: MDFont? { internalItalic?.font } /// Bold variant, or `nil` to fall back to `normal` for strong runs. - public let bold: MDFont? + public var bold: MDFont? { internalBold?.font } /// Bold-italic variant, or `nil` to fall back to `bold` then `italic`. - public let boldItalic: MDFont? + public var boldItalic: MDFont? { internalBoldItalic?.font } /// Optional kerning override applied via `NSAttributedString.Key.kern`. public let preferredLetterSpacing: CGFloat? /// Optional preferred line height in points. When greater than the font's /// natural line height, the renderer adds the difference as line spacing. public let preferredLineHeight: CGFloat? + private let internalNormal: SendableFont + private let internalItalic: SendableFont? + private let internalBold: SendableFont? + private let internalBoldItalic: SendableFont? + /// Create a font set with explicit variants and optional spacing overrides. public init(normal: MDFont, italic: MDFont?, bold: MDFont?, boldItalic: MDFont?, preferredLetterSpacing: CGFloat?, preferredLineHeight: CGFloat?) { - self.normal = normal - self.italic = italic - self.bold = bold - self.boldItalic = boldItalic + self.internalNormal = SendableFont(normal) + self.internalItalic = italic.map(SendableFont.init) + self.internalBold = bold.map(SendableFont.init) + self.internalBoldItalic = boldItalic.map(SendableFont.init) self.preferredLetterSpacing = preferredLetterSpacing self.preferredLineHeight = preferredLineHeight } diff --git a/Sources/MarkdownText/Utilities/SendableFont.swift b/Sources/MarkdownText/Utilities/SendableFont.swift new file mode 100644 index 0000000..fef9bd3 --- /dev/null +++ b/Sources/MarkdownText/Utilities/SendableFont.swift @@ -0,0 +1,26 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// + +#if canImport(UIKit) +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif + +/// A `Sendable` wrapper around `MDFont` (`UIFont`/`NSFont`). +/// +/// `UIFont`/`NSFont` are effectively immutable and safe to share across +/// concurrency domains, but the SDK does not mark them as `Sendable`. Storing a +/// font in this value type keeps the unchecked assertion in a single, auditable +/// place so font-holding configuration types can be plainly `Sendable` without +/// leaking a retroactive `Sendable` conformance onto the platform font types. +struct SendableFont: Hashable, @unchecked Sendable { + /// The wrapped platform font. + let font: MDFont + + init(_ font: MDFont) { + self.font = font + } +}