Skip to content

Commit 050ab10

Browse files
committed
Refactor STMarkdown files for improved readability and consistency
- Simplified method signatures by removing unnecessary line breaks and comments across multiple files. - Cleaned up comments to enhance focus on code functionality. - Updated initialization and rendering methods for better clarity and organization.
1 parent 878f0f5 commit 050ab10

16 files changed

Lines changed: 40 additions & 262 deletions

‎Sources/STMarkdown/Attachments/STMarkdownAttachmentRefreshSupport.swift‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,14 @@ enum STMarkdownAttachmentRefreshSupport {
2121
/// - refresh: 主线程闭包;图像就绪时会在主线程被调用,参数为触发刷新的 attachment。
2222
/// - Returns: 注册产生的 observation token 数组,调用方持有以控制生命周期。
2323
@discardableResult
24-
static func bindRefreshHandlers(
25-
in attributedText: NSAttributedString,
26-
refresh: @escaping @MainActor (_ attachment: NSTextAttachment) -> Void
27-
) -> [STMarkdownRefreshObservation] {
24+
static func bindRefreshHandlers(in attributedText: NSAttributedString, refresh: @escaping @MainActor (_ attachment: NSTextAttachment) -> Void) -> [STMarkdownRefreshObservation] {
2825
let range = NSRange(location: 0, length: attributedText.length)
2926
guard range.length > 0 else { return [] }
3027
var tokens: [STMarkdownRefreshObservation] = []
3128
attributedText.enumerateAttribute(.attachment, in: range) { value, _, _ in
3229
guard let attachment = value as? STMarkdownRefreshableAttachment else { return }
3330
let token = attachment.addDisplayObserver { [weak attachment] in
3431
guard let attachment else { return }
35-
// 发射端 (`STMarkdownAsyncImageAttachment.loadImage`) 已通过
36-
// `DispatchQueue.main.async` 保证回调在主线程触发;这里直接同步执行避免
37-
// 多一帧延迟。若调用链未来发生变化导致不在主线程,再降级到一次主线程派发。
3832
if Thread.isMainThread {
3933
MainActor.assumeIsolated {
4034
refresh(attachment)

‎Sources/STMarkdown/Attachments/STMarkdownNumberBadgeAttachment.swift‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ import UIKit
1111
/// 所有引用角标使用统一的直径(可随正文字体放大),避免不同数字(如 1 vs 12)因文本宽度差异导致圆圈大小不一致。
1212
public final class STMarkdownNumberBadgeAttachment: NSTextAttachment {
1313

14-
/// 默认角标直径,对应 17pt 正文下的视觉基线。`renderBadgeImage` 的
15-
/// `diameter` 默认值引用此常量,确保两条渲染路径(TextKit 内联 / CoreGraphics 表格)
16-
/// 视觉一致。实际内联渲染会随正文字体线性放大,下限保持该值。
1714
public static let fixedDiameter: CGFloat = 18
1815

1916
/// - Parameters:
@@ -23,9 +20,6 @@ public final class STMarkdownNumberBadgeAttachment: NSTextAttachment {
2320
/// - backgroundColor: 圆圈背景颜色
2421
public init(numberText: String, font: UIFont, textColor: UIColor, backgroundColor: UIColor) {
2522
super.init(data: nil, ofType: nil)
26-
27-
// 直径随正文字体线性缩放:以 17pt 正文对应 18pt 直径为基准,大字体辅助模式下
28-
// 角标同步放大;低于默认 pointSize 时保持 `fixedDiameter` 下限,保证小字号可读性。
2923
let baseFontPointSize: CGFloat = 17
3024
let scaled = font.pointSize / baseFontPointSize * Self.fixedDiameter
3125
let diameter = max(Self.fixedDiameter, ceil(scaled))
@@ -125,7 +119,6 @@ private extension STMarkdownNumberBadgeAttachment {
125119
}
126120

127121
let size = CGSize(width: diameter, height: diameter)
128-
// 字号按直径线性缩放,保留原 11/10/9 的比例(17/18 ≈ 0.611)。
129122
let ratio: CGFloat = number.count <= 1 ? 11.0 / 18.0 : (number.count == 2 ? 10.0 / 18.0 : 9.0 / 18.0)
130123
let badgeFont = UIFont.st_systemFont(ofSize: diameter * ratio, weight: .semibold)
131124
let textAttributes: [NSAttributedString.Key: Any] = [
@@ -170,7 +163,6 @@ private extension STMarkdownNumberBadgeAttachment {
170163
var blue: CGFloat = 0
171164
var alpha: CGFloat = 0
172165
resolved.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
173-
// 量化到 0...255,避免浮点误差命中不到缓存
174166
let q = { (c: CGFloat) -> Int in Int((c * 255).rounded()) & 0xFF }
175167
return (q(red) << 24) | (q(green) << 16) | (q(blue) << 8) | q(alpha)
176168
}

‎Sources/STMarkdown/Attachments/STMarkdownRefreshableAttachment.swift‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ final class STMarkdownRefreshObserverRegistry {
5454
private let lock = NSLock()
5555
private var entries: [Entry] = []
5656

57-
/// 注册观察者,返回用于移除的 token。
5857
func add(_ observer: @escaping () -> Void) -> STMarkdownRefreshObservation {
5958
let id = UUID()
6059
self.lock.lock()
@@ -65,7 +64,6 @@ final class STMarkdownRefreshObserverRegistry {
6564
}
6665
}
6766

68-
/// 通知所有观察者。持锁时只拷贝数组,实际回调在锁外触发,避免重入死锁。
6967
func notify() {
7068
self.lock.lock()
7169
let snapshot = self.entries

‎Sources/STMarkdown/Rendering/Advanced/STMarkdownAsyncImageRenderer.swift‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ public final class STMarkdownURLSessionImageLoader: STMarkdownImageLoading, @unc
2424
public static let shared = STMarkdownURLSessionImageLoader()
2525
private let cache = NSCache<NSURL, UIImage>()
2626
private let session: URLSession
27-
/// 进行中的请求表:同一 URL 在已有 dataTask 完成前,所有后续 caller 共享该任务结果,
28-
/// 避免同一图像被重复下载(流式 markdown 里复用图很常见)。
2927
private let lock = NSLock()
3028
private var inFlight: [URL: STMarkdownInFlightRequest] = [:]
3129

@@ -73,10 +71,8 @@ extension STMarkdownURLSessionImageLoader: STMarkdownCancellableImageLoading {
7371
return nil
7472
}
7573
let consumerId = UUID()
76-
7774
self.lock.lock()
7875
if let existing = self.inFlight[url] {
79-
// 已有进行中请求,只追加 consumer。
8076
existing.consumers[consumerId] = completion
8177
self.lock.unlock()
8278
return STMarkdownInFlightCancellable(loader: self, url: url, id: consumerId)
@@ -102,7 +98,6 @@ extension STMarkdownURLSessionImageLoader: STMarkdownCancellableImageLoading {
10298
self.deliverInFlight(url: url, image: image)
10399
}
104100
self.lock.lock()
105-
// 中途可能所有 consumer 都取消:此时 `inFlight[url]` 已被清理,不应再启动任务。
106101
guard self.inFlight[url] != nil else {
107102
self.lock.unlock()
108103
return STMarkdownInFlightCancellable(loader: self, url: url, id: consumerId)

‎Sources/STMarkdown/Rendering/Advanced/STMarkdownAttributedStringRenderer.swift‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,6 @@ private extension STMarkdownAttributedStringRenderer {
221221
}
222222

223223
func renderTable(_ table: STMarkdownTableModel) -> NSAttributedString {
224-
// 早期实现把 inline 节点 render 成 NSAttributedString 后只取 `.string`,
225-
// 把粗体/斜体/链接等格式全部丢掉。这里复用 `STMarkdownDefaultTableRenderer`
226-
// 至少能维持等宽对齐与表头分隔,明显优于"裸文本拼接"。
227224
if let rendered = STMarkdownDefaultTableRenderer().renderTable(table, style: self.style) {
228225
return rendered
229226
}

‎Sources/STMarkdown/Rendering/Advanced/STMarkdownCodeBlockAttachmentRenderer.swift‎

Lines changed: 6 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ import UIKit
1010
public struct STMarkdownCodeBlockAttachmentRenderer: STMarkdownCodeBlockRendering {
1111
public init() {}
1212

13-
public func renderCodeBlock(
14-
language: String?,
15-
code: String,
16-
style: STMarkdownStyle
17-
) -> NSAttributedString? {
13+
public func renderCodeBlock(language: String?, code: String, style: STMarkdownStyle) -> NSAttributedString? {
1814
let attachment = NSTextAttachment()
1915
let image = self.renderAttachmentImage(language: language, code: code, style: style)
2016
attachment.image = image
@@ -24,32 +20,24 @@ public struct STMarkdownCodeBlockAttachmentRenderer: STMarkdownCodeBlockRenderin
2420
}
2521

2622
private extension STMarkdownCodeBlockAttachmentRenderer {
27-
func renderAttachmentImage(
28-
language: String?,
29-
code: String,
30-
style: STMarkdownStyle
31-
) -> UIImage {
23+
func renderAttachmentImage(language: String?, code: String, style: STMarkdownStyle) -> UIImage {
3224
let headerFont = UIFont.st_monospacedSystemFont(ofSize: max(style.font.pointSize - 2, 12), weight: .semibold)
3325
let codeFont = UIFont.st_monospacedSystemFont(ofSize: max(style.font.pointSize - 1, 12), weight: .regular)
3426
let insets = style.codeBlockContentInsets
3527
let cornerRadius = style.codeBlockCornerRadius
36-
3728
let blockWidth: CGFloat
3829
if style.renderWidth > 0 {
3930
blockWidth = style.renderWidth
4031
} else {
4132
blockWidth = 280 + insets.left + insets.right
4233
}
4334
let contentWidth = blockWidth - insets.left - insets.right
44-
4535
let backgroundColor = style.codeBlockBackgroundColor ?? UIColor.secondarySystemBackground
4636
let headerColor = style.codeBlockHeaderTextColor ?? style.textColor.withAlphaComponent(0.72)
4737
let textColor = style.codeBlockTextColor ?? style.textColor
48-
4938
let paragraphStyle = NSMutableParagraphStyle()
5039
paragraphStyle.lineBreakMode = .byCharWrapping
5140
paragraphStyle.lineSpacing = max(style.bodyLineSpacing, 2)
52-
5341
let codeAttributes: [NSAttributedString.Key: Any] = [
5442
.font: codeFont,
5543
.foregroundColor: textColor,
@@ -59,14 +47,12 @@ private extension STMarkdownCodeBlockAttachmentRenderer {
5947
.font: headerFont,
6048
.foregroundColor: headerColor,
6149
]
62-
6350
let codeAttributedText = NSAttributedString(string: code, attributes: codeAttributes)
6451
let codeBounds = codeAttributedText.boundingRect(
6552
with: CGSize(width: contentWidth, height: .greatestFiniteMagnitude),
6653
options: [.usesLineFragmentOrigin, .usesFontLeading],
6754
context: nil
6855
)
69-
7056
let headerText: String
7157
if let language, language.isEmpty == false {
7258
headerText = language.uppercased()
@@ -75,67 +61,37 @@ private extension STMarkdownCodeBlockAttachmentRenderer {
7561
}
7662
let headerHeight = headerText.isEmpty ? 0 : ceil(headerFont.lineHeight)
7763
let separatorSpacing: CGFloat = headerText.isEmpty ? 0 : 8
78-
// `boundingRect` 已经按 paragraphStyle.lineSpacing 参与排版,不能再按行数叠加
79-
// lineSpacing,否则会把多行代码块高度显著放大。这里只补一个固定像素级余量,
80-
// 用于覆盖字体 leading / 像素取整导致的末行裁切。
8164
let measured = ceil(codeBounds.height)
8265
let safetyPadding = min(max(style.bodyLineSpacing, 2), 4)
8366
let codeHeight = max(measured + safetyPadding, ceil(codeFont.lineHeight))
8467
let blockHeight = insets.top + headerHeight + separatorSpacing + codeHeight + insets.bottom
85-
8668
let format = UIGraphicsImageRendererFormat.default()
8769
format.scale = style.resolvedDisplayScale
8870
let renderer = UIGraphicsImageRenderer(size: CGSize(width: blockWidth, height: blockHeight), format: format)
89-
9071
return renderer.image { context in
9172
let cgContext = context.cgContext
9273
let rect = CGRect(x: 0, y: 0, width: blockWidth, height: blockHeight)
9374
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
9475
backgroundColor.setFill()
9576
path.fill()
96-
97-
// 绘制边框
9877
if style.codeBlockBorderWidth > 0 {
9978
let borderColor = style.codeBlockBorderColor ?? UIColor.separator
10079
borderColor.setStroke()
10180
path.lineWidth = style.codeBlockBorderWidth
10281
path.stroke()
10382
}
104-
10583
var currentY = insets.top
106-
10784
if headerText.isEmpty == false {
108-
let headerRect = CGRect(
109-
x: insets.left,
110-
y: currentY,
111-
width: contentWidth,
112-
height: headerHeight
113-
)
85+
let headerRect = CGRect(x: insets.left, y: currentY, width: contentWidth, height: headerHeight)
11486
headerText.draw(in: headerRect, withAttributes: headerAttributes)
11587
currentY += headerHeight + 4
116-
117-
let separatorRect = CGRect(
118-
x: insets.left,
119-
y: currentY,
120-
width: contentWidth,
121-
height: 1
122-
)
88+
let separatorRect = CGRect(x: insets.left, y: currentY, width: contentWidth, height: 1)
12389
cgContext.setFillColor((style.horizontalRuleColor ?? UIColor.separator).withAlphaComponent(0.35).cgColor)
12490
cgContext.fill(separatorRect)
12591
currentY += separatorSpacing - 4
12692
}
127-
128-
let codeRect = CGRect(
129-
x: insets.left,
130-
y: currentY,
131-
width: contentWidth,
132-
height: codeHeight
133-
)
134-
codeAttributedText.draw(
135-
with: codeRect,
136-
options: [.usesLineFragmentOrigin, .usesFontLeading],
137-
context: nil
138-
)
93+
let codeRect = CGRect(x: insets.left, y: currentY, width: contentWidth, height: codeHeight)
94+
codeAttributedText.draw(with: codeRect, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
13995
}
14096
}
14197
}

‎Sources/STMarkdown/Rendering/Advanced/STMarkdownCodeHighlighter.swift‎

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import JavaScriptCore
1515
/// - 调用方最好从后台线程调用,在缓存未命中时 `jsQueue.sync` 会阻塞当前线程直到高亮完成。
1616
public final class STMarkdownCodeHighlighter: STMarkdownCodeBlockRendering {
1717

18-
// MARK: - Supported Languages
19-
2018
/// highlight.js 内置且经过验证的语言标识符(均为小写)。
2119
public static let supportedLanguages: Set<String> = [
2220
"bash", "c", "cpp", "csharp", "css", "go", "java",
@@ -25,92 +23,70 @@ public final class STMarkdownCodeHighlighter: STMarkdownCodeBlockRendering {
2523
"swift", "typescript", "xml"
2624
]
2725

28-
// MARK: - JS Infrastructure
29-
3026
private let vm: JSVirtualMachine
3127
private let jsContext: JSContext
3228
private let stylesheet: String
33-
/// JSContext 不是线程安全的,所有 JS 调用都必须通过此串行队列序列化。
29+
private let cacheLock = NSLock()
3430
private let jsQueue = DispatchQueue(label: "com.stmarkdown.code-highlighter.js", qos: .userInitiated)
3531

36-
// MARK: - Cache
37-
3832
private let cache: NSCache<NSString, NSAttributedString> = {
3933
let c = NSCache<NSString, NSAttributedString>()
4034
c.name = "STMarkdownCodeHighlighter"
4135
c.totalCostLimit = 10 * 1024 * 1024
4236
return c
4337
}()
44-
private let cacheLock = NSLock()
45-
46-
// MARK: - Init
4738

4839
public init() {
4940
let bundle = Self.resourceBundle()
50-
let jsCode = bundle.url(forResource: "highlight.min", withExtension: "js")
51-
.flatMap { try? String(contentsOf: $0, encoding: .utf8) } ?? ""
52-
stylesheet = bundle.url(forResource: "default.min", withExtension: "css")
53-
.flatMap { try? String(contentsOf: $0, encoding: .utf8) } ?? ""
54-
55-
vm = JSVirtualMachine()
56-
jsContext = JSContext(virtualMachine: vm)
57-
jsContext.exceptionHandler = { _, exception in
41+
let jsCode = bundle.url(forResource: "highlight.min", withExtension: "js").flatMap { try? String(contentsOf: $0, encoding: .utf8) } ?? ""
42+
self.stylesheet = bundle.url(forResource: "default.min", withExtension: "css").flatMap { try? String(contentsOf: $0, encoding: .utf8) } ?? ""
43+
self.vm = JSVirtualMachine()
44+
self.jsContext = JSContext(virtualMachine: self.vm)
45+
self.jsContext.exceptionHandler = { _, exception in
5846
print("[STMarkdownCodeHighlighter] JS exception: \(exception?.toString() ?? "unknown")")
5947
}
6048
if !jsCode.isEmpty {
61-
jsContext.evaluateScript(jsCode)
49+
self.jsContext.evaluateScript(jsCode)
6250
}
6351
}
64-
65-
// MARK: - STMarkdownCodeBlockRendering
66-
52+
6753
public func renderCodeBlock(language: String?, code: String, style: STMarkdownStyle) -> NSAttributedString? {
6854
let key = "\(language ?? "")|\(code)" as NSString
69-
70-
cacheLock.lock()
71-
let cached = cache.object(forKey: key)
72-
cacheLock.unlock()
55+
self.cacheLock.lock()
56+
let cached = self.cache.object(forKey: key)
57+
self.cacheLock.unlock()
7358
if let cached { return cached }
7459

7560
var result: NSAttributedString?
76-
jsQueue.sync { [self] in
77-
result = highlight(code: code, language: language, style: style)
61+
self.jsQueue.sync { [self] in
62+
result = self.highlight(code: code, language: language, style: style)
7863
}
7964

8065
if let result {
8166
let cost = result.length * MemoryLayout<unichar>.stride
82-
cacheLock.lock()
83-
cache.setObject(result, forKey: key, cost: cost)
84-
cacheLock.unlock()
67+
self.cacheLock.lock()
68+
self.cache.setObject(result, forKey: key, cost: cost)
69+
self.cacheLock.unlock()
8570
}
8671
return result
8772
}
8873

89-
// MARK: - Private
90-
9174
private func highlight(code: String, language: String?, style: STMarkdownStyle) -> NSAttributedString? {
92-
guard let hljs = jsContext.objectForKeyedSubscript("hljs"),
93-
!hljs.isUndefined else { return nil }
94-
75+
guard let hljs = self.jsContext.objectForKeyedSubscript("hljs"), !hljs.isUndefined else { return nil }
9576
let normalizedLang = language?.lowercased() ?? ""
9677
let jsResult: JSValue?
9778
if !normalizedLang.isEmpty, Self.supportedLanguages.contains(normalizedLang) {
9879
jsResult = hljs.invokeMethod("highlight", withArguments: [code, ["language": normalizedLang]])
9980
} else {
10081
jsResult = hljs.invokeMethod("highlightAuto", withArguments: [code])
10182
}
102-
103-
guard let htmlTokens = jsResult?.objectForKeyedSubscript("value")?.toString(),
104-
!htmlTokens.isEmpty else { return nil }
105-
83+
guard let htmlTokens = jsResult?.objectForKeyedSubscript("value")?.toString(), !htmlTokens.isEmpty else { return nil }
10684
let fontSize = max(style.font.pointSize - 1, 12)
107-
// 覆盖 hljs 默认背景色,由外层容器(STMarkdownStyle.codeBlockBackgroundColor)控制背景。
10885
let html = String(
10986
format: "<style>code{font-size:%.2fpx}.hljs{background:transparent}%@</style>"
11087
+ "<pre><code class=\"hljs\">%@</code></pre>",
11188
fontSize, stylesheet, htmlTokens
11289
)
113-
11490
guard let data = html.data(using: .utf8),
11591
let attributed = try? NSMutableAttributedString(
11692
data: data,

‎Sources/STMarkdown/Rendering/Advanced/STMarkdownMermaidRenderer.swift‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ public class STMarkdownMermaidRenderer: NSObject {
2525
public static let shared = STMarkdownMermaidRenderer()
2626

2727
private var webView: WKWebView?
28-
/// 改为 `NSCache` 以便在内存紧张时自动释放,并显式限制条目数。
29-
/// 早期用 `Dictionary<String, UIImage>` 无上限,长会话进程会把几十 MB 图片留到进程结束。
3028
private let imageCacheStore: NSCache<NSString, UIImage> = {
3129
let cache = NSCache<NSString, UIImage>()
3230
cache.countLimit = 64

‎Sources/STMarkdown/Rendering/Default/STMarkdownDefaultHorizontalRuleRenderer.swift‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ public struct STMarkdownDefaultHorizontalRuleRenderer: STMarkdownHorizontalRuleR
1818
paragraphStyle.alignment = .natural
1919
paragraphStyle.lineBreakMode = .byClipping
2020

21-
// 优先 horizontalRuleColor → 退回 dividerColor → 再退回 textColor 28% 透明度。
22-
// dividerColor 是早期遗留 dead config,在分隔线场景里语义最贴近,先于默认色。
2321
let color = style.horizontalRuleColor
2422
?? style.dividerColor
2523
?? style.textColor.withAlphaComponent(0.28)

0 commit comments

Comments
 (0)