SwiftyMarkdown.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. //
  2. // SwiftyMarkdown.swift
  3. // SwiftyMarkdown
  4. //
  5. // Created by Simon Fairbairn on 05/03/2016.
  6. // Copyright © 2016 Voyage Travel Apps. All rights reserved.
  7. //
  8. import os.log
  9. #if os(macOS)
  10. import AppKit
  11. #else
  12. import UIKit
  13. #endif
  14. extension OSLog {
  15. private static var subsystem = "SwiftyMarkdown"
  16. static let swiftyMarkdownPerformance = OSLog(subsystem: subsystem, category: "Swifty Markdown Performance")
  17. }
  18. public enum CharacterStyle : CharacterStyling {
  19. case none
  20. case bold
  21. case italic
  22. case code
  23. case link
  24. case image
  25. case referencedLink
  26. case referencedImage
  27. case strikethrough
  28. public func isEqualTo(_ other: CharacterStyling) -> Bool {
  29. guard let other = other as? CharacterStyle else {
  30. return false
  31. }
  32. return other == self
  33. }
  34. }
  35. enum MarkdownLineStyle : LineStyling {
  36. var shouldTokeniseLine: Bool {
  37. switch self {
  38. case .codeblock:
  39. return false
  40. default:
  41. return true
  42. }
  43. }
  44. case yaml
  45. case h1
  46. case h2
  47. case h3
  48. case h4
  49. case h5
  50. case h6
  51. case previousH1
  52. case previousH2
  53. case body
  54. case blockquote
  55. case codeblock
  56. case unorderedList
  57. case unorderedListIndentFirstOrder
  58. case unorderedListIndentSecondOrder
  59. case orderedList
  60. case orderedListIndentFirstOrder
  61. case orderedListIndentSecondOrder
  62. case referencedLink
  63. func styleIfFoundStyleAffectsPreviousLine() -> LineStyling? {
  64. switch self {
  65. case .previousH1:
  66. return MarkdownLineStyle.h1
  67. case .previousH2:
  68. return MarkdownLineStyle.h2
  69. default :
  70. return nil
  71. }
  72. }
  73. }
  74. @objc public enum FontStyle : Int {
  75. case normal
  76. case bold
  77. case italic
  78. case boldItalic
  79. }
  80. #if os(macOS)
  81. @objc public protocol FontProperties {
  82. var fontName : String? { get set }
  83. var color : NSColor { get set }
  84. var fontSize : CGFloat { get set }
  85. var fontStyle : FontStyle { get set }
  86. }
  87. #else
  88. @objc public protocol FontProperties {
  89. var fontName : String? { get set }
  90. var color : UIColor { get set }
  91. var fontSize : CGFloat { get set }
  92. var fontStyle : FontStyle { get set }
  93. }
  94. #endif
  95. @objc public protocol LineProperties {
  96. var alignment : NSTextAlignment { get set }
  97. }
  98. /**
  99. A class defining the styles that can be applied to the parsed Markdown. The `fontName` property is optional, and if it's not set then the `fontName` property of the Body style will be applied.
  100. If that is not set, then the system default will be used.
  101. */
  102. @objc open class BasicStyles : NSObject, FontProperties {
  103. public var fontName : String?
  104. #if os(macOS)
  105. public var color = NSColor.black
  106. #else
  107. public var color = UIColor.black
  108. #endif
  109. public var fontSize : CGFloat = 0.0
  110. public var fontStyle : FontStyle = .normal
  111. }
  112. @objc open class LineStyles : NSObject, FontProperties, LineProperties {
  113. public var fontName : String?
  114. #if os(macOS)
  115. public var color = NSColor.black
  116. #else
  117. public var color = UIColor.black
  118. #endif
  119. public var fontSize : CGFloat = 0.0
  120. public var fontStyle : FontStyle = .normal
  121. public var alignment: NSTextAlignment = .left
  122. }
  123. /// A class that takes a [Markdown](https://daringfireball.net/projects/markdown/) string or file and returns an NSAttributedString with the applied styles. Supports Dynamic Type.
  124. @objc open class SwiftyMarkdown: NSObject {
  125. static public var frontMatterRules = [
  126. FrontMatterRule(openTag: "---", closeTag: "---", keyValueSeparator: ":")
  127. ]
  128. static public var lineRules = [
  129. LineRule(token: "=", type: MarkdownLineStyle.previousH1, removeFrom: .entireLine, changeAppliesTo: .previous),
  130. LineRule(token: "-", type: MarkdownLineStyle.previousH2, removeFrom: .entireLine, changeAppliesTo: .previous),
  131. LineRule(token: "\t\t- ", type: MarkdownLineStyle.unorderedListIndentSecondOrder, removeFrom: .leading, shouldTrim: false),
  132. LineRule(token: "\t- ", type: MarkdownLineStyle.unorderedListIndentFirstOrder, removeFrom: .leading, shouldTrim: false),
  133. LineRule(token: "- ",type : MarkdownLineStyle.unorderedList, removeFrom: .leading),
  134. LineRule(token: "\t\t* ", type: MarkdownLineStyle.unorderedListIndentSecondOrder, removeFrom: .leading, shouldTrim: false),
  135. LineRule(token: "\t* ", type: MarkdownLineStyle.unorderedListIndentFirstOrder, removeFrom: .leading, shouldTrim: false),
  136. LineRule(token: "\t\t1. ", type: MarkdownLineStyle.orderedListIndentSecondOrder, removeFrom: .leading, shouldTrim: false),
  137. LineRule(token: "\t1. ", type: MarkdownLineStyle.orderedListIndentFirstOrder, removeFrom: .leading, shouldTrim: false),
  138. LineRule(token: "1. ",type : MarkdownLineStyle.orderedList, removeFrom: .leading),
  139. LineRule(token: "* ",type : MarkdownLineStyle.unorderedList, removeFrom: .leading),
  140. LineRule(token: " ", type: MarkdownLineStyle.codeblock, removeFrom: .leading, shouldTrim: false),
  141. LineRule(token: "\t", type: MarkdownLineStyle.codeblock, removeFrom: .leading, shouldTrim: false),
  142. LineRule(token: ">",type : MarkdownLineStyle.blockquote, removeFrom: .leading),
  143. LineRule(token: "###### ",type : MarkdownLineStyle.h6, removeFrom: .both),
  144. LineRule(token: "##### ",type : MarkdownLineStyle.h5, removeFrom: .both),
  145. LineRule(token: "#### ",type : MarkdownLineStyle.h4, removeFrom: .both),
  146. LineRule(token: "### ",type : MarkdownLineStyle.h3, removeFrom: .both),
  147. LineRule(token: "## ",type : MarkdownLineStyle.h2, removeFrom: .both),
  148. LineRule(token: "# ",type : MarkdownLineStyle.h1, removeFrom: .both)
  149. ]
  150. static public var characterRules = [
  151. CharacterRule(primaryTag: CharacterRuleTag(tag: "![", type: .open), otherTags: [
  152. CharacterRuleTag(tag: "]", type: .close),
  153. CharacterRuleTag(tag: "[", type: .metadataOpen),
  154. CharacterRuleTag(tag: "]", type: .metadataClose)
  155. ], styles: [1 : CharacterStyle.image], metadataLookup: true, definesBoundary: true),
  156. CharacterRule(primaryTag: CharacterRuleTag(tag: "![", type: .open), otherTags: [
  157. CharacterRuleTag(tag: "]", type: .close),
  158. CharacterRuleTag(tag: "(", type: .metadataOpen),
  159. CharacterRuleTag(tag: ")", type: .metadataClose)
  160. ], styles: [1 : CharacterStyle.image], metadataLookup: false, definesBoundary: true),
  161. CharacterRule(primaryTag: CharacterRuleTag(tag: "[", type: .open), otherTags: [
  162. CharacterRuleTag(tag: "]", type: .close),
  163. CharacterRuleTag(tag: "[", type: .metadataOpen),
  164. CharacterRuleTag(tag: "]", type: .metadataClose)
  165. ], styles: [1 : CharacterStyle.link], metadataLookup: true, definesBoundary: true),
  166. CharacterRule(primaryTag: CharacterRuleTag(tag: "[", type: .open), otherTags: [
  167. CharacterRuleTag(tag: "]", type: .close),
  168. CharacterRuleTag(tag: "(", type: .metadataOpen),
  169. CharacterRuleTag(tag: ")", type: .metadataClose)
  170. ], styles: [1 : CharacterStyle.link], metadataLookup: false, definesBoundary: true),
  171. CharacterRule(primaryTag: CharacterRuleTag(tag: "`", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.code], shouldCancelRemainingTags: true, balancedTags: true),
  172. CharacterRule(primaryTag:CharacterRuleTag(tag: "~", type: .repeating), otherTags : [], styles: [2 : CharacterStyle.strikethrough], minTags:2 , maxTags:2),
  173. CharacterRule(primaryTag: CharacterRuleTag(tag: "*", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2),
  174. CharacterRule(primaryTag: CharacterRuleTag(tag: "_", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2)
  175. ]
  176. let lineProcessor = SwiftyLineProcessor(rules: SwiftyMarkdown.lineRules, defaultRule: MarkdownLineStyle.body, frontMatterRules: SwiftyMarkdown.frontMatterRules)
  177. let tokeniser = SwiftyTokeniser(with: SwiftyMarkdown.characterRules)
  178. /// The styles to apply to any H1 headers found in the Markdown
  179. open var h1 = LineStyles()
  180. /// The styles to apply to any H2 headers found in the Markdown
  181. open var h2 = LineStyles()
  182. /// The styles to apply to any H3 headers found in the Markdown
  183. open var h3 = LineStyles()
  184. /// The styles to apply to any H4 headers found in the Markdown
  185. open var h4 = LineStyles()
  186. /// The styles to apply to any H5 headers found in the Markdown
  187. open var h5 = LineStyles()
  188. /// The styles to apply to any H6 headers found in the Markdown
  189. open var h6 = LineStyles()
  190. /// The default body styles. These are the base styles and will be used for e.g. headers if no other styles override them.
  191. open var body = LineStyles()
  192. /// The styles to apply to any blockquotes found in the Markdown
  193. open var blockquotes = LineStyles()
  194. /// The styles to apply to any links found in the Markdown
  195. open var link = BasicStyles()
  196. /// The styles to apply to any bold text found in the Markdown
  197. open var bold = BasicStyles()
  198. /// The styles to apply to any italic text found in the Markdown
  199. open var italic = BasicStyles()
  200. /// The styles to apply to any code blocks or inline code text found in the Markdown
  201. open var code = BasicStyles()
  202. open var strikethrough = BasicStyles()
  203. public var bullet : String = "・"
  204. public var underlineLinks : Bool = false
  205. public var frontMatterAttributes : [String : String] {
  206. get {
  207. return self.lineProcessor.frontMatterAttributes
  208. }
  209. }
  210. var currentType : MarkdownLineStyle = .body
  211. var string : String
  212. var orderedListCount = 0
  213. var orderedListIndentFirstOrderCount = 0
  214. var orderedListIndentSecondOrderCount = 0
  215. var previouslyFoundTokens : [Token] = []
  216. var applyAttachments = true
  217. let perfomanceLog = PerformanceLog(with: "SwiftyMarkdownPerformanceLogging", identifier: "Swifty Markdown", log: .swiftyMarkdownPerformance)
  218. /**
  219. - parameter string: A string containing [Markdown](https://daringfireball.net/projects/markdown/) syntax to be converted to an NSAttributedString
  220. - returns: An initialized SwiftyMarkdown object
  221. */
  222. public init(string : String ) {
  223. self.string = string
  224. super.init()
  225. self.setup()
  226. }
  227. /**
  228. A failable initializer that takes a URL and attempts to read it as a UTF-8 string
  229. - parameter url: The location of the file to read
  230. - returns: An initialized SwiftyMarkdown object, or nil if the string couldn't be read
  231. */
  232. public init?(url : URL ) {
  233. do {
  234. self.string = try NSString(contentsOf: url, encoding: String.Encoding.utf8.rawValue) as String
  235. } catch {
  236. self.string = ""
  237. return nil
  238. }
  239. super.init()
  240. self.setup()
  241. }
  242. func setup() {
  243. #if os(macOS)
  244. self.setFontColorForAllStyles(with: .labelColor)
  245. #elseif !os(watchOS)
  246. if #available(iOS 13.0, tvOS 13.0, *) {
  247. self.setFontColorForAllStyles(with: .label)
  248. }
  249. #endif
  250. }
  251. /**
  252. Set font size for all styles
  253. - parameter size: size of font
  254. */
  255. open func setFontSizeForAllStyles(with size: CGFloat) {
  256. h1.fontSize = size
  257. h2.fontSize = size
  258. h3.fontSize = size
  259. h4.fontSize = size
  260. h5.fontSize = size
  261. h6.fontSize = size
  262. body.fontSize = size
  263. italic.fontSize = size
  264. bold.fontSize = size
  265. code.fontSize = size
  266. link.fontSize = size
  267. link.fontSize = size
  268. strikethrough.fontSize = size
  269. }
  270. #if os(macOS)
  271. open func setFontColorForAllStyles(with color: NSColor) {
  272. h1.color = color
  273. h2.color = color
  274. h3.color = color
  275. h4.color = color
  276. h5.color = color
  277. h6.color = color
  278. body.color = color
  279. italic.color = color
  280. bold.color = color
  281. code.color = color
  282. link.color = color
  283. blockquotes.color = color
  284. strikethrough.color = color
  285. }
  286. #else
  287. open func setFontColorForAllStyles(with color: UIColor) {
  288. h1.color = color
  289. h2.color = color
  290. h3.color = color
  291. h4.color = color
  292. h5.color = color
  293. h6.color = color
  294. body.color = color
  295. italic.color = color
  296. bold.color = color
  297. code.color = color
  298. link.color = color
  299. blockquotes.color = color
  300. strikethrough.color = color
  301. }
  302. #endif
  303. open func setFontNameForAllStyles(with name: String) {
  304. h1.fontName = name
  305. h2.fontName = name
  306. h3.fontName = name
  307. h4.fontName = name
  308. h5.fontName = name
  309. h6.fontName = name
  310. body.fontName = name
  311. italic.fontName = name
  312. bold.fontName = name
  313. code.fontName = name
  314. link.fontName = name
  315. blockquotes.fontName = name
  316. strikethrough.fontName = name
  317. }
  318. /**
  319. Generates an NSAttributedString from the string or URL passed at initialisation. Custom fonts or styles are applied to the appropriate elements when this method is called.
  320. - returns: An NSAttributedString with the styles applied
  321. */
  322. open func attributedString(from markdownString : String? = nil) -> NSAttributedString {
  323. self.previouslyFoundTokens.removeAll()
  324. self.perfomanceLog.start()
  325. if let existentMarkdownString = markdownString {
  326. self.string = existentMarkdownString
  327. }
  328. let attributedString = NSMutableAttributedString(string: "")
  329. self.lineProcessor.processEmptyStrings = MarkdownLineStyle.body
  330. let foundAttributes : [SwiftyLine] = lineProcessor.process(self.string)
  331. let references : [SwiftyLine] = foundAttributes.filter({ $0.line.starts(with: "[") && $0.line.contains("]:") })
  332. let referencesRemoved : [SwiftyLine] = foundAttributes.filter({ !($0.line.starts(with: "[") && $0.line.contains("]:") ) })
  333. var keyValuePairs : [String : String] = [:]
  334. for line in references {
  335. let strings = line.line.components(separatedBy: "]:")
  336. guard strings.count >= 2 else {
  337. continue
  338. }
  339. var key : String = strings[0]
  340. if !key.isEmpty {
  341. let newstart = key.index(key.startIndex, offsetBy: 1)
  342. let range : Range<String.Index> = newstart..<key.endIndex
  343. key = String(key[range]).trimmingCharacters(in: .whitespacesAndNewlines)
  344. }
  345. keyValuePairs[key] = strings[1].trimmingCharacters(in: .whitespacesAndNewlines)
  346. }
  347. self.perfomanceLog.tag(with: "(line processing complete)")
  348. self.tokeniser.metadataLookup = keyValuePairs
  349. for (idx, line) in referencesRemoved.enumerated() {
  350. if idx > 0 {
  351. attributedString.append(NSAttributedString(string: "\n"))
  352. }
  353. let finalTokens = self.tokeniser.process(line.line)
  354. self.previouslyFoundTokens.append(contentsOf: finalTokens)
  355. self.perfomanceLog.tag(with: "(tokenising complete for line \(idx)")
  356. attributedString.append(attributedStringFor(tokens: finalTokens, in: line))
  357. }
  358. self.perfomanceLog.end()
  359. return attributedString
  360. }
  361. }
  362. extension SwiftyMarkdown {
  363. func attributedStringFor( tokens : [Token], in line : SwiftyLine ) -> NSAttributedString {
  364. var finalTokens = tokens
  365. let finalAttributedString = NSMutableAttributedString()
  366. var attributes : [NSAttributedString.Key : AnyObject] = [:]
  367. guard let markdownLineStyle = line.lineStyle as? MarkdownLineStyle else {
  368. preconditionFailure("The passed line style is not a valid Markdown Line Style")
  369. }
  370. var listItem = self.bullet
  371. switch markdownLineStyle {
  372. case .orderedList:
  373. self.orderedListCount += 1
  374. self.orderedListIndentFirstOrderCount = 0
  375. self.orderedListIndentSecondOrderCount = 0
  376. listItem = "\(self.orderedListCount)."
  377. case .orderedListIndentFirstOrder, .unorderedListIndentFirstOrder:
  378. self.orderedListIndentFirstOrderCount += 1
  379. self.orderedListIndentSecondOrderCount = 0
  380. if markdownLineStyle == .orderedListIndentFirstOrder {
  381. listItem = "\(self.orderedListIndentFirstOrderCount)."
  382. }
  383. case .orderedListIndentSecondOrder, .unorderedListIndentSecondOrder:
  384. self.orderedListIndentSecondOrderCount += 1
  385. if markdownLineStyle == .orderedListIndentSecondOrder {
  386. listItem = "\(self.orderedListIndentSecondOrderCount)."
  387. }
  388. default:
  389. self.orderedListCount = 0
  390. self.orderedListIndentFirstOrderCount = 0
  391. self.orderedListIndentSecondOrderCount = 0
  392. }
  393. let lineProperties : LineProperties
  394. switch markdownLineStyle {
  395. case .h1:
  396. lineProperties = self.h1
  397. case .h2:
  398. lineProperties = self.h2
  399. case .h3:
  400. lineProperties = self.h3
  401. case .h4:
  402. lineProperties = self.h4
  403. case .h5:
  404. lineProperties = self.h5
  405. case .h6:
  406. lineProperties = self.h6
  407. case .codeblock:
  408. lineProperties = body
  409. let paragraphStyle = NSMutableParagraphStyle()
  410. paragraphStyle.firstLineHeadIndent = 20.0
  411. attributes[.paragraphStyle] = paragraphStyle
  412. case .blockquote:
  413. lineProperties = self.blockquotes
  414. let paragraphStyle = NSMutableParagraphStyle()
  415. paragraphStyle.firstLineHeadIndent = 20.0
  416. paragraphStyle.headIndent = 20.0
  417. attributes[.paragraphStyle] = paragraphStyle
  418. case .unorderedList, .unorderedListIndentFirstOrder, .unorderedListIndentSecondOrder, .orderedList, .orderedListIndentFirstOrder, .orderedListIndentSecondOrder:
  419. let interval : CGFloat = 30
  420. var addition = interval
  421. var indent = ""
  422. switch line.lineStyle as! MarkdownLineStyle {
  423. case .unorderedListIndentFirstOrder, .orderedListIndentFirstOrder:
  424. addition = interval * 2
  425. indent = "\t"
  426. case .unorderedListIndentSecondOrder, .orderedListIndentSecondOrder:
  427. addition = interval * 3
  428. indent = "\t\t"
  429. default:
  430. break
  431. }
  432. lineProperties = body
  433. let paragraphStyle = NSMutableParagraphStyle()
  434. paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: interval, options: [:]), NSTextTab(textAlignment: .left, location: interval, options: [:])]
  435. paragraphStyle.defaultTabInterval = interval
  436. paragraphStyle.headIndent = addition
  437. attributes[.paragraphStyle] = paragraphStyle
  438. finalTokens.insert(Token(type: .string, inputString: "\(indent)\(listItem)\t"), at: 0)
  439. case .yaml:
  440. lineProperties = body
  441. case .previousH1:
  442. lineProperties = body
  443. case .previousH2:
  444. lineProperties = body
  445. case .body:
  446. lineProperties = body
  447. case .referencedLink:
  448. lineProperties = body
  449. }
  450. if lineProperties.alignment != .left {
  451. let paragraphStyle = NSMutableParagraphStyle()
  452. paragraphStyle.alignment = lineProperties.alignment
  453. attributes[.paragraphStyle] = paragraphStyle
  454. }
  455. for token in finalTokens {
  456. attributes[.font] = self.font(for: line)
  457. attributes[.link] = nil
  458. attributes[.strikethroughStyle] = nil
  459. attributes[.foregroundColor] = self.color(for: line)
  460. guard let styles = token.characterStyles as? [CharacterStyle] else {
  461. continue
  462. }
  463. if styles.contains(.italic) {
  464. attributes[.font] = self.font(for: line, characterOverride: .italic)
  465. attributes[.foregroundColor] = self.italic.color
  466. }
  467. if styles.contains(.bold) {
  468. attributes[.font] = self.font(for: line, characterOverride: .bold)
  469. attributes[.foregroundColor] = self.bold.color
  470. }
  471. if let linkIdx = styles.firstIndex(of: .link), linkIdx < token.metadataStrings.count {
  472. attributes[.foregroundColor] = self.link.color
  473. attributes[.font] = self.font(for: line, characterOverride: .link)
  474. attributes[.link] = token.metadataStrings[linkIdx] as AnyObject
  475. if underlineLinks {
  476. attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue as AnyObject
  477. }
  478. }
  479. if styles.contains(.strikethrough) {
  480. attributes[.font] = self.font(for: line, characterOverride: .strikethrough)
  481. attributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue as AnyObject
  482. attributes[.foregroundColor] = self.strikethrough.color
  483. }
  484. #if !os(watchOS)
  485. if let imgIdx = styles.firstIndex(of: .image), imgIdx < token.metadataStrings.count {
  486. if !self.applyAttachments {
  487. continue
  488. }
  489. #if !os(macOS)
  490. let image1Attachment = NSTextAttachment()
  491. image1Attachment.image = UIImage(named: token.metadataStrings[imgIdx])
  492. let str = NSAttributedString(attachment: image1Attachment)
  493. finalAttributedString.append(str)
  494. #elseif !os(watchOS)
  495. let image1Attachment = NSTextAttachment()
  496. image1Attachment.image = NSImage(named: token.metadataStrings[imgIdx])
  497. let str = NSAttributedString(attachment: image1Attachment)
  498. finalAttributedString.append(str)
  499. #endif
  500. continue
  501. }
  502. #endif
  503. if styles.contains(.code) {
  504. attributes[.foregroundColor] = self.code.color
  505. attributes[.font] = self.font(for: line, characterOverride: .code)
  506. } else {
  507. // Switch back to previous font
  508. }
  509. let str = NSAttributedString(string: token.outputString, attributes: attributes)
  510. finalAttributedString.append(str)
  511. }
  512. return finalAttributedString
  513. }
  514. }