ziban 0.1.0__tar.gz

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,41 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: ${{ matrix.os }} / py${{ matrix.python }}
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, windows-latest, macos-latest]
16
+ python: ["3.9", "3.12", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python }}
22
+ - name: Install
23
+ run: python -m pip install -e ".[dev]"
24
+ - name: Test
25
+ run: python -m pytest -q
26
+
27
+ demo:
28
+ name: CLI demo
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ - uses: actions/setup-python@v5
33
+ with:
34
+ python-version: "3.12"
35
+ - name: Install
36
+ run: python -m pip install -e .
37
+ - name: Break a sentence
38
+ run: |
39
+ printf '他说:「这次不一样。」然后关掉了对话框。' | ziban wrap - --width-em 10 --ruler
40
+ - name: Show the ruleset
41
+ run: ziban rules
ziban-0.1.0/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .pytest_cache/
7
+ .venv/
8
+ venv/
9
+ .idea/
10
+ .vscode/
11
+ .DS_Store
ziban-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ziban contributors
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.
ziban-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.5
2
+ Name: ziban
3
+ Version: 0.1.0
4
+ Summary: CJK typesetting for programmatic renderers: line breaking, kinsoku, punctuation compression, hanging punctuation.
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: chinese,cjk,kinsoku,line-breaking,pdf,pillow,text-layout,typography
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Multimedia :: Graphics
13
+ Classifier: Topic :: Text Processing :: Fonts
14
+ Classifier: Topic :: Text Processing :: Linguistic
15
+ Requires-Python: >=3.9
16
+ Requires-Dist: tomli>=1.1.0; python_version < '3.11'
17
+ Provides-Extra: dev
18
+ Requires-Dist: pillow>=9.0; extra == 'dev'
19
+ Requires-Dist: pytest>=7.0; extra == 'dev'
20
+ Provides-Extra: pillow
21
+ Requires-Dist: pillow>=9.0; extra == 'pillow'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # ziban
25
+
26
+ **Correct CJK line breaking for everything that is not a browser.**
27
+
28
+ [中文说明 →](README.zh-CN.md)
29
+
30
+ Browsers got `text-spacing-trim`, `line-break: strict` and proper kinsoku years
31
+ ago. Everything else — Pillow, ReportLab, SVG, ffmpeg subtitles, `python-pptx`,
32
+ canvas — still reimplements it badly, because there is nothing to ask.
33
+
34
+ `ziban` is that thing: a pure-Python typesetting engine that turns a string and
35
+ a box into lines a Chinese typesetter would accept — and tells you, in machine
36
+ readable form, every time it had to compromise.
37
+
38
+ ![Same text, same column: naive chunking on the left, ziban on the right](docs/compare.png)
39
+
40
+ ## The problem, in eight seconds
41
+
42
+ Naive `if len(s) > N: cut()`:
43
+
44
+ ```
45
+ 他说:「这次不一样。
46
+ 」然后关掉了对话框。
47
+ ```
48
+
49
+ `ziban`:
50
+
51
+ ```
52
+ 他说:「这次不一样。」
53
+ 然后关掉了对话框。
54
+ ```
55
+
56
+ The first version starts a line with `」`. That is not a matter of taste:
57
+ [《中文排版需求》(CLREQ)](https://www.w3.org/TR/clreq/) forbids closing
58
+ punctuation at line start (避头) and opening punctuation at line end (避尾).
59
+ Every renderer that is not a browser quietly breaks that rule — and the
60
+ punctuation it breaks is exactly the punctuation readers notice.
61
+
62
+ The image above is generated, not drawn by hand:
63
+ `python examples/compare.py --font <path-to-a-CJK-font>`. Re-run it after any
64
+ change to the rules and you will see immediately whether the typography got
65
+ better or worse.
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ pip install ziban # core, zero dependencies
71
+ pip install "ziban[pillow]" # + measuring with real font files
72
+ ```
73
+
74
+ Python 3.9+. No browser, no native build, no ICU, no model.
75
+
76
+ ## Use it
77
+
78
+ ### As a CLI
79
+
80
+ ```console
81
+ $ echo "这是一个比较长的段落,用来看断行在真实文本上的表现。" \
82
+ | ziban wrap - --width-em 8 --ruler
83
+ +----------------+
84
+ 1 | 这是一个比较长的 | 8.0em
85
+ 2 | 段落,用来看断行 | 8.0em
86
+ 3 | 在真实文本上的表 | 8.0em
87
+ 4 | 现。 | 2.0em
88
+
89
+ 4 line(s), box 8.0em, no diagnostics
90
+ ```
91
+
92
+ `> ` in the gutter means the line ends with a hanging mark, `<` means a
93
+ compressed one, `!` means it overflowed. `--json` gives you the same thing
94
+ as structured data, for scripts and agents.
95
+
96
+ ### As a library
97
+
98
+ ```python
99
+ from ziban import Style, layout, EmMetrics
100
+
101
+ page = layout(
102
+ "他说:「这次不一样。」然后关掉了对话框。",
103
+ width=10 * 32, # 10 characters wide
104
+ style=Style(size=32, leading=1.7),
105
+ metrics=EmMetrics(32), # or PillowMetrics("NotoSerifSC.otf", 32)
106
+ )
107
+
108
+ for line in page.lines:
109
+ print(line.text, line.width)
110
+
111
+ page.diagnostics
112
+ # [Diagnostic(code='ORPHAN_LINE', line=1, ...)]
113
+ ```
114
+
115
+ Every line carries its own explanation:
116
+
117
+ ```python
118
+ line.adjustments # (Adjustment(kind='hang', index=10, delta=32.0),)
119
+ line.slack # pixels left over
120
+ line.overflow # pixels past the box, 0 if it fits
121
+ line.justify_spacing
122
+ ```
123
+
124
+ ## What it does
125
+
126
+ | | |
127
+ |---|---|
128
+ | **避头尾 (kinsoku)** | closing marks never start a line, opening marks never end one |
129
+ | **标点挤压 (compression)** | a full-width mark is drawn at half width when that is what makes the line fit |
130
+ | **行末悬挂 (hanging)** | a mark may sit outside the margin rather than be pushed down — capped at one em *and* a quarter of the measure |
131
+ | **中西混排** | breaks between CJK and Latin, never inside a Latin word, numbers stay glued to their units |
132
+ | **硬断行** | `\n` is always honoured, blank lines survive |
133
+ | **诊断** | `OVERFLOW`, `NO_BREAK_OPPORTUNITY`, `KINSOKU_FALLBACK`, `ORPHAN_LINE` — with a suggested fix |
134
+ | **确定性** | same text + same font + same size → byte-identical output, on every platform |
135
+
136
+ ## What it does not do
137
+
138
+ - No AI. No API keys. No "generate my copy".
139
+ - No browser, no headless Chromium, no layout engine to install.
140
+ - No GUI editor.
141
+ - No auto-publishing to any platform.
142
+ - **No bundled fonts.** Microsoft YaHei and PingFang are not redistributable;
143
+ bring your own, or use Noto Sans SC / Noto Serif SC (OFL).
144
+
145
+ Being explicit about the last one is not pedantry: a library that ships a
146
+ proprietary font is a library companies cannot use.
147
+
148
+ ## Rules are data, not `if`-statements
149
+
150
+ The whole policy lives in [`src/ziban/data/clreq.toml`](src/ziban/data/clreq.toml):
151
+
152
+ ```toml
153
+ [kinsoku]
154
+ head_forbidden = " ,、。.;:?!)〕〉》」』】”’}…"
155
+ tail_forbidden = "(〔〈《「『【“‘{"
156
+
157
+ [compress]
158
+ compressed_ratio = 0.5
159
+ chars = ",、。.;:?!)〕〉》」』】”’…"
160
+
161
+ [hang]
162
+ max_ratio = 1.0
163
+ max_share = 0.25
164
+ ```
165
+
166
+ Change the file to change the behaviour. A Japanese ruleset is a second TOML,
167
+ not a second engine. This is also why contributing is cheap: most typographic
168
+ bugs are one line of data.
169
+
170
+ ## How it compares
171
+
172
+ | | kinsoku | compression | hanging | works outside a browser | deterministic |
173
+ |---|---|---|---|---|---|
174
+ | [heti](https://github.com/sivan/heti) | via browser | partial | ✗ | ✗ | ✗ |
175
+ | [pangu.py](https://github.com/vinta/pangu.py) | ✗ | ✗ | ✗ | ✓ | ✓ |
176
+ | [budoux](https://github.com/google/budoux) | ✗ | ✗ | ✗ | ✓ | ✓ |
177
+ | browser `text-spacing-trim` | ✓ | ✓ | partial | ✗ | ✓ |
178
+ | **ziban** | ✓ | ✓ | ✓ | ✓ | ✓ |
179
+
180
+ `budoux` decides *where* a break is semantically acceptable; `ziban` decides
181
+ whether the break is typographically legal. They compose.
182
+
183
+ ## Status
184
+
185
+ `0.1.0` — engine complete for horizontal text, 958 tests green
186
+ (144 corpus × width combinations (24 paragraphs × 6 measures) checked against six invariants, plus golden
187
+ cases). Not yet on PyPI.
188
+
189
+ Next:
190
+
191
+ - **0.2** font probing across Windows/macOS/Linux, pair compression (「。」 in
192
+ one em), a Knuth–Plass-style cost function so the last line stops being an
193
+ orphan
194
+ - **0.3** adapters: ReportLab, SVG, `python-pptx`; vertical text behind a flag
195
+ - **0.4** a `cards` example that renders real 1080×1440 image sets, and a
196
+ `SKILL.md` wrapper so agents can call it
197
+
198
+ ## License
199
+
200
+ MIT. See [LICENSE](LICENSE).
201
+
202
+ The ruleset follows [CLREQ](https://www.w3.org/TR/clreq/) (W3C). Character
203
+ classes follow a curated subset of [UAX #14](https://www.unicode.org/reports/tr14/).
ziban-0.1.0/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # ziban
2
+
3
+ **Correct CJK line breaking for everything that is not a browser.**
4
+
5
+ [中文说明 →](README.zh-CN.md)
6
+
7
+ Browsers got `text-spacing-trim`, `line-break: strict` and proper kinsoku years
8
+ ago. Everything else — Pillow, ReportLab, SVG, ffmpeg subtitles, `python-pptx`,
9
+ canvas — still reimplements it badly, because there is nothing to ask.
10
+
11
+ `ziban` is that thing: a pure-Python typesetting engine that turns a string and
12
+ a box into lines a Chinese typesetter would accept — and tells you, in machine
13
+ readable form, every time it had to compromise.
14
+
15
+ ![Same text, same column: naive chunking on the left, ziban on the right](docs/compare.png)
16
+
17
+ ## The problem, in eight seconds
18
+
19
+ Naive `if len(s) > N: cut()`:
20
+
21
+ ```
22
+ 他说:「这次不一样。
23
+ 」然后关掉了对话框。
24
+ ```
25
+
26
+ `ziban`:
27
+
28
+ ```
29
+ 他说:「这次不一样。」
30
+ 然后关掉了对话框。
31
+ ```
32
+
33
+ The first version starts a line with `」`. That is not a matter of taste:
34
+ [《中文排版需求》(CLREQ)](https://www.w3.org/TR/clreq/) forbids closing
35
+ punctuation at line start (避头) and opening punctuation at line end (避尾).
36
+ Every renderer that is not a browser quietly breaks that rule — and the
37
+ punctuation it breaks is exactly the punctuation readers notice.
38
+
39
+ The image above is generated, not drawn by hand:
40
+ `python examples/compare.py --font <path-to-a-CJK-font>`. Re-run it after any
41
+ change to the rules and you will see immediately whether the typography got
42
+ better or worse.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install ziban # core, zero dependencies
48
+ pip install "ziban[pillow]" # + measuring with real font files
49
+ ```
50
+
51
+ Python 3.9+. No browser, no native build, no ICU, no model.
52
+
53
+ ## Use it
54
+
55
+ ### As a CLI
56
+
57
+ ```console
58
+ $ echo "这是一个比较长的段落,用来看断行在真实文本上的表现。" \
59
+ | ziban wrap - --width-em 8 --ruler
60
+ +----------------+
61
+ 1 | 这是一个比较长的 | 8.0em
62
+ 2 | 段落,用来看断行 | 8.0em
63
+ 3 | 在真实文本上的表 | 8.0em
64
+ 4 | 现。 | 2.0em
65
+
66
+ 4 line(s), box 8.0em, no diagnostics
67
+ ```
68
+
69
+ `> ` in the gutter means the line ends with a hanging mark, `<` means a
70
+ compressed one, `!` means it overflowed. `--json` gives you the same thing
71
+ as structured data, for scripts and agents.
72
+
73
+ ### As a library
74
+
75
+ ```python
76
+ from ziban import Style, layout, EmMetrics
77
+
78
+ page = layout(
79
+ "他说:「这次不一样。」然后关掉了对话框。",
80
+ width=10 * 32, # 10 characters wide
81
+ style=Style(size=32, leading=1.7),
82
+ metrics=EmMetrics(32), # or PillowMetrics("NotoSerifSC.otf", 32)
83
+ )
84
+
85
+ for line in page.lines:
86
+ print(line.text, line.width)
87
+
88
+ page.diagnostics
89
+ # [Diagnostic(code='ORPHAN_LINE', line=1, ...)]
90
+ ```
91
+
92
+ Every line carries its own explanation:
93
+
94
+ ```python
95
+ line.adjustments # (Adjustment(kind='hang', index=10, delta=32.0),)
96
+ line.slack # pixels left over
97
+ line.overflow # pixels past the box, 0 if it fits
98
+ line.justify_spacing
99
+ ```
100
+
101
+ ## What it does
102
+
103
+ | | |
104
+ |---|---|
105
+ | **避头尾 (kinsoku)** | closing marks never start a line, opening marks never end one |
106
+ | **标点挤压 (compression)** | a full-width mark is drawn at half width when that is what makes the line fit |
107
+ | **行末悬挂 (hanging)** | a mark may sit outside the margin rather than be pushed down — capped at one em *and* a quarter of the measure |
108
+ | **中西混排** | breaks between CJK and Latin, never inside a Latin word, numbers stay glued to their units |
109
+ | **硬断行** | `\n` is always honoured, blank lines survive |
110
+ | **诊断** | `OVERFLOW`, `NO_BREAK_OPPORTUNITY`, `KINSOKU_FALLBACK`, `ORPHAN_LINE` — with a suggested fix |
111
+ | **确定性** | same text + same font + same size → byte-identical output, on every platform |
112
+
113
+ ## What it does not do
114
+
115
+ - No AI. No API keys. No "generate my copy".
116
+ - No browser, no headless Chromium, no layout engine to install.
117
+ - No GUI editor.
118
+ - No auto-publishing to any platform.
119
+ - **No bundled fonts.** Microsoft YaHei and PingFang are not redistributable;
120
+ bring your own, or use Noto Sans SC / Noto Serif SC (OFL).
121
+
122
+ Being explicit about the last one is not pedantry: a library that ships a
123
+ proprietary font is a library companies cannot use.
124
+
125
+ ## Rules are data, not `if`-statements
126
+
127
+ The whole policy lives in [`src/ziban/data/clreq.toml`](src/ziban/data/clreq.toml):
128
+
129
+ ```toml
130
+ [kinsoku]
131
+ head_forbidden = " ,、。.;:?!)〕〉》」』】”’}…"
132
+ tail_forbidden = "(〔〈《「『【“‘{"
133
+
134
+ [compress]
135
+ compressed_ratio = 0.5
136
+ chars = ",、。.;:?!)〕〉》」』】”’…"
137
+
138
+ [hang]
139
+ max_ratio = 1.0
140
+ max_share = 0.25
141
+ ```
142
+
143
+ Change the file to change the behaviour. A Japanese ruleset is a second TOML,
144
+ not a second engine. This is also why contributing is cheap: most typographic
145
+ bugs are one line of data.
146
+
147
+ ## How it compares
148
+
149
+ | | kinsoku | compression | hanging | works outside a browser | deterministic |
150
+ |---|---|---|---|---|---|
151
+ | [heti](https://github.com/sivan/heti) | via browser | partial | ✗ | ✗ | ✗ |
152
+ | [pangu.py](https://github.com/vinta/pangu.py) | ✗ | ✗ | ✗ | ✓ | ✓ |
153
+ | [budoux](https://github.com/google/budoux) | ✗ | ✗ | ✗ | ✓ | ✓ |
154
+ | browser `text-spacing-trim` | ✓ | ✓ | partial | ✗ | ✓ |
155
+ | **ziban** | ✓ | ✓ | ✓ | ✓ | ✓ |
156
+
157
+ `budoux` decides *where* a break is semantically acceptable; `ziban` decides
158
+ whether the break is typographically legal. They compose.
159
+
160
+ ## Status
161
+
162
+ `0.1.0` — engine complete for horizontal text, 958 tests green
163
+ (144 corpus × width combinations (24 paragraphs × 6 measures) checked against six invariants, plus golden
164
+ cases). Not yet on PyPI.
165
+
166
+ Next:
167
+
168
+ - **0.2** font probing across Windows/macOS/Linux, pair compression (「。」 in
169
+ one em), a Knuth–Plass-style cost function so the last line stops being an
170
+ orphan
171
+ - **0.3** adapters: ReportLab, SVG, `python-pptx`; vertical text behind a flag
172
+ - **0.4** a `cards` example that renders real 1080×1440 image sets, and a
173
+ `SKILL.md` wrapper so agents can call it
174
+
175
+ ## License
176
+
177
+ MIT. See [LICENSE](LICENSE).
178
+
179
+ The ruleset follows [CLREQ](https://www.w3.org/TR/clreq/) (W3C). Character
180
+ classes follow a curated subset of [UAX #14](https://www.unicode.org/reports/tr14/).
@@ -0,0 +1,164 @@
1
+ # ziban
2
+
3
+ **给"浏览器之外"的一切,做对中文断行。**
4
+
5
+ [English →](README.md)
6
+
7
+ 浏览器很多年前就有了 `text-spacing-trim`、`line-break: strict` 和真正的禁则处理。
8
+ 但除了浏览器,别的地方全都得自己重写一遍——Pillow、ReportLab、SVG、ffmpeg 烧字幕、
9
+ `python-pptx`、canvas——而且几乎都写错了,因为**没有一个地方可以去问**。
10
+
11
+ `ziban` 就是那个地方:一个纯 Python 的中文排版引擎,把「一段文字 + 一个盒子」
12
+ 变成排版师认可的若干行,并且在每次不得不妥协时,给出机器可读的说明。
13
+
14
+ ![同一段文字、同一个栏宽:左边按字数硬切,右边是 ziban](docs/compare.png)
15
+
16
+ ## 八秒钟看懂问题
17
+
18
+ `if len(s) > N: cut()` 的结果:
19
+
20
+ ```
21
+ 他说:「这次不一样。
22
+ 」然后关掉了对话框。
23
+ ```
24
+
25
+ `ziban`:
26
+
27
+ ```
28
+ 他说:「这次不一样。」
29
+ 然后关掉了对话框。
30
+ ```
31
+
32
+ 第一行以 `」` 开头。这不是审美问题:[《中文排版需求》(CLREQ)](https://www.w3.org/TR/clreq/)
33
+ 明确规定收尾标点不可出现在行首(避头)、起首标点不可出现在行尾(避尾)。
34
+ 所有非浏览器的渲染器都在默默违反这条规则——而它违反的,恰恰是读者一眼就能看见的那些标点。
35
+
36
+ ## 安装
37
+
38
+ ```bash
39
+ pip install ziban # 核心,零依赖
40
+ pip install "ziban[pillow]" # 需要用真实字体度量时
41
+ ```
42
+
43
+ Python 3.9+。不要浏览器、不要编译、不要 ICU、不要模型。
44
+
45
+ ## 用法
46
+
47
+ ### 命令行
48
+
49
+ ```console
50
+ $ echo "这是一个比较长的段落,用来看断行在真实文本上的表现。" \
51
+ | ziban wrap - --width-em 8 --ruler
52
+ +----------------+
53
+ 1 | 这是一个比较长的 | 8.0em
54
+ 2 | 段落,用来看断行 | 8.0em
55
+ 3 | 在真实文本上的表 | 8.0em
56
+ 4 | 现。 | 2.0em
57
+
58
+ 4 line(s), box 8.0em, no diagnostics
59
+ ```
60
+
61
+ 行号后面那一列:`>` 表示本行末尾有标点悬挂,`<` 表示有标点挤压,`!` 表示溢出。
62
+ 加 `--json` 得到结构化结果,给脚本和 agent 用。
63
+
64
+ ### 库
65
+
66
+ ```python
67
+ from ziban import Style, layout, EmMetrics
68
+
69
+ page = layout(
70
+ "他说:「这次不一样。」然后关掉了对话框。",
71
+ width=10 * 32, # 一行 10 个字
72
+ style=Style(size=32, leading=1.7),
73
+ metrics=EmMetrics(32), # 或 PillowMetrics("NotoSerifSC.otf", 32)
74
+ )
75
+
76
+ for line in page.lines:
77
+ print(line.text, line.width)
78
+
79
+ page.diagnostics
80
+ # [Diagnostic(code='ORPHAN_LINE', line=1, ...)]
81
+ ```
82
+
83
+ 每一行都带着自己的解释:
84
+
85
+ ```python
86
+ line.adjustments # (Adjustment(kind='hang', index=10, delta=32.0),)
87
+ line.slack # 剩余空间
88
+ line.overflow # 超出盒子的像素,放得下就是 0
89
+ line.justify_spacing
90
+ ```
91
+
92
+ ## 它做了什么
93
+
94
+ | | |
95
+ |---|---|
96
+ | **避头尾** | 收尾标点不进行首,起首标点不留行尾 |
97
+ | **标点挤压** | 差一点点放得下时,全角标点压到半宽 |
98
+ | **行末悬挂** | 宁可让标点悬出边界,也不把它挤到下一行;上限 1 em,且不超过行宽的 1/4 |
99
+ | **中西混排** | 汉字与拉丁文之间可断,拉丁单词内部不断,数字不与单位分家 |
100
+ | **硬断行** | `\n` 一定生效,空行保留 |
101
+ | **诊断** | `OVERFLOW`、`NO_BREAK_OPPORTUNITY`、`KINSOKU_FALLBACK`、`ORPHAN_LINE`,每条都带修复建议 |
102
+ | **确定性** | 同文本 + 同字体 + 同字号 → 逐字节相同的输出,跨平台一致 |
103
+
104
+ ## 它不做什么
105
+
106
+ - 不调 AI,不要 API key,不做"帮我写文案"。
107
+ - 不要浏览器、不要无头 Chromium、不要装排版引擎。
108
+ - 不做 GUI 编辑器。
109
+ - 不自动发布到任何平台。
110
+ - **不打包任何字体。** 微软雅黑、苹方都不能随包分发;要么自带,要么用思源黑体/Noto(OFL)。
111
+
112
+ 最后一条不是洁癖:一个内置专有字体的库,公司是不能用的。
113
+
114
+ ## 规则是数据,不是 if-else
115
+
116
+ 所有排版策略都在 [`src/ziban/data/clreq.toml`](src/ziban/data/clreq.toml) 里:
117
+
118
+ ```toml
119
+ [kinsoku]
120
+ head_forbidden = " ,、。.;:?!)〕〉》」』】”’}…"
121
+ tail_forbidden = "(〔〈《「『【“‘{"
122
+
123
+ [compress]
124
+ compressed_ratio = 0.5
125
+ chars = ",、。.;:?!)〕〉》」』】”’…"
126
+
127
+ [hang]
128
+ max_ratio = 1.0
129
+ max_share = 0.25
130
+ ```
131
+
132
+ 改行为就改这张表。日文规则集是第二个 TOML,不是第二个引擎。这也是为什么贡献很便宜:
133
+ **绝大多数排版 bug 只值一行数据。**
134
+
135
+ ## 和它们比
136
+
137
+ | | 禁则 | 挤压 | 悬挂 | 浏览器之外可用 | 确定性 |
138
+ |---|---|---|---|---|---|
139
+ | [heti](https://github.com/sivan/heti) | 靠浏览器 | 部分 | ✗ | ✗ | ✗ |
140
+ | [pangu.py](https://github.com/vinta/pangu.py) | ✗ | ✗ | ✗ | ✓ | ✓ |
141
+ | [budoux](https://github.com/google/budoux) | ✗ | ✗ | ✗ | ✓ | ✓ |
142
+ | 浏览器 `text-spacing-trim` | ✓ | ✓ | 部分 | ✗ | ✓ |
143
+ | **ziban** | ✓ | ✓ | ✓ | ✓ | ✓ |
144
+
145
+ `budoux` 决定"断在哪里语义上说得过去",`ziban` 决定"这个断点排版上合不合法"。两者可以叠加。
146
+
147
+ ## 状态
148
+
149
+ `0.1.0` —— 横排引擎完成,958 个测试全绿(24 段语料 × 6 种行宽 × 6 条不变量,
150
+ 外加金标用例)。尚未发布到 PyPI。
151
+
152
+ 接下来:
153
+
154
+ - **0.2** 跨平台字体探测;连续标点挤压(「。」占一个 em);Knuth–Plass 式代价断行,
155
+ 让最后一行不再孤零零
156
+ - **0.3** 适配器:ReportLab、SVG、`python-pptx`;竖排放到开关后面
157
+ - **0.4** `cards` 示例:真正渲染 1080×1440 组图;再包一层 `SKILL.md` 让 agent 能调用
158
+
159
+ ## 许可
160
+
161
+ MIT,见 [LICENSE](LICENSE)。
162
+
163
+ 规则集依照 [CLREQ](https://www.w3.org/TR/clreq/)(W3C);字符分类是
164
+ [UAX #14](https://www.unicode.org/reports/tr14/) 的裁剪子集。
Binary file