feat(latex): 新增 LaTeX 排版系统,从 Markdown 生成 ctexbook

- 新增 officefile/latex/ 目录,包含 main.tex、preamble.tex 及 14 个章节 + 10 个附录 tex 文件
- md→tex 转换流程:pandoc 转换 + _postprocess.py 后处理(去编号、修破折号、清标签)
- 修复 5 个 md 源文件的标题层级(H1→H2 降级,统一层级结构)
- 配置 VS Code LaTeX Workshop(xelatex + ctexbook 编译链)
- 新增 VS Code workspace 和 .gitignore(排除 LaTeX 编译产物)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 12:21:15 +08:00
commit d4665a362e
30 changed files with 12168 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
import re, os
outdir = os.path.join(os.path.dirname(__file__), 'chapters')
counts = {'labels': 0, 'dashes': 0, 'rules': 0, 'chapnum': 0, 'secnum': 0, 'emdashtext': 0}
for fname in sorted(os.listdir(outdir)):
if not fname.endswith('.tex') or fname == 'ch13-conclusion.tex':
continue
fpath = os.path.join(outdir, fname)
with open(fpath, 'r', encoding='utf-8') as f:
content = f.read()
# 1. Remove Unicode labels like \label{ux7b2c...}
label_pattern = re.compile(r'\\label\{[^}]*ux[0-9a-f]+[^}]*\}')
matches = label_pattern.findall(content)
counts['labels'] += len(matches)
content = label_pattern.sub('', content)
# 2. Fix dashes + strip chapter number prefix (first line)
lines = content.split('\n')
if lines and lines[0].startswith('\\chapter{'):
old_line = lines[0]
new_line = old_line.replace('------', '——')
new_line = re.sub(r'\\chapter\{\d+章[:\s]+', r'\\chapter{', new_line)
if new_line != old_line:
if '------' in old_line:
counts['dashes'] += 1
if re.search(r'\d+章', old_line):
counts['chapnum'] += 1
lines[0] = new_line
content = '\n'.join(lines)
# 3. Remove decorative rules
rule = '\\begin{center}\\rule{0.5\\linewidth}{0.5pt}\\end{center}'
count = content.count(rule)
counts['rules'] += count
content = content.replace(rule + '\n\n', '\n')
content = content.replace(rule + '\n', '\n')
# 4. Fix em-dashes in body text
count = content.count('------')
counts['emdashtext'] += count
content = content.replace('------', '——')
# 5. Strip numeric prefixes from section/subsection/subsubsection
for cmd in ['section', 'subsection', 'subsubsection']:
def make_stripper(cmd_name):
def strip_num(m):
title = m.group(1)
new_title = re.sub(r'^\d+\.(\d+(\.\d+)?)?\s*', '', title)
if new_title != title:
counts['secnum'] += 1
return '\\' + cmd_name + '{' + new_title + '}'
return m.group(0)
return strip_num
pattern = re.compile(r'\\' + cmd + r'\{([^}]+)\}')
content = pattern.sub(make_stripper(cmd), content)
with open(fpath, 'w', encoding='utf-8') as f:
f.write(content)
print(f'OK {fname}')
print(f'Done: labels={counts["labels"]} dashes={counts["dashes"]} rules={counts["rules"]} chapnum={counts["chapnum"]} secnum={counts["secnum"]} emdash_text={counts["emdashtext"]}')