diff --git a/Package.resolved b/Package.resolved index 1874936d..b6eaa4f2 100644 --- a/Package.resolved +++ b/Package.resolved @@ -9,6 +9,33 @@ "version" : "6.0.0" } }, + { + "identity" : "sdwebimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImage.git", + "state" : { + "revision" : "cac9a55a3ae92478a2c95042dcc8d9695d2129ca", + "version" : "5.21.0" + } + }, + { + "identity" : "sdwebimageswiftui", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImageSwiftUI", + "state" : { + "revision" : "53573d6dd017e354c0e7d8f1c86b77ef1383c996", + "version" : "2.2.7" + } + }, + { + "identity" : "splash", + "kind" : "remoteSourceControl", + "location" : "https://github.com/JohnSundell/Splash", + "state" : { + "revision" : "7f4df436eb78fe64fe2c32c58006e9949fa28ad8", + "version" : "0.16.0" + } + }, { "identity" : "swift-cmark", "kind" : "remoteSourceControl", diff --git a/Sources/MarkdownUI/Extensibility/DefaultEmbeddedImageProvider.swift b/Sources/MarkdownUI/Extensibility/DefaultEmbeddedImageProvider.swift new file mode 100644 index 00000000..b1f617a4 --- /dev/null +++ b/Sources/MarkdownUI/Extensibility/DefaultEmbeddedImageProvider.swift @@ -0,0 +1,35 @@ +import SwiftUI + +public struct DefaultEmbeddedImageProvider: EmbeddedImageProvider { + + public func makeImage(data: Data) -> Image { + Image(data: data) ?? Image(systemName: "") + } + +} + +private extension Image { + + init?(data: Data) { +#if os(iOS) + guard let image = UIImage(data: data) else { + return nil + } + + self.init(uiImage: image) +#elseif os(macOS) + guard let image = NSImage(data: data) else { + return nil + } + + self.init(nsImage: image) +#endif + } + +} + +extension EmbeddedImageProvider where Self == DefaultEmbeddedImageProvider { + public static var `default`: Self { + .init() + } +} diff --git a/Sources/MarkdownUI/Extensibility/EmbeddedImageProvider.swift b/Sources/MarkdownUI/Extensibility/EmbeddedImageProvider.swift new file mode 100644 index 00000000..79f10e37 --- /dev/null +++ b/Sources/MarkdownUI/Extensibility/EmbeddedImageProvider.swift @@ -0,0 +1,6 @@ +import SwiftUI + +public protocol EmbeddedImageProvider { + + @ViewBuilder func makeImage(data: Data) -> Image +} diff --git a/Sources/MarkdownUI/Views/Environment/Environment+EmbeddedImageProvider.swift b/Sources/MarkdownUI/Views/Environment/Environment+EmbeddedImageProvider.swift new file mode 100644 index 00000000..c82b14d7 --- /dev/null +++ b/Sources/MarkdownUI/Views/Environment/Environment+EmbeddedImageProvider.swift @@ -0,0 +1,21 @@ +import SwiftUI + +extension View { + /// Sets the embedded image provider to use for Markdown images that have the image content embedded inside the document + /// - Parameter provider: the image provider to use for embedded images + /// - Returns: A view that has the specified provider set as an environment variable + public func embeddedImageProvider(_ provider: EmbeddedImageProvider) -> some View { + environment(\.embeddedImageProvider, provider) + } +} + +extension EnvironmentValues { + var embeddedImageProvider: EmbeddedImageProvider { + get { self[EmbeddedImageProviderKey.self] } + set { self[EmbeddedImageProviderKey.self] = newValue } + } +} + +private struct EmbeddedImageProviderKey: EnvironmentKey { + static let defaultValue: EmbeddedImageProvider = .default +} diff --git a/Sources/MarkdownUI/Views/Inlines/ImageView.swift b/Sources/MarkdownUI/Views/Inlines/ImageView.swift index 90f97e0a..bf50962c 100644 --- a/Sources/MarkdownUI/Views/Inlines/ImageView.swift +++ b/Sources/MarkdownUI/Views/Inlines/ImageView.swift @@ -3,6 +3,7 @@ import SwiftUI struct ImageView: View { @Environment(\.theme.image) private var image @Environment(\.imageProvider) private var imageProvider + @Environment(\.embeddedImageProvider) private var embeddedImageProvider: EmbeddedImageProvider @Environment(\.imageBaseURL) private var baseURL private let data: RawImageData @@ -20,8 +21,31 @@ struct ImageView: View { ) } + @ViewBuilder private var label: some View { - self.imageProvider.makeImage(url: self.url) + if let imageData = Data(base64Encoded: base64String) { + self.embeddedImageForData(imageData) + } else if let url = self.url { + self.remoteImageForURL(url) + } + } + + private var base64String: String { + self.data.source.replacingOccurrences(of: "data:image/[a-z]+;base64,", with: "", options: .regularExpression) + } + + @ViewBuilder + private func remoteImageForURL(_ url: URL) -> some View { + self.imageProvider.makeImage(url: url) + .link(destination: self.data.destination) + .accessibilityLabel(self.data.alt) + } + + @ViewBuilder + private func embeddedImageForData(_ imageData: Data) -> some View { + self.embeddedImageProvider.makeImage(data: imageData) + .resizable() + .scaledToFit() .link(destination: self.data.destination) .accessibilityLabel(self.data.alt) }