Skip to content

Instantly share code, notes, and snippets.

@fakiho
Created August 7, 2024 14:46
Show Gist options
  • Select an option

  • Save fakiho/fa9a871bfc998052ca2358a940516bd8 to your computer and use it in GitHub Desktop.

Select an option

Save fakiho/fa9a871bfc998052ca2358a940516bd8 to your computer and use it in GitHub Desktop.
import Foundation
import UIKit
struct MarkdownParser {
func parse(markdown: String) -> [AttributedString] {
// Split the markdown string into lines to preserve newlines
let lines = markdown.split(separator: "\n", omittingEmptySubsequences: false)
var parsedLines = [AttributedString]()
for line in lines {
if let attributedLineString = try? AttributedString(
markdown: String(line),
options: .init(allowsExtendedAttributes: true, interpretedSyntax: .full)
) {
parsedLines.append(attributedLineString)
}
}
return parsedLines
}
}
struct MarkdownHeaderFontStyle {
var H1: UIFont
var H2: UIFont
var H3: UIFont
}
struct MarkdownConfiguration {
var defaultFont: UIFont
var boldFont: UIFont
var italicFont: UIFont
var boldItalicFont: UIFont
var headerFontStyle: MarkdownHeaderFontStyle
var textColor: UIColor
var alignment: NSTextAlignment
}
class AttributedStringAssembler {
private let configuration: MarkdownConfiguration
init(configuration: MarkdownConfiguration) {
self.configuration = configuration
}
func assemble(from parsedLines: [AttributedString]) -> NSAttributedString {
let attributedString = NSMutableAttributedString()
for attributedLineString in parsedLines {
var lineString = attributedLineString
// Apply default font and color
lineString.font = configuration.defaultFont
lineString.foregroundColor = configuration.textColor
// Process runs for inline and presentation intents
for run in lineString.runs {
let range = run.range
// Process inline presentation intents
if let inlineIntent = run.attributes[AttributeScopes.FoundationAttributes.InlinePresentationIntentAttribute.self] {
switch inlineIntent {
case .emphasized:
lineString[range].font = configuration.italicFont
case .stronglyEmphasized:
lineString[range].font = configuration.boldFont
case [.stronglyEmphasized, .emphasized]:
lineString[range].font = configuration.boldItalicFont
default:
break
}
}
// Process presentation intents for headers and apply additional formatting
if let presentationIntent = run.attributes[AttributeScopes.FoundationAttributes.PresentationIntentAttribute.self] {
for component in presentationIntent.components {
switch component.kind {
case .header(level: let level):
switch level {
case 1:
lineString[range].font = configuration.headerFontStyle.H1
case 2:
lineString[range].font = configuration.headerFontStyle.H2
case 3:
lineString[range].font = configuration.headerFontStyle.H3
default:
lineString[range].font = configuration.headerFontStyle.H3
}
default:
break
}
}
}
}
// Convert AttributedString to NSMutableAttributedString for further manipulation
let mutableAttributedLineString = NSMutableAttributedString(attributedString: NSAttributedString(lineString))
// Add the processed line to the final attributed string
attributedString.append(mutableAttributedLineString)
// Append a newline character to preserve the original markdown newlines
attributedString.append(NSAttributedString(string: "\n"))
}
// Apply paragraph style to the entire attributed string
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = configuration.alignment
let range = NSRange(location: 0, length: attributedString.length)
attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
return NSAttributedString(attributedString: attributedString)
}
}
class MarkdownProcessor {
private let parser: MarkdownParser
private let assembler: AttributedStringAssembler
init(configuration: MarkdownConfiguration) {
self.parser = MarkdownParser()
self.assembler = AttributedStringAssembler(configuration: configuration)
}
func process(markdown: String) -> NSAttributedString? {
let parsedLines = parser.parse(markdown: markdown)
return assembler.assemble(from: parsedLines)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment