|
| 1 | +// =========================== |
| 2 | +// 格式化工具库 |
| 3 | +// =========================== |
| 4 | + |
| 5 | + |
| 6 | +// 粗体处理逻辑 |
| 7 | +#let setup-bold(body) = { |
| 8 | + show text.where(weight: "bold").or(strong): it => { |
| 9 | + show regex("[\p{script=Han}!-・〇-〰—]+"): cn => { |
| 10 | + set text(weight: "regular") |
| 11 | + context { |
| 12 | + set text(stroke: 0.02857em + text.fill) |
| 13 | + cn |
| 14 | + } |
| 15 | + } |
| 16 | + it |
| 17 | + } |
| 18 | + body |
| 19 | +} |
| 20 | + |
| 21 | +// 下划线处理逻辑 |
| 22 | +#let setup-underline(body) = { |
| 23 | + show underline: it => context { |
| 24 | + let line_width = measure(it.body).width |
| 25 | + box[ |
| 26 | + #it.body |
| 27 | + #place( |
| 28 | + bottom + left, |
| 29 | + dy: 0.15em, |
| 30 | + line(length: line_width, stroke: 0.05em) |
| 31 | + ) |
| 32 | + ] |
| 33 | + } |
| 34 | + body |
| 35 | +} |
| 36 | + |
| 37 | +// 自定义下划线函数(#u): |
| 38 | +// width 参数为必填命名参数,指定固定宽度,内容自动居中 |
| 39 | +// 使用: #u(width: )[] |
| 40 | +#let u(width: none, offset: 0.2em, body) = { |
| 41 | + assert(width != none, message: "参数 width 是必填的") |
| 42 | + context { |
| 43 | + let line_width = width |
| 44 | + |
| 45 | + box(width: line_width)[ |
| 46 | + #align(center, body) |
| 47 | + #place( |
| 48 | + bottom + left, |
| 49 | + dy: offset, |
| 50 | + line(length: line_width, stroke: 0.05em) |
| 51 | + ) |
| 52 | + ] |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +// 中文检测 |
| 57 | +#let has-chinese(content) = { |
| 58 | + let text-content = if type(content) == str { |
| 59 | + content |
| 60 | + } else if content.has("text") { |
| 61 | + content.text |
| 62 | + } else if content.has("body") { |
| 63 | + return has-chinese(content.body) |
| 64 | + } else if content.has("children") { |
| 65 | + return content.children.any(has-chinese) |
| 66 | + } else { |
| 67 | + return false |
| 68 | + } |
| 69 | + if type(text-content) == str { |
| 70 | + text-content.codepoints().any(c => { |
| 71 | + let code = str.to-unicode(c) |
| 72 | + code >= 0x4E00 and code <= 0x9FFF |
| 73 | + }) |
| 74 | + } else { |
| 75 | + false |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +// 斜体处理逻辑: |
| 80 | +// - 英文:使用原生 italic |
| 81 | +// - 空格:保持原样 |
| 82 | +// - 其他(中文、数字、符号等):使用 skew(-18deg)倾斜 |
| 83 | +#let setup-emph(body) = { |
| 84 | + show emph: it => { |
| 85 | + let process-content(content) = { |
| 86 | + if type(content) == str { |
| 87 | + let pattern = regex("( +)|([a-zA-Z]+)|([^ a-zA-Z]+)") |
| 88 | + let matches = content.matches(pattern) |
| 89 | + |
| 90 | + matches.map(m => { |
| 91 | + let str = m.text |
| 92 | + let first-char = str.at(0) |
| 93 | + if first-char >= "A" and first-char <= "Z" or first-char >= "a" and first-char <= "z" { |
| 94 | + box(inset: 0pt, outset: 0pt, text(style: "italic")[#str]) |
| 95 | + } else if first-char == " " { |
| 96 | + box(inset: 0pt, outset: 0pt, str) |
| 97 | + } else { |
| 98 | + box(skew(ax: -18deg)[#str]) |
| 99 | + } |
| 100 | + }).join() |
| 101 | + } else if content.has("text") { |
| 102 | + process-content(content.text) |
| 103 | + } else if content.has("body") { |
| 104 | + process-content(content.body) |
| 105 | + } else if content.has("children") { |
| 106 | + content.children.map(process-content).join() |
| 107 | + } else { |
| 108 | + let repr-str = repr(content) |
| 109 | + if repr-str == "[ ]" { |
| 110 | + box(inset: 0pt, outset: 0pt, content) |
| 111 | + } else { |
| 112 | + content |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + if has-chinese(it.body) { |
| 118 | + process-content(it.body) |
| 119 | + } else { |
| 120 | + it |
| 121 | + } |
| 122 | + } |
| 123 | + body |
| 124 | +} |
| 125 | + |
| 126 | +// 统一格式化函数 |
| 127 | +#let zh-format(body) = { |
| 128 | + show: setup-bold |
| 129 | + show: setup-underline |
| 130 | + show: setup-emph |
| 131 | + body |
| 132 | +} |
0 commit comments