Files
ONE/backend/internal/render/pangu.go
T
Sakurasan 23771f0661 文章排版四件套(参考 xLog):盘古之白 + 表格圆角斑马纹 + 代码块封顶与复制按钮 + 标题悬停锚点
- PanguHTML:CJK 与拉丁/数字间补空格,标签原样保留、code/pre 内文本不动;详情/时间线短文/RSS 描述三个出口生效(带单测)
- 表格改圆角容器 + tbody 斑马纹 + 纵向大留白(classic/vivid 双主题)
- pre max-height 30.5em 封顶内部滚动;enhanceProse 注入悬停复制按钮(App 全局委托),按下有 2px 物理感
- 带 id 的 h2-h4 悬停浮现 # 锚点,点击跳转
- 中文字体栈此前已配(PingFang SC 等),无需改动
2026-09-29 00:53:50 +08:00

93 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 盘古之白:在 CJK 与拉丁字母/数字之间补一个空格,中文混排的阅读观感
// 差别很大(「把MVP搬进仓库」→「把 MVP 搬进仓库」)。
// 在 HTML 层做:标签原样保留,<code>/<pre>/<script>/<style> 内的文本
// 一律不动(代码语义容不得我们加空格),其余文本段做补空格。
package render
import (
"regexp"
"strings"
"unicode"
)
var tagRe = regexp.MustCompile(`<[^>]*>`)
// code 上下文里的文本不处理
var codeOpenRe = regexp.MustCompile(`(?i)<(code|pre|script|style|kbd|samp)\b[^>]*>`)
var codeCloseRe = regexp.MustCompile(`(?i)</(code|pre|script|style|kbd|samp)>`)
// CJK 统一表意区 + 扩展A/兼容 + 日文假名已在 2E80-9FFF 覆盖,
// 再补全角标点区(FF00-FFEF)与 CJK 标点(3000-303F)
var cjkRe = regexp.MustCompile(`[\x{2E80}-\x{9FFF}\x{3000}-\x{303F}\x{F900}-\x{FAFF}\x{FF00}-\x{FFEF}]`)
var latinTailRe = regexp.MustCompile(`([\x{2E80}-\x{9FFF}\x{3000}-\x{303F}\x{F900}-\x{FAFF}\x{FF00}-\x{FFEF}])([A-Za-z0-9_$@])`)
var latinHeadRe = regexp.MustCompile(`([A-Za-z0-9+%,.;:!?%)\]])([\x{2E80}-\x{9FFF}\x{3000}-\x{303F}\x{F900}-\x{FAFF}\x{FF00}-\x{FFEF}])`)
func isCJK(r rune) bool {
return cjkRe.MatchString(string(r))
}
// panguText 对纯文本做补空格。已经隔着空格的不动(正则天然要求相邻)。
func panguText(s string) string {
if !hasCJKAndLatin(s) {
return s
}
prev := ""
for prev != s {
prev = s
s = latinTailRe.ReplaceAllString(s, "$1 $2")
s = latinHeadRe.ReplaceAllString(s, "$1 $2")
}
return s
}
func hasCJKAndLatin(s string) bool {
var cjk, latin bool
for _, r := range s {
if isCJK(r) {
cjk = true
} else if unicode.IsLetter(r) || unicode.IsNumber(r) {
latin = true
}
if cjk && latin {
return true
}
}
return false
}
// PanguHTML 给渲染出的正文 HTML 做盘古之白。流式扫描:标签原样输出,
// 普通文本段补空格,代码上下文里的文本跳过。
func PanguHTML(html string) string {
if !strings.Contains(html, "<") {
return html
}
var b strings.Builder
inCode := false
for {
loc := tagRe.FindStringIndex(html)
if loc == nil {
rest := html
if !inCode {
rest = panguText(rest)
}
b.WriteString(rest)
return b.String()
}
head, tag := html[:loc[0]], html[loc[0]:loc[1]]
if !inCode {
head = panguText(head)
}
b.WriteString(head)
b.WriteString(tag)
if !inCode {
if m := codeOpenRe.FindStringSubmatch(tag); m != nil {
// 自闭合或行内 code 直接开着也按进/出配对处理(</code> 总会出现)
inCode = true
}
} else if codeCloseRe.MatchString(tag) {
inCode = false
}
html = html[loc[1]:]
}
}