From 690765709078260949397cf8768641ed498fa777 Mon Sep 17 00:00:00 2001 From: iacore Date: Sun, 28 Jun 2026 12:18:05 +0800 Subject: [PATCH] Add MPI Typst template --- AGENTS.md | 26 ++++ scripts/compile-typst.fish | 20 +++ .../lib/mpi-bilingual-template.typ | 136 ++++++++++++++++++ .../从物品整理到心灵整理/helpers.typ | 45 ------ .../mindful-organizing.typ | 35 ++--- 5 files changed, 190 insertions(+), 72 deletions(-) create mode 100755 scripts/compile-typst.fish create mode 100644 translate-files/lib/mpi-bilingual-template.typ delete mode 100644 translate-files/从物品整理到心灵整理/helpers.typ diff --git a/AGENTS.md b/AGENTS.md index 693db08..23c2642 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -112,6 +112,32 @@ When editing `.dj` files, use `patch` (mode='replace') — not regex-based string replacement in `execute_code`. `patch` is safer, surfaces conflicts, and produces a diff you can review. +## Typst Bilingual Template + +For producing PDFs from bilingual Chinese-English articles: + +- Repo / project root: `~/documents/mpi/` +- Template directory (contains `lib/`): `~/documents/mpi/translate-files/` +- Template: `translate-files/lib/mpi-bilingual-template.typ` +- Example host: `translate-files/从物品整理到心灵整理/mindful-organizing.typ` +- Design notes: `references/typst-template-design.md` + +Host files should contain only one `#import` and one `#show:` rule; all +formatting is handled by the template. + +Compile with the helper script: + +```bash +~/documents/mpi/scripts/compile-typst.fish ./从物品整理到心灵整理/mindful-organizing.typ [output.pdf] +``` + +Or manually from the template directory: + +```bash +cd ~/documents/mpi/translate-files +typst compile --root . ./从物品整理到心灵整理/mindful-organizing.typ +``` + ## Scripts Utility scripts in `scripts/` (fish for CLI wrappers, Python for data processing). diff --git a/scripts/compile-typst.fish b/scripts/compile-typst.fish new file mode 100755 index 0000000..46ff5dd --- /dev/null +++ b/scripts/compile-typst.fish @@ -0,0 +1,20 @@ +#!/usr/bin/env fish +# Compile a Typst file to PDF. +# Usage: compile-typst [output.pdf] +# Output defaults to /tmp/.pdf +# The Typst project root is set to the parent of the file's directory +# (so imports like ../lib/... resolve correctly). + +set src (realpath $argv[1]) +set src_dir (dirname $src) +set root (dirname $src_dir) + +if set -q argv[2] + set out "$argv[2]" +else + set base (basename $src .typ) + set out "/tmp/$base.pdf" +end + +typst compile --root $root $src $out +echo $out diff --git a/translate-files/lib/mpi-bilingual-template.typ b/translate-files/lib/mpi-bilingual-template.typ new file mode 100644 index 0000000..0555b5a --- /dev/null +++ b/translate-files/lib/mpi-bilingual-template.typ @@ -0,0 +1,136 @@ +// MPI Bilingual Chinese-English Template +// ============================================================ +// A simple Typst template for bilingual Chinese-English Buddhist +// texts, matching the 静心学堂丛书 formatting guide. +// +// Fonts (all available from Google Fonts): +// - Chinese body: Noto Serif CJK SC +// - English body: Noto Serif (Times New Roman substitute) +// +// Format highlights: +// - Body size: 10.5pt (Chinese 五号) +// - Line spacing: 1.5em +// - Justified body text with 2em first-line indent +// - Headings unnumbered, with 1.2em block spacing +// - Footnotes in 8pt, left aligned +// ============================================================ + +#let chinese-font = "Noto Serif CJK SC" +#let english-font = "Noto Serif" + +// Convert any content to a plain string. +#let to-string(content) = { + if type(content) == str { + content + } else if content.func() == text { + content.text + } else if content.has("children") { + content.children.map(to-string).join("") + } else { + "" + } +} + +// Return true if the content is primarily Chinese. +#let is-chinese(content) = { + let txt = to-string(content) + let cn-re = regex("[\u{4e00}-\u{9fff}]") + let en-re = regex("[A-Za-z]") + for c in txt.clusters() { + if c.match(cn-re) != none { return true } + if c.match(en-re) != none { return false } + } + false +} + +// Bilingual table of contents. +// Only English headings show dotted leaders and page numbers; +// Chinese headings are shown compactly as section headers. +#let toc() = context { + let indent-per-level = 1.5em + for h in query(heading.where(outlined: true)) { + let cn = is-chinese(h.body) + block( + inset: (left: (h.level - 1) * indent-per-level), + above: if cn { 0.3em } else { 0.7em }, + below: if cn { 0.7em } else { 1.2em }, + { + if cn { + h.body + } else { + let page-num = counter(page).at(h.location()).first() + h.body + box(width: 1fr, repeat[.]) + str(page-num) + } + } + ) + } +} + +// Document class. +// +// title-cn, title-en, subtitle-cn, subtitle-en accept free Typst content. +// The template centers them and applies only size/weight; the global font +// stack handles Chinese vs English glyphs. +#let mpi-bilingual( + title-cn: none, + title-en: none, + subtitle-cn: none, + subtitle-en: none, + show-toc: true, + body, +) = { + // Base text: Chinese font first, then English fallback. + set text(font: (chinese-font, english-font), size: 10.5pt, lang: "zh") + + // A4 page with 2.5cm margins. + set page( + paper: "a4", + margin: (top: 2.5cm, bottom: 2.5cm, left: 2.5cm, right: 2.5cm), + numbering: "1", + ) + + // Body paragraphs: justified, 1.5 line spacing, 2em first-line indent, + // with a blank line's worth of space between paragraphs. + set par(justify: true, leading: 1.5em, spacing: 1.5em, first-line-indent: (amount: 2em, all: true)) + + // Headings: no numbering, bold, with generous block spacing. + set heading(numbering: none) + show heading: it => block(above: 2em, below: 1.2em, it.body) + + // Footnotes: 8pt, left aligned. + show footnote: set text(size: 8pt) + set footnote.entry(indent: 0pt) + + // Title page. + align(center)[ + #set par(leading: 1.5em, spacing: 0pt) + #if title-cn != none { + text(size: 1.4em, weight: "bold")[#title-cn] + } + #if subtitle-cn != none { + linebreak() + subtitle-cn + } + #v(1.5em) + #if title-en != none { + text(size: 1.2em, weight: "bold")[#title-en] + } + #if subtitle-en != none { + linebreak() + subtitle-en + } + ] + + v(2.5em) + + // Table of contents. + if show-toc { + align(center)[#text(size: 1.2em)[目录 / Contents]] + v(0.8em) + toc() + pagebreak() + } + + // Main body. + body +} diff --git a/translate-files/从物品整理到心灵整理/helpers.typ b/translate-files/从物品整理到心灵整理/helpers.typ deleted file mode 100644 index f697670..0000000 --- a/translate-files/从物品整理到心灵整理/helpers.typ +++ /dev/null @@ -1,45 +0,0 @@ -// Helper functions for the bilingual outline and table of contents. - -#let to-string(content) = { - if type(content) == str { - content - } else if content.func() == text { - content.text - } else if content.has("children") { - content.children.map(to-string).join("") - } else { - "" - } -} - -#let is-chinese-heading(content) = { - let txt = to-string(content) - let cn-re = regex("[\u{4e00}-\u{9fff}]") - let en-re = regex("[A-Za-z]") - for c in txt.clusters() { - if c.match(cn-re) != none { return true } - if c.match(en-re) != none { return false } - } - false -} - -#let toc() = context { - let indent-per-level = 1.5em - let headings = query(heading.where(outlined: true)) - for h in headings { - let is-cn = is-chinese-heading(h.body) - block( - inset: (left: (h.level - 1) * indent-per-level), - above: if is-cn { 0.3em } else { 0.7em }, - below: if is-cn { 0.7em } else { 1.2em }, - { - if is-cn { - h.body - } else { - let page-num = counter(page).at(h.location()).first() - h.body + box(width: 1fr, repeat[.]) + str(page-num) - } - } - ) - } -} diff --git a/translate-files/从物品整理到心灵整理/mindful-organizing.typ b/translate-files/从物品整理到心灵整理/mindful-organizing.typ index 63ee1e7..44bbbd8 100644 --- a/translate-files/从物品整理到心灵整理/mindful-organizing.typ +++ b/translate-files/从物品整理到心灵整理/mindful-organizing.typ @@ -1,33 +1,14 @@ // Auto-generated from DOCX via pandoc + cleanup -#set text(font: ("Noto Serif CJK SC", "Noto Serif"), size: 11pt, lang: "zh") -#set page(margin: (top: 2.5cm, bottom: 2.5cm, left: 2.5cm, right: 2.5cm), numbering: "1") -#set heading(numbering: none) -#set par(justify: true, leading: 0.8em) -#show heading: set block(spacing: 1.2em) +// Reformatted to use lib/mpi-bilingual-template.typ -#import "helpers.typ": toc +#import "../lib/mpi-bilingual-template.typ": mpi-bilingual -#align(center)[ - #heading(outlined: false)[从物品整理到心灵整理] - #v(0.5em) - ——济群法师 2024年9月28日讲于泰宁甘露别院 -] - -#align(center)[ - *From Organizing Belongings to Organizing the Mind* - #v(0.3em) - —Lectured by Master Jiqun at Amrita Retreat Center, Taining, September 28, 2024 -] - -#v(3em) -#align(center)[#text(size: 1.2em)[目录 / Contents]] -#v(0.8em) -#set page(numbering: none) -#toc() -#pagebreak() -#set page(numbering: "1") - -#set par(first-line-indent: (amount: 2em, all: true)) +#show: mpi-bilingual.with( + title-cn: [从物品整理到心灵整理], + title-en: [From Organizing Belongings to Organizing the Mind], + subtitle-cn: [——济群法师 2024年9月28日讲于泰宁甘露别院], + subtitle-en: [—Lectured by Master Jiqun at Amrita Retreat Center, Taining, September 28, 2024], +) 静心整理的项目已经研发很长时间,我们原来还做过断舍离的项目。静心整理和断舍离,可以说是两个项目,也可以说是一个项目的两部分。为什么我们讲了断舍离,还要再讲静心整理?主要是因为,断舍离落实起来相对较难。关于这个问题,我曾问过项目组:虽然社会上一直在倡导断舍离,但相关从业者有多少?真正在实践的人有多少?其实并不多。因为对普通人来说,舍弃物品并非易事,出离物欲更是难上加难。