fix(dsl): render articles faithfully

Split lines on LF, CRLF and lone CR like the indexer, strip leading whitespace from body lines, break the paragraph after a closing margin tag, and apply named colors from [c] tags.
This commit is contained in:
OneWay committed 2026-09-18 01:50:00 +08:00
1 parent bddcb99177
commit f51684367a
2 files changed
+76 -28

No files matched your search

@@ -180,14 +180,19 @@ internal fun parseDslArticleContent(rawContent: String): String {
while (i < len) { while (i < len) {
val lineStart = i val lineStart = i
while (i < len && rawContent[i] != '\n') i++ while (i < len && rawContent[i] != '\n' && rawContent[i] != '\r') i++
val lineEnd = i val lineEnd = i
if (i < len && rawContent[i] == '\r') {
i++
if (i < len && rawContent[i] == '\n') i++ if (i < len && rawContent[i] == '\n') i++
} else if (i < len && rawContent[i] == '\n') {
i++
}
if (lineStart == lineEnd && i >= len) break if (lineStart == lineEnd && i >= len) break
val line = rawContent.substring(lineStart, lineEnd) val line = rawContent.substring(lineStart, lineEnd)
val trimmed = line.trimEnd() val trimmed = line.trim()
if (trimmed.isEmpty()) continue if (trimmed.isEmpty()) continue
if (skippingHeadwords) { if (skippingHeadwords) {
@@ -24,6 +24,40 @@ object DslAnnotatedParser {
"ref", "ex", "e", "trn", "com", "lang", "sup", "'" "ref", "ex", "e", "trn", "com", "lang", "sup", "'"
) )
private val DSL_NAMED_COLORS = mapOf(
"black" to Color(0xFF000000),
"silver" to Color(0xFFC0C0C0),
"gray" to Color(0xFF808080),
"grey" to Color(0xFF808080),
"lightgray" to Color(0xFFD3D3D3),
"lightgrey" to Color(0xFFD3D3D3),
"darkgray" to Color(0xFFA9A9A9),
"darkgrey" to Color(0xFFA9A9A9),
"white" to Color(0xFFFFFFFF),
"maroon" to Color(0xFF800000),
"red" to Color(0xFFFF0000),
"darkred" to Color(0xFF8B0000),
"crimson" to Color(0xFFDC143C),
"brown" to Color(0xFFA52A2A),
"orange" to Color(0xFFFFA500),
"pink" to Color(0xFFFFC0CB),
"purple" to Color(0xFF800080),
"violet" to Color(0xFFEE82EE),
"fuchsia" to Color(0xFFFF00FF),
"magenta" to Color(0xFFFF00FF),
"olive" to Color(0xFF808000),
"yellow" to Color(0xFFFFFF00),
"green" to Color(0xFF008000),
"darkgreen" to Color(0xFF006400),
"lime" to Color(0xFF00FF00),
"teal" to Color(0xFF008080),
"aqua" to Color(0xFF00FFFF),
"cyan" to Color(0xFF00FFFF),
"navy" to Color(0xFF000080),
"blue" to Color(0xFF0000FF),
"darkblue" to Color(0xFF00008B),
)
data class ColorScheme( data class ColorScheme(
val secondaryText: Color, val secondaryText: Color,
val labelText: Color, val labelText: Color,
@@ -54,9 +88,9 @@ object DslAnnotatedParser {
val stack = ArrayDeque<SpanStyle>() val stack = ArrayDeque<SpanStyle>()
val refStack = ArrayDeque<Pair<Int, StringBuilder>>() val refStack = ArrayDeque<Pair<Int, StringBuilder>>()
val lazyRefs = mutableListOf<LazyRef>() val lazyRefs = mutableListOf<LazyRef>()
var lastChar: Char? = null
var accentNext = false var accentNext = false
var currentIndent = 0 var currentIndent = 0
var mClosedPendingBreak = false
val exampleStyle = SpanStyle(color = colorScheme.secondaryText) val exampleStyle = SpanStyle(color = colorScheme.secondaryText)
val labelStyle = SpanStyle(color = colorScheme.labelText) val labelStyle = SpanStyle(color = colorScheme.labelText)
val linkStyle = SpanStyle(color = colorScheme.linkText, textDecoration = TextDecoration.Underline) val linkStyle = SpanStyle(color = colorScheme.linkText, textDecoration = TextDecoration.Underline)
@@ -84,6 +118,20 @@ object DslAnnotatedParser {
stack.forEach { builder.pushStyle(it) } stack.forEach { builder.pushStyle(it) }
lazyRefs.clear() lazyRefs.clear()
} }
mClosedPendingBreak = false
}
fun appendChar(char: Char) {
if (mClosedPendingBreak) {
mClosedPendingBreak = false
if (refStack.isEmpty() && !char.isWhitespace()) {
flushBlock()
}
}
builder.append(char)
if (refStack.isNotEmpty()) {
refStack.last().second.append(char)
}
} }
fun marginIndent(tagName: String): Int { fun marginIndent(tagName: String): Int {
@@ -99,20 +147,11 @@ object DslAnnotatedParser {
when (val char = dsl[i]) { when (val char = dsl[i]) {
'\\' -> { '\\' -> {
if (i + 1 < length) { if (i + 1 < length) {
val next = dsl[i + 1] appendChar(dsl[i + 1])
builder.append(next)
if (refStack.isNotEmpty()) {
refStack.last().second.append(next)
}
lastChar = next
i += 2 i += 2
continue continue
} }
builder.append(char) appendChar(char)
if (refStack.isNotEmpty()) {
refStack.last().second.append(char)
}
lastChar = char
i++ i++
} }
'[' -> { '[' -> {
@@ -148,6 +187,9 @@ object DslAnnotatedParser {
} else if (tagName == "'") { } else if (tagName == "'") {
accentNext = false accentNext = false
} }
if (tagName.startsWith("m")) {
mClosedPendingBreak = true
}
pop() pop()
} else { } else {
when (tagName) { when (tagName) {
@@ -161,17 +203,15 @@ object DslAnnotatedParser {
} else { } else {
builder.append("\n") builder.append("\n")
} }
lastChar = '\n'
} }
"p" -> { "p" -> {
push(labelStyle) push(labelStyle)
} }
"b" -> push(SpanStyle(fontWeight = FontWeight.Bold)) "b" -> push(SpanStyle(fontWeight = FontWeight.Bold))
"m", "m0", "m1", "m2", "m3", "m4", "m5" -> { "m", "m0", "m1", "m2", "m3", "m4", "m5" -> {
if (lastChar != '\n' && lastChar != null) { if (builder.length > 0) {
if (refStack.isEmpty()) flushBlock() else builder.append("\n") if (refStack.isEmpty()) flushBlock() else builder.append("\n")
} }
lastChar = '\n'
currentIndent = marginIndent(tagName) currentIndent = marginIndent(tagName)
push(exampleStyle) push(exampleStyle)
} }
@@ -181,7 +221,18 @@ object DslAnnotatedParser {
refStack.addLast(builder.length to StringBuilder()) refStack.addLast(builder.length to StringBuilder())
} }
"ex", "e" -> push(exampleStyle) "ex", "e" -> push(exampleStyle)
"c", "t", "trn", "lang", "sup" -> push(SpanStyle()) "c" -> {
val colorName = if (spaceIdx >= 0) {
tagWithParams.substring(spaceIdx + 1).trim().lowercase()
} else {
null
}
push(when {
colorName == null -> labelStyle
else -> DSL_NAMED_COLORS[colorName]?.let { SpanStyle(color = it) } ?: SpanStyle()
})
}
"t", "trn", "lang", "sup" -> push(SpanStyle())
"com" -> push(SpanStyle(fontStyle = FontStyle.Italic, color = colorScheme.labelText)) "com" -> push(SpanStyle(fontStyle = FontStyle.Italic, color = colorScheme.labelText))
} }
} }
@@ -189,18 +240,11 @@ object DslAnnotatedParser {
continue continue
} }
} }
builder.append(char) appendChar(char)
if (refStack.isNotEmpty()) {
refStack.last().second.append(char)
}
lastChar = char
i++ i++
} }
else -> { else -> {
builder.append(char) appendChar(char)
if (refStack.isNotEmpty()) {
refStack.last().second.append(char)
}
if (accentNext) { if (accentNext) {
builder.append('\u0301') builder.append('\u0301')
@@ -210,7 +254,6 @@ object DslAnnotatedParser {
accentNext = false accentNext = false
} }
lastChar = char
i++ i++
} }
} }