text-compositor 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,173 @@
1
+ // 幅・高さいずれかが利用可能領域をはみ出す場合だけ、縦横比を保って自動縮小する
2
+ // (mermaidなど事前レンダリング済み画像用。正方形に近い図は幅基準だけだと高さが溢れるため、
3
+ // 幅・高さ両方の縮小率を計算し、小さい方(より厳しい制約)を採用する)。
4
+ // 【注意】layout()が返すsize.heightは「ページの残りスペース」ではなく「コンテナ全体の高さ」を
5
+ // 返すため、見出しや説明文がすでに使った分は考慮されない。動的計算は信頼できないため、
6
+ // タイトル・本文の余地を見込んだ固定の高さ上限(MAX_IMG_HEIGHT)を安全側に設定する。
7
+ #let MAX_IMG_HEIGHT = 12cm
8
+ #let fit-image(path) = layout(size => context {
9
+ let img = image(path)
10
+ let i-size = measure(img)
11
+ let w-scale = size.width / i-size.width
12
+ let h-scale = MAX_IMG_HEIGHT / i-size.height
13
+ let scale = calc.min(w-scale, h-scale, 1.0)
14
+ if scale < 1.0 {
15
+ image(path, width: i-size.width * scale)
16
+ } else {
17
+ img
18
+ }
19
+ })
20
+
21
+ // Graphviz(diagraph、#82でwidth/height明示指定に対応するためconf()の外へ出し、
22
+ // build.py生成コードから直接呼べるようエクスポートした。conf()内のshow raw.where(lang: "dot")
23
+ // ルールは、width/height未指定の呼び出し(自動縮小のみ)としてこの関数をそのまま使う)。
24
+ #import "@preview/diagraph:0.3.7": render
25
+ #let render-graph(code, width: none, height: none) = if width != none or height != none {
26
+ render(code,
27
+ width: if width != none { width } else { auto },
28
+ height: if height != none { height } else { auto })
29
+ } else {
30
+ layout(size => context {
31
+ let graph = render(code)
32
+ let g-size = measure(graph)
33
+ if g-size.width > size.width {
34
+ render(code, width: 100%)
35
+ } else {
36
+ graph
37
+ }
38
+ })
39
+ }
40
+
41
+ // GitHub形式のalert記法(#61)。template.typと同じ実装(note-me、MIT、@preview/note-me:0.6.0。
42
+ // #63でライセンス確認済み)。全テンプレートが同じ関数名を持つ必要があるため(#61参照)。
43
+ #import "@preview/note-me:0.6.0": note, tip, important, warning, caution
44
+ #let callout-fns = (note: note, tip: tip, important: important, warning: warning, caution: caution)
45
+ #let callout(kind: "note", body) = (callout-fns.at(kind, default: note))(body)
46
+
47
+ // 本文ページのヘッダー・フッター(#42)。template.typと同じ関数名でエクスポートし、build.py側が
48
+ // テンプレート種別を意識せず同じ呼び出し方でチャプター単位の上書きを再発行できるようにする。
49
+ // logo(#54): template.typと同じ実装。
50
+ #let render-header(header_text, logo) = if logo != none {
51
+ grid(
52
+ columns: (auto, auto),
53
+ column-gutter: 0.5em,
54
+ align: (left + horizon, left + horizon),
55
+ image(logo, height: 1.5em),
56
+ text(16pt, fill: luma(100))[#header_text],
57
+ )
58
+ } else {
59
+ align(left)[#text(16pt, fill: luma(100))[#header_text]]
60
+ }
61
+
62
+ #let render-footer(footer_text, paginate) = {
63
+ let page-num = align(right)[#text(16pt, fill: luma(100))[#context counter(page).display("1")]]
64
+ if footer_text != none and paginate {
65
+ grid(
66
+ columns: (1fr, 1fr),
67
+ align(left)[#text(16pt, fill: luma(100))[#footer_text]],
68
+ page-num,
69
+ )
70
+ } else if footer_text != none {
71
+ align(left)[#text(16pt, fill: luma(100))[#footer_text]]
72
+ } else if paginate {
73
+ page-num
74
+ } else {
75
+ none
76
+ }
77
+ }
78
+
79
+ // 本文ページの背景画像(#55)。template.typと同じ実装。
80
+ #let render-background(path) = if path != none {
81
+ place(top + left, image(path, width: 100%, height: 100%))
82
+ } else {
83
+ none
84
+ }
85
+
86
+ #let conf(
87
+ title: none,
88
+ subtitle: none,
89
+ author: none,
90
+ date: none,
91
+ paper_size: "presentation-16-9",
92
+ landscape: true,
93
+ cover: true,
94
+ cover_page_number: false,
95
+ toc: false,
96
+ graphviz: true,
97
+ header: none,
98
+ footer: none,
99
+ paginate: true,
100
+ background: none,
101
+ logo: none,
102
+ doc,
103
+ ) = {
104
+ // フォント設定(CJKフォントは build.py が取得・キャッシュした Noto Sans JP を --font-path 経由で渡す。
105
+ // OSフォントは直接指定しない。Noto Sans JP に無いグリフはTypstが自動でシステムフォントにフォールバックする)
106
+ set text(font: "Noto Sans JP", size: 18pt)
107
+
108
+ // Graphviz。graphviz: false のときは既定のraw表示(素のコード表示)にフォールバックする。
109
+ // showルール自体は常時登録する(ブロックスコープで閉じるため、ifの中で宣言すると効かなくなる)。
110
+ // render-graph()自体はモジュールのトップレベルで定義済み(#82でwidth/height対応のため移動)。
111
+ show raw.where(lang: "dot"): it => if graphviz { align(center)[#render-graph(it.text)] } else { it }
112
+ show raw.where(lang: "graphviz"): it => if graphviz { align(center)[#render-graph(it.text)] } else { it }
113
+
114
+ let page-number-footer = align(right)[#text(16pt, fill: luma(100))[#context counter(page).display("1")]]
115
+
116
+ // 表紙(cover: false のときは出さず、Markdown側のタイトルスライドに任せる)
117
+ // 表紙だけページ番号表示を切り替えたいので、本文とは別に一時的な set page で囲む
118
+ // (Typstの set は現在のブロックを抜けると元に戻るため、表紙用ページ設定を本文へ漏らさず適用できる)
119
+ if cover and title != none {
120
+ set page(
121
+ paper: paper_size,
122
+ flipped: landscape,
123
+ margin: (x: 2cm, y: 1.5cm),
124
+ header: none,
125
+ footer: if cover_page_number { page-number-footer } else { none }
126
+ )
127
+ align(center + horizon)[
128
+ #text(44pt, weight: "bold", fill: rgb("#003366"))[#title]
129
+ #v(2em)
130
+ #text(28pt)[#subtitle]
131
+ #v(3em)
132
+ #text(20pt)[#author]
133
+ ]
134
+ pagebreak()
135
+ }
136
+
137
+ // 本文のページ設定
138
+ set page(
139
+ paper: paper_size,
140
+ flipped: landscape,
141
+ margin: (x: 2cm, y: 1.5cm),
142
+ header: if header != none { render-header(header, logo) } else { none },
143
+ footer: render-footer(footer, paginate),
144
+ background: render-background(background),
145
+ )
146
+
147
+ // 本文の設定
148
+ set par(justify: true, leading: 1.2em)
149
+
150
+ // H1 (実は使用しない想定だが念のため)
151
+ show heading.where(level: 1): it => {
152
+ align(center)[#text(36pt, weight: "bold")[#it.body]]
153
+ v(1.5em)
154
+ }
155
+
156
+ // H2をスライドタイトルとして扱う
157
+ show heading.where(level: 2): it => {
158
+ text(24pt, weight: "bold", fill: rgb("#003366"))[#it.body]
159
+ v(0.5em)
160
+ line(length: 100%, stroke: 2pt + rgb("#003366"))
161
+ v(1em)
162
+ }
163
+
164
+ // ブロッククオートのスタイル
165
+ show quote.where(block: true): it => rect(
166
+ fill: luma(245),
167
+ stroke: (left: 4pt + rgb("#003366")),
168
+ inset: 1em,
169
+ width: 100%
170
+ )[#it.body]
171
+
172
+ doc
173
+ }
@@ -0,0 +1,187 @@
1
+ // 幅・高さいずれかが利用可能領域をはみ出す場合だけ、縦横比を保って自動縮小する(mermaidなど事前レンダリング済み画像用)
2
+ #let MAX_IMG_HEIGHT = 12cm
3
+ #let fit-image(path) = layout(size => context {
4
+ let img = image(path)
5
+ let i-size = measure(img)
6
+ let w-scale = size.width / i-size.width
7
+ let h-scale = MAX_IMG_HEIGHT / i-size.height
8
+ let scale = calc.min(w-scale, h-scale, 1.0)
9
+ if scale < 1.0 {
10
+ image(path, width: i-size.width * scale)
11
+ } else {
12
+ img
13
+ }
14
+ })
15
+
16
+ // Graphviz(diagraph、#82でwidth/height明示指定に対応するためconf()の外へ出し、
17
+ // build.py生成コードから直接呼べるようエクスポートした。conf()内のshow raw.where(lang: "dot")
18
+ // ルールは、width/height未指定の呼び出し(自動縮小のみ)としてこの関数をそのまま使う)。
19
+ #import "@preview/diagraph:0.3.7": render
20
+ #let render-graph(code, width: none, height: none) = if width != none or height != none {
21
+ render(code,
22
+ width: if width != none { width } else { auto },
23
+ height: if height != none { height } else { auto })
24
+ } else {
25
+ layout(size => context {
26
+ let graph = render(code)
27
+ let g-size = measure(graph)
28
+ if g-size.width > size.width {
29
+ render(code, width: 100%)
30
+ } else {
31
+ graph
32
+ }
33
+ })
34
+ }
35
+
36
+ // GitHub形式のalert記法(`> [!NOTE]`等、#61)。build.py側がblockquoteの先頭行から種別を検出し、
37
+ // callout(kind: "note")[...]のようなコードを生成する。実体はnote-me(MIT、@preview/note-me:0.6.0。
38
+ // #63でライセンス確認済み)にそのまま委譲する。全テンプレートが同じ関数名を持つ必要があるため
39
+ // (原稿を書く人はテンプレートの種類を意識しない。doc/usage/10_custom_template.md参照)、
40
+ // slide.typにも同じ実装がある。
41
+ #import "@preview/note-me:0.6.0": note, tip, important, warning, caution
42
+ #let callout-fns = (note: note, tip: tip, important: important, warning: warning, caution: caution)
43
+ #let callout(kind: "note", body) = (callout-fns.at(kind, default: note))(body)
44
+
45
+ // 本文ページのヘッダー・フッター(#42)。chapters[]/front-matterによるチャプター単位の上書きは
46
+ // build.py側が章ごとに#set page(header: render-header(...), footer: render-footer(...))を
47
+ // 再発行する形で実現する(state()は使わない。#16の反省点。landscape/paper_sizeと同じ
48
+ // 「明示指定を都度出し直す」パターンで、チャプターの並べ替えに対して安全)。
49
+ // logo(#54): 左にロゴ画像、右にheader_text。logoがnoneのときは従来どおり右寄せのみ。
50
+ #let render-header(header_text, logo) = {
51
+ if logo != none {
52
+ grid(
53
+ columns: (auto, 1fr),
54
+ align: (left + horizon, right + horizon),
55
+ image(logo, height: 1.2em),
56
+ text(8pt, fill: luma(100))[#header_text],
57
+ )
58
+ } else {
59
+ align(right)[#text(8pt, fill: luma(100))[#header_text]]
60
+ }
61
+ v(0.5em)
62
+ line(length: 100%, stroke: 0.5pt + luma(200))
63
+ }
64
+
65
+ #let render-footer(footer_text, paginate) = {
66
+ let page-num = context counter(page).display("1")
67
+ if footer_text != none and paginate {
68
+ grid(
69
+ columns: (1fr, 1fr),
70
+ align(left)[#text(9pt)[#footer_text]],
71
+ align(right)[#text(9pt)[#page-num]],
72
+ )
73
+ } else if footer_text != none {
74
+ align(left)[#text(9pt)[#footer_text]]
75
+ } else if paginate {
76
+ align(center)[#text(9pt)[#page-num]]
77
+ } else {
78
+ none
79
+ }
80
+ }
81
+
82
+ // 本文ページの背景画像(#55)。header/footerと同じ「章単位で明示指定を出し直す」パターンで、
83
+ // build.py側が章ごとに#set page(background: render-background(...))を再発行する。
84
+ // 画像はページ全面に敷き、本文はその上に通常どおりレイアウトされる(重なりの調整はしない。
85
+ // 濃い背景を使うと本文が読みにくくなるため、薄い画像を使うことを利用者側で判断する)。
86
+ #let render-background(path) = if path != none {
87
+ place(top + left, image(path, width: 100%, height: 100%))
88
+ } else {
89
+ none
90
+ }
91
+
92
+ #let conf(
93
+ title: none,
94
+ subtitle: none,
95
+ author: none,
96
+ date: none,
97
+ paper_size: "a4",
98
+ landscape: false,
99
+ cover: true,
100
+ cover_page_number: false,
101
+ toc: false,
102
+ graphviz: true,
103
+ header: none,
104
+ footer: none,
105
+ paginate: true,
106
+ background: none,
107
+ logo: none,
108
+ doc,
109
+ ) = {
110
+ // フォント設定(CJKフォントは build.py が取得・キャッシュした Noto Sans JP を --font-path 経由で渡す。
111
+ // OSフォントは直接指定しない。Noto Sans JP に無いグリフはTypstが自動でシステムフォントにフォールバックする)
112
+ set text(font: "Noto Sans JP", size: 10.5pt)
113
+
114
+ // プラグイン: Graphviz (dot) 自動レンダリングと、はみ出し防止の自動縮小
115
+ // graphviz: false のときは既定のraw表示(素のコード表示)にフォールバックする。
116
+ // showルール自体は常時登録する(ifブロックの中で宣言すると、ブロックを抜けた
117
+ // doc側には効かなくなる。show/setはブロックスコープで閉じるため)。
118
+ // render-graph()自体はモジュールのトップレベルで定義済み(#82でwidth/height対応のため移動)。
119
+ show raw.where(lang: "dot"): it => if graphviz { align(center)[#render-graph(it.text)] } else { it }
120
+ show raw.where(lang: "graphviz"): it => if graphviz { align(center)[#render-graph(it.text)] } else { it }
121
+
122
+ // -------------------------
123
+ // 1. 表紙 (Cover Page) : cover: false のときは省略する
124
+ // -------------------------
125
+ if cover {
126
+ set page(
127
+ paper: "a4", flipped: false, margin: 2.5cm, header: none,
128
+ footer: if cover_page_number { align(center)[#text(10pt)[#context counter(page).display("1")]] } else { none }
129
+ )
130
+
131
+ align(center + horizon)[
132
+ #text(24pt, weight: "bold")[#title]
133
+ #v(3em)
134
+ #text(14pt)[#subtitle]
135
+ #v(4em)
136
+ #text(12pt)[#author]
137
+ #v(1em)
138
+ #text(12pt)[#date]
139
+ ]
140
+ pagebreak()
141
+ }
142
+
143
+ // -------------------------
144
+ // 2. 目次 (Table of Contents) : toc: false のときは省略する
145
+ // -------------------------
146
+ if toc {
147
+ set page(
148
+ paper: paper_size,
149
+ flipped: landscape,
150
+ margin: (x: 2cm, y: 2.5cm),
151
+ header: none,
152
+ footer: align(center)[#text(10pt)[- #context counter(page).display("i") -]]
153
+ )
154
+ counter(page).update(1) // 目次のページ番号を i から開始
155
+
156
+ align(center)[
157
+ #text(18pt, weight: "bold")[目次]
158
+ ]
159
+ v(1.5em)
160
+ outline(title: none, indent: auto)
161
+ pagebreak()
162
+ }
163
+
164
+ // -------------------------
165
+ // 3. 本文 (Body)
166
+ // -------------------------
167
+ set page(
168
+ paper: paper_size,
169
+ flipped: landscape,
170
+ header: render-header(if header != none { header } else { title }, logo),
171
+ footer: render-footer(footer, paginate),
172
+ background: render-background(background),
173
+ )
174
+ counter(page).update(1) // 本文のページ番号を 1 からリセット
175
+
176
+ set par(justify: true, leading: 0.8em)
177
+
178
+ // 見出し1 (大見出し/章) の直前で必ず改ページする(すでにページ先頭の場合はスキップ)
179
+ show heading.where(level: 1): it => {
180
+ pagebreak(weak: true)
181
+ v(1em)
182
+ it
183
+ v(0.5em)
184
+ }
185
+
186
+ doc
187
+ }
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: text-compositor
3
+ Version: 0.2.0
4
+ Summary: A tool for composing multiple text files into a PDF document.
5
+ License-Expression: MIT
6
+ Project-URL: Repository, https://github.com/tokudiro/text-compositor
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: typst==0.15.0
11
+ Requires-Dist: markdown-it-py==3.0.0
12
+ Requires-Dist: mdit-py-plugins==0.6.1
13
+ Requires-Dist: PyYAML==6.0.2
14
+ Requires-Dist: platformdirs==4.3.6
15
+ Provides-Extra: mermaid
16
+ Requires-Dist: playwright==1.62.0; extra == "mermaid"
17
+ Dynamic: license-file
18
+
19
+ # text-compositor
20
+
21
+ *[日本語版 (Japanese version)](README-ja.md)*
22
+
23
+ A tool that treats multiple text files as independent fragments and deterministically composes them into a single human-readable PDF document. Works equally well for AI-generated drafts refined by humans and for existing, hand-authored documents you want to organize and consolidate.
24
+
25
+ This README covers only the essentials needed to get started quickly. For a full walkthrough of `config.yaml` and how to write your source documents, see the [usage guide](doc/usage/) (which can also be built into a PDF with `cd doc/usage && python ../../build.py`). For detailed design rationale and implementation status, see [doc/spec.md](doc/spec.md). Differences from general-purpose tools like Quarto are summarized in [doc/diff.md](doc/diff.md).
26
+
27
+ ## Features
28
+
29
+ - Combines multiple text files (chapters) into a single PDF
30
+ - Markdown → AST via [markdown-it-py](https://github.com/executablebooks/markdown-it-py) → deterministic conversion to [Typst](https://typst.app/) syntax → PDF output
31
+ - Outputs other text files to the PDF unchanged
32
+ - Separates the tool itself from the documentation (source files), which can live anywhere outside the repository
33
+ - Python-centric with minimal downloads, no dependency on external servers or SaaS (runs the same way on GitHub Actions and locally on Windows/Linux/macOS)
34
+
35
+ Files listed in `chapters` are handled differently depending on their extension:
36
+
37
+ - `.md`/`.markdown`: converted as Markdown
38
+ - `.yaml`/`.yml`/`.json`: rendered as monospaced text with syntax highlighting
39
+ - `.dot`/`.gv`, `.mmd`, `.puml`/`.plantuml`/`.pu`, `.d2`: each rendered as a one-chapter diagram (Graphviz, Mermaid, PlantUML, D2 respectively)
40
+ - `.csv`: rendered as a structured Typst table
41
+ - everything else (plain text, code files, etc.): rendered as plain monospaced text
42
+
43
+ See the [usage guide](doc/usage/) for details.
44
+
45
+ ## Requirements
46
+
47
+ - Python 3.10+
48
+
49
+ > **Windows note — Microsoft Store Python is not supported, even inside `pipx`/`venv`:** If Python was installed from the Microsoft Store, this tool cannot be used with it, full stop — `pipx` or a `venv` does **not** work around this.
50
+ >
51
+ > Windows redirects that Python's writes under `%LOCALAPPDATA%` into a package-private folder, and this redirection follows the Store installation into any `venv`/`pipx` environment created from it (verified by testing), breaking every subprocess-based feature (PlantUML, Mermaid, D2) even though the files appear to exist to Python itself.
52
+ >
53
+ > Before installing this tool, install Python from [python.org](https://www.python.org/downloads/) (or another non-Store distribution such as `winget install Python.Python.3.12`) and use that Python for the steps below. Run `text-compositor --check-env` afterward to confirm the environment is set up correctly.
54
+
55
+ There are two ways to install and run this tool.
56
+
57
+ ### Option A: `pip install` (recommended)
58
+
59
+ ```bash
60
+ pipx install text-compositor
61
+ ```
62
+
63
+ `pipx` installs the tool into an isolated environment and exposes the `text-compositor` command — recommended for most users. If you're developing this tool itself, use a `venv` instead so an editable install (`pip install -e .`) picks up code changes immediately:
64
+
65
+ ```bash
66
+ python -m venv .venv
67
+ .venv/bin/pip install -e . # Windows: .venv\Scripts\pip install -e .
68
+ ```
69
+
70
+ ### Option B: Clone and run directly (no installation)
71
+
72
+ ```bash
73
+ pip install -r requirements.txt
74
+ ```
75
+
76
+ No Typst compiler binary is bundled; it's obtained from the `typst` package (PyPI wheel) installed via `pip install` above. No additional downloads or installation steps are needed.
77
+
78
+ ### Using Mermaid diagrams (optional)
79
+
80
+ This is only needed if your source documents use ` ```mermaid ` fences (or specify an `.mmd` file directly in `chapters`). With Option A, install the `mermaid` extra:
81
+
82
+ ```bash
83
+ pipx install "text-compositor[mermaid]"
84
+ # or, if already installed: pipx inject text-compositor playwright==1.62.0
85
+ ```
86
+
87
+ With Option B (clone and run directly):
88
+
89
+ ```bash
90
+ pip install playwright==1.62.0
91
+ ```
92
+
93
+ - **A Google Chrome or Microsoft Edge installation already present on your system** (no new download by default; it's auto-detected and reused at build time)
94
+ - The `playwright` package above (used only to connect to the existing browser via CDP; Playwright's own browser-download feature is not used by default)
95
+
96
+ Node.js/npm is not required. At build time, the official single-file Mermaid bundle (`mermaid.min.js`, ~3.4MB) is fetched and loaded into a headless browser to convert diagrams to SVG (the bundle JS itself is cached under an OS-standard user cache directory — e.g. `%LOCALAPPDATA%\text-compositor\Cache` on Windows, `~/.cache/text-compositor` on Linux — and conversion results are cached under `.text-compositor/cache/`; neither is re-fetched afterward). None of this is needed for documents that don't use Mermaid.
97
+
98
+ If no Chrome/Edge is found on the system, the build fails by default. Setting `plugins: { mermaid_auto_download: true }` instead auto-fetches Playwright's own Chromium, but **this download is about 700MB** (this setting exists as a last resort for when no pre-installed browser is available; downloading that much by default is intentionally avoided).
99
+
100
+ ### Using PlantUML diagrams (optional)
101
+
102
+ No extra `config.yaml` settings are needed to use ` ```plantuml ` fences in your source documents (or to specify a `.puml` file directly in `chapters`) — `plugins.plantuml` defaults to `true` and no additional `pip install` is required.
103
+
104
+ - If Java (11+) is available locally, it's reused as-is
105
+ - Otherwise, Eclipse Temurin JRE (Adoptium distribution, ~49.7MB) is auto-fetched and cached by default under the same user cache directory as above. Setting `plugins: { plantuml_auto_download: false }` disables the auto-fetch and fails the build instead
106
+ - GitHub Actions' `ubuntu-latest` ships with Java by default, so no extra download happens on CI
107
+
108
+ The layout engine is the pure-Java Smetana implementation, so no external binary like Graphviz (`dot`) is required. PlantUML itself (MIT edition, ~17.6MB) is cached under the same user cache directory, and conversion results are cached under `.text-compositor/cache/`, same as Mermaid.
109
+
110
+ ### Using D2 diagrams (optional)
111
+
112
+ No extra `config.yaml` settings are needed to use ` ```d2 ` fences in your source documents (or to specify a `.d2` file directly in `chapters`) — `plugins.d2` defaults to `true` and no additional `pip install` is required.
113
+
114
+ - If the `d2` command is available locally, it's reused as-is
115
+ - Otherwise, the official D2 CLI binary (a single Go executable, ~13MB) is auto-fetched and cached by default under the same user cache directory as above. Setting `plugins: { d2_auto_download: false }` disables the auto-fetch and fails the build instead
116
+
117
+ Unlike Java (used by PlantUML) or a browser (used by Mermaid), GitHub Actions' `ubuntu-latest` does not ship with D2 preinstalled, so a `d2` diagram triggers this ~13MB download on every CI run (this project's workflows don't persist the cache directory across separate runs).
118
+
119
+ ## Usage
120
+
121
+ If installed via `pip`/`pipx` (Option A):
122
+
123
+ ```bash
124
+ text-compositor --config <path/to/text-compositor.config.yaml>
125
+ ```
126
+
127
+ If cloned and run directly (Option B):
128
+
129
+ ```bash
130
+ python build.py --config <path/to/text-compositor.config.yaml>
131
+ ```
132
+
133
+ If `--config` is omitted, `text-compositor.config.yaml` (or `.json`) located directly in the current directory is auto-detected.
134
+
135
+ ```bash
136
+ cd my-project/
137
+ python /path/to/text-compositor/build.py
138
+ ```
139
+
140
+ Run `text-compositor --check-env` (or `python build.py --check-env`) to check your environment (dependencies, Typst version, cached assets, Mermaid/PlantUML/D2 prerequisites) without running a build.
141
+
142
+ Other flags control runtime behavior only (not document content, which stays entirely in `config.yaml`): `-q`/`--quiet` suppresses `[Info]`-level logging, `-v`/`--verbose` adds extra detail (which chapter is being processed, cache reuse), and `--keep-temp` keeps the intermediate `temp_build.typ` around after a successful build instead of deleting it (useful for debugging; it's always kept after a failed build).
143
+
144
+ See [sample/text-compositor.config.yaml](sample/text-compositor.config.yaml) for how to write the config file, and the [usage guide](doc/usage/) for details on `document:`/`plugins:`, front matter, Marp directives, and more. The Markdown files listed in `chapters` are concatenated in order to produce the PDF.
145
+
146
+ ## Trying the sample
147
+
148
+ ```bash
149
+ cd sample/
150
+ python ../build.py
151
+ ```
152
+
153
+ This generates `sample/System_Specification.pdf`.
154
+
155
+ ## Implementation status
156
+
157
+ The core feature implemented so far is combining multiple files into a single PDF according to the config file specified via `--config` (or auto-detected). Expanding CLI options (e.g., overriding the output path) is still in the planning stage. See [GitHub Issues](https://github.com/tokudiro/text-compositor/issues) for known issues and upcoming plans.
158
+
159
+ ## License
160
+
161
+ [MIT License](LICENSE)
@@ -0,0 +1,10 @@
1
+ text_compositor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ text_compositor/build.py,sha256=it-jK24k6VN05Ea7PcXE4rufAsQ7Bbyz8RPXnl00b5g,165721
3
+ text_compositor/templates/slide.typ,sha256=IRdWxGC2Ehcc7CDdYF-MP4l_z7nrSTIAHCuF8R-Be8k,6824
4
+ text_compositor/templates/template.typ,sha256=AUKvvdhnczCM70ai5X5aNjOChoBaqDrXc9LbYH7LZ00,7329
5
+ text_compositor-0.2.0.dist-info/licenses/LICENSE,sha256=xUIdpZav5CQwNXgUV8vEvjoYPbR7UeRxi-op2SavSnM,1065
6
+ text_compositor-0.2.0.dist-info/METADATA,sha256=rgCdQ2ADZKBUySGD50pBv8eBhiUmbrJ8-8eMSUwSRwY,9551
7
+ text_compositor-0.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
8
+ text_compositor-0.2.0.dist-info/entry_points.txt,sha256=dCT80LB4OjazbH_9-2xpRIyj4mNmiu1sSxMbrstyRjk,64
9
+ text_compositor-0.2.0.dist-info/top_level.txt,sha256=Y64ydH6Fo5HPyutCkQnZQrbb8IQUY-yFIXOvyEro-A8,16
10
+ text_compositor-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ text-compositor = text_compositor.build:build
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tokudiro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ text_compositor