text-compositor 0.4.2__tar.gz → 0.4.3__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.
- {text_compositor-0.4.2 → text_compositor-0.4.3}/PKG-INFO +11 -1
- {text_compositor-0.4.2 → text_compositor-0.4.3}/README.md +10 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/pyproject.toml +1 -1
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_html_output.py +6 -1
- text_compositor-0.4.3/tests/test_pdf_fonts.py +100 -0
- text_compositor-0.4.3/tests/test_pdf_metadata.py +81 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_worker.py +1 -1
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/html_output.py +11 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/templates/_common.typ +9 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/templates/paper.typ +9 -1
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/templates/slide.typ +9 -1
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/templates/template.typ +9 -1
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/worker.py +1 -1
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor.egg-info/PKG-INFO +11 -1
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor.egg-info/SOURCES.txt +2 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/LICENSE +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/setup.cfg +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_aggregate_table.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_api.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_asset_resolution.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_bundled_diagram_tools.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_bundled_typst_assets.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_cetz_render.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_check_pins.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_cli_single_markdown.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_common_template.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_config.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_config_key_validation.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_csv_table.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_diagnostics.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_diagram_cache_key.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_diagram_trim.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_distribution.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_doc_badges.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_download.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_emphasis_lint.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_front_matter.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_graphviz_render.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_html_line_numbers.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_incremental.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_java_encoding.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_layout_blocks.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_line_mapping.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_markdown_basics.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_math_render.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_mermaid_resident.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_pandoc_compat.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_paper_template.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_pikchr_render.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_plantuml_batch.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_renderer_structure.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_revision_history.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_script_references.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_sections.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_soft_break_hints.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_structurizr_render.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_svg_trim.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_table_cell_style.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_variables.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_vega_render.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_viewer_version.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/tests/test_watch.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/__init__.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/api.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/build.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/cetz_render.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/changes.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/chapters.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/compiler.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/config.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/deps.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/diagnostics.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/document.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/emphasis_lint.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/env_check.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/graphviz_render.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/host_renderers.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/log.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/math_render.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/mermaid.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/pikchr_render.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/project.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/renderer.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/renderer_diagrams.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/renderer_inline.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/renderer_layout.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/renderer_tables.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/renderer_tokens.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/svg_trim.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/typst_literal.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/typst_runtime.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor/vega_render.py +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor.egg-info/dependency_links.txt +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor.egg-info/entry_points.txt +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor.egg-info/requires.txt +0 -0
- {text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: text-compositor
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: A tool for composing multiple text files into a PDF document.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/tokudiro/text-compositor
|
|
@@ -192,6 +192,16 @@ This generates `sample/SampleDocument.pdf`. Two more samples show other template
|
|
|
192
192
|
|
|
193
193
|
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.
|
|
194
194
|
|
|
195
|
+
## Policy for adding diagram notations
|
|
196
|
+
|
|
197
|
+
This tool is free for commercial use, runs locally, and puts no restrictions on the people who use it (see chapter 2 of [doc/spec.md](doc/spec.md)). A diagram tool or notation is therefore **not** added when it has one of these:
|
|
198
|
+
|
|
199
|
+
- **A strong license constraint.** Strong copyleft such as GPL or AGPL, a watermark or credit that must stay in the output, a fee, or a usage limit.
|
|
200
|
+
- **A heavy runtime.** It needs Docker or a similar container, or a large runtime that most users do not have.
|
|
201
|
+
- **An external service.** It sends the manuscript to a server.
|
|
202
|
+
|
|
203
|
+
Candidates that fail these checks are closed as "not planned" on [GitHub Issues](https://github.com/tokudiro/text-compositor/issues), with the reason recorded.
|
|
204
|
+
|
|
195
205
|
## License
|
|
196
206
|
|
|
197
207
|
[MIT License](LICENSE)
|
|
@@ -174,6 +174,16 @@ This generates `sample/SampleDocument.pdf`. Two more samples show other template
|
|
|
174
174
|
|
|
175
175
|
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.
|
|
176
176
|
|
|
177
|
+
## Policy for adding diagram notations
|
|
178
|
+
|
|
179
|
+
This tool is free for commercial use, runs locally, and puts no restrictions on the people who use it (see chapter 2 of [doc/spec.md](doc/spec.md)). A diagram tool or notation is therefore **not** added when it has one of these:
|
|
180
|
+
|
|
181
|
+
- **A strong license constraint.** Strong copyleft such as GPL or AGPL, a watermark or credit that must stay in the output, a fee, or a usage limit.
|
|
182
|
+
- **A heavy runtime.** It needs Docker or a similar container, or a large runtime that most users do not have.
|
|
183
|
+
- **An external service.** It sends the manuscript to a server.
|
|
184
|
+
|
|
185
|
+
Candidates that fail these checks are closed as "not planned" on [GitHub Issues](https://github.com/tokudiro/text-compositor/issues), with the reason recorded.
|
|
186
|
+
|
|
177
187
|
## License
|
|
178
188
|
|
|
179
189
|
[MIT License](LICENSE)
|
|
@@ -69,6 +69,11 @@ def messages(result, severity):
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
class TestApi:
|
|
72
|
+
def test_headings_get_github_style_ids_for_in_document_links(self, tmp_path):
|
|
73
|
+
# 文書内リンク[…](#見出し)の飛び先(#361)。重複には-1・-2を付ける
|
|
74
|
+
_, html = convert(tmp_path, "# Hello, World!\n\n## 日本語 見出し\n\n## Hello, World!\n")
|
|
75
|
+
assert 'id="hello-world"' in html and 'id="日本語-見出し"' in html and 'id="hello-world-1"' in html
|
|
76
|
+
|
|
72
77
|
def test_writes_a_standalone_document_next_to_the_markdown(self, tmp_path):
|
|
73
78
|
result, html = convert(tmp_path, "# 見出し\n\n本文。\n")
|
|
74
79
|
assert isinstance(result, HtmlResult)
|
|
@@ -76,7 +81,7 @@ class TestApi:
|
|
|
76
81
|
assert result.html_path == str(tmp_path / ".text-compositor" / "preview.html")
|
|
77
82
|
assert html.startswith("<!DOCTYPE html>")
|
|
78
83
|
assert "<title>見出し</title>" in html
|
|
79
|
-
assert '<h1 data-line="1">見出し</h1>' in html
|
|
84
|
+
assert '<h1 data-line="1" id="見出し">見出し</h1>' in html
|
|
80
85
|
assert "<script" not in html
|
|
81
86
|
# スクリプトは、ブラウザ側でも禁止する(Viewerは、JavaScriptを有効にしたビューで開く)
|
|
82
87
|
assert "script-src 'none'" in html and "object-src 'none'" in html
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""PDFのフォント埋め込みの検証(#320)。
|
|
2
|
+
|
|
3
|
+
生成したPDFの、すべてのフォントが、PDFの中に埋め込まれている(サブセット化されている)ことを確かめる。
|
|
4
|
+
埋め込まれていないフォントがあると、閲覧する環境に、そのフォントが無いとき、見た目が変わる(文字化け・字体の置き換え)。
|
|
5
|
+
|
|
6
|
+
TypstのPDFは、フォントの辞書を、平文(圧縮しない)で書くため、追加のツール(pdffonts等)なしで、読める。
|
|
7
|
+
フォントは、システムのものを使わず(`ignore_system_fonts=True`、#71)、同梱のNoto Sans JPと、Typst内蔵のフォントだけを使う。
|
|
8
|
+
"""
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
import text_compositor.build as build_mod
|
|
15
|
+
from text_compositor import log
|
|
16
|
+
|
|
17
|
+
@pytest.fixture(autouse=True)
|
|
18
|
+
def _restore_verbosity():
|
|
19
|
+
"""`build()`は、-q/-vを、プロセスグローバルへ反映する。他のテストへ、残さない(残すと、後の警告の検査が落ちる)。"""
|
|
20
|
+
yield
|
|
21
|
+
log.set_verbosity(False, False)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# 本文・見出し・太字・斜体・コード(等幅)・数式・表・Graphviz・SVGの文字を含む、1つの文書。
|
|
25
|
+
# Mermaid・PlantUML・D2は、ブラウザ・Java・外部の実行ファイルが要るため、含めない(PDFに入る文字は、同じ経路のため)。
|
|
26
|
+
DOCUMENT = """# フォント埋め込みの検証
|
|
27
|
+
|
|
28
|
+
## 本文と装飾
|
|
29
|
+
|
|
30
|
+
通常の本文です。**太字**、*斜体*、***太字の斜体***、`インラインコード(日本語)`。
|
|
31
|
+
|
|
32
|
+
記号: → ✓ ★ ① ≠ ∞ ± × ÷。ラテン: café。ギリシャ・キリル: αβγ Привет。
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# 日本語のコメント
|
|
36
|
+
def greet(name):
|
|
37
|
+
return f"こんにちは、{name}"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
数式: $E = mc^2$
|
|
41
|
+
|
|
42
|
+
| 名前 | 説明 |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| りんご | 赤い果物 |
|
|
45
|
+
|
|
46
|
+
```dot
|
|
47
|
+
digraph G { 開始 -> 処理 -> 終了; }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```svg
|
|
51
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="240" height="60"><text x="10" y="35" font-family="Arial" font-size="20">SVG text 日本語</text></svg>
|
|
52
|
+
```
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
# フォントの辞書: `N 0 obj <</Type/FontDescriptor/FontName/ABCDEF+名前/.../FontFile3 N 0 R>>`
|
|
56
|
+
DESCRIPTOR = re.compile(rb"<<(?:(?!>>).)*?/Type\s*/FontDescriptor(?:(?!>>).)*?>>", re.S)
|
|
57
|
+
FONT_NAME = re.compile(rb"/FontName\s*/([^/\s>]+)")
|
|
58
|
+
FONT_FILE = re.compile(rb"/FontFile[23]?\s+\d+\s+\d+\s+R")
|
|
59
|
+
SUBSET_TAG = re.compile(rb"^[A-Z]{6}\+")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def font_descriptors(pdf: bytes):
|
|
63
|
+
"""PDFの、フォントの辞書ごとに、(フォント名, 埋め込まれているか)を返す。"""
|
|
64
|
+
result = []
|
|
65
|
+
for match in DESCRIPTOR.finditer(pdf):
|
|
66
|
+
body = match.group(0)
|
|
67
|
+
name = FONT_NAME.search(body)
|
|
68
|
+
result.append((name.group(1) if name else b"(no name)", FONT_FILE.search(body) is not None))
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_font_descriptors_reads_the_embedded_flag():
|
|
73
|
+
"""読み取りの部品そのもの: 埋め込みのあるものと、無いもの(標準14フォントの参照など)を、区別できる。"""
|
|
74
|
+
embedded = b"5 0 obj\n<</Type/FontDescriptor/FontName/ABCDEF+NotoSansJP-Regular/Flags 4/FontFile3 9 0 R>>\nendobj\n"
|
|
75
|
+
missing = b"6 0 obj\n<</Type/FontDescriptor/FontName/Helvetica/Flags 32>>\nendobj\n"
|
|
76
|
+
other = b"7 0 obj\n<</Type/Font/Subtype/Type0/BaseFont/ABCDEF+X>>\nendobj\n"
|
|
77
|
+
assert font_descriptors(embedded + missing + other) == [(b"ABCDEF+NotoSansJP-Regular", True), (b"Helvetica", False)]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_every_font_in_a_real_pdf_is_embedded_and_subset(tmp_path, monkeypatch):
|
|
81
|
+
from text_compositor.deps import ensure_fonts
|
|
82
|
+
try:
|
|
83
|
+
ensure_fonts()
|
|
84
|
+
except SystemExit:
|
|
85
|
+
pytest.skip("fonts are not available")
|
|
86
|
+
md = tmp_path / "fonts.md"
|
|
87
|
+
md.write_text(DOCUMENT, encoding="utf-8")
|
|
88
|
+
monkeypatch.setattr(sys, "argv", ["text-compositor", str(md), "-q"])
|
|
89
|
+
build_mod.build()
|
|
90
|
+
pdf = (tmp_path / "fonts.pdf").read_bytes()
|
|
91
|
+
|
|
92
|
+
fonts = font_descriptors(pdf)
|
|
93
|
+
assert fonts, "no font is found in the PDF"
|
|
94
|
+
names = [name.decode("latin1") for name, _ in fonts]
|
|
95
|
+
not_embedded = [name for name, embedded in fonts if not embedded]
|
|
96
|
+
assert not not_embedded, f"fonts that are not embedded: {not_embedded} (all: {names})"
|
|
97
|
+
not_subset = [name for name, _ in fonts if not SUBSET_TAG.match(name)]
|
|
98
|
+
assert not not_subset, f"fonts that are not subset (no ABCDEF+ tag): {not_subset} (all: {names})"
|
|
99
|
+
# 日本語の本文・見出しは、同梱のNoto Sans JPで出る(システムのフォントに、置き換わらない)
|
|
100
|
+
assert any(b"NotoSansJP" in name for name, _ in fonts), names
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""PDFのメタデータ(プロパティ)の検証(#317)。
|
|
2
|
+
|
|
3
|
+
`document:`のtitle・subtitle・author・dateが、PDFのTitle・Subject・Author・作成日に入ることを、3つのテンプレート
|
|
4
|
+
(template・paper・slide)で確かめる。TypstのPDFは、情報の辞書を平文で書くため、追加のツールなしで読める
|
|
5
|
+
(フォントの検証(test_pdf_fonts.py)と同じ方法)。
|
|
6
|
+
"""
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
import text_compositor.build as build_mod
|
|
13
|
+
from text_compositor import log
|
|
14
|
+
|
|
15
|
+
TEMPLATES = ["template", "paper", "slide"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture(autouse=True)
|
|
19
|
+
def _restore_verbosity():
|
|
20
|
+
"""`build()`は、-q/-vを、プロセスグローバルへ反映する。他のテストへ、残さない。"""
|
|
21
|
+
yield
|
|
22
|
+
log.set_verbosity(False, False)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def pdf_info(pdf: bytes) -> dict:
|
|
26
|
+
"""PDFの情報の辞書(`/Creator(Typst ...)`を持つ辞書)から、Title・Author・Subject・CreationDateを読む。
|
|
27
|
+
文字列は、UTF-16BEの16進(`<FEFF...>`)か、括弧で囲んだ文字列。ブックマークの`/Title`とは、別の辞書のため、混ざらない。"""
|
|
28
|
+
match = re.search(rb"<<(?:(?!>>).)*?/Creator\(Typst(?:(?!>>).)*?>>", pdf, re.S)
|
|
29
|
+
assert match, "the document information dictionary is not found"
|
|
30
|
+
body = match.group(0)
|
|
31
|
+
info = {}
|
|
32
|
+
for key in (b"Title", b"Author", b"Subject", b"CreationDate"):
|
|
33
|
+
found = re.search(rb"/" + key + rb"(?:<FEFF([0-9A-Fa-f]*)>|\(([^)]*)\))", body)
|
|
34
|
+
if found:
|
|
35
|
+
info[key.decode()] = (bytes.fromhex(found.group(1).decode()).decode("utf-16-be") if found.group(1) is not None
|
|
36
|
+
else found.group(2).decode("latin1"))
|
|
37
|
+
return info
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def build_pdf(tmp_path, monkeypatch, template, document):
|
|
41
|
+
from text_compositor.deps import ensure_fonts
|
|
42
|
+
try:
|
|
43
|
+
ensure_fonts()
|
|
44
|
+
except SystemExit:
|
|
45
|
+
pytest.skip("fonts are not available")
|
|
46
|
+
(tmp_path / "a.md").write_text("# 本文\n\nこんにちは。\n", encoding="utf-8")
|
|
47
|
+
lines = ["document:"] + [f' {key}: "{value}"' for key, value in document.items()]
|
|
48
|
+
lines += ["output:", ' filename: "out.pdf"', ' dir: "."', "template:", f' path: "{template}"',
|
|
49
|
+
"inputs:", ' dir: "."', "chapters:", ' - "a.md"']
|
|
50
|
+
config = tmp_path / "config.yaml"
|
|
51
|
+
config.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
52
|
+
monkeypatch.setattr(sys, "argv", ["text-compositor", "--config", str(config), "-q"])
|
|
53
|
+
build_mod.build()
|
|
54
|
+
return (tmp_path / "out.pdf").read_bytes()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_pdf_info_reads_hex_and_literal_strings():
|
|
58
|
+
"""読み取りの部品そのもの: UTF-16の16進の文字列と、括弧の文字列を読み、ブックマークの/Titleは、読まない。"""
|
|
59
|
+
pdf = (b"2 0 obj\n<</Parent 3 0 R/Title<FEFF672C6587>/Dest 9 0 R>>\nendobj\n"
|
|
60
|
+
b"20 0 obj\n<</Title(Plain)/Author<FEFF5C717530>/Creator(Typst 0.15.0)/CreationDate(D:20260814000000Z)>>\nendobj\n")
|
|
61
|
+
assert pdf_info(pdf) == {"Title": "Plain", "Author": "山田", "CreationDate": "D:20260814000000Z"}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.parametrize("template", TEMPLATES)
|
|
65
|
+
def test_document_values_are_written_to_the_pdf_properties(tmp_path, monkeypatch, template):
|
|
66
|
+
pdf = build_pdf(tmp_path, monkeypatch, template,
|
|
67
|
+
{"title": "テスト文書", "subtitle": "副題です", "author": "山田 太郎", "date": "2026-08-14"})
|
|
68
|
+
info = pdf_info(pdf)
|
|
69
|
+
assert info["Title"] == "テスト文書"
|
|
70
|
+
assert info["Subject"] == "副題です"
|
|
71
|
+
assert info["Author"] == "山田 太郎"
|
|
72
|
+
assert info["CreationDate"].startswith("D:20260814"), info
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_a_free_form_date_keeps_the_build_time(tmp_path, monkeypatch):
|
|
76
|
+
"""dateが、YYYY-MM-DDでない(自由な文字列。例: 「2026年8月版」)とき、作成日は、従来どおり、ビルドの日時になる(表紙には、文字列のまま出る)。
|
|
77
|
+
title・author・subtitleを書かないときは、設定の既定値(config.pyのDEFAULT_CONFIG。表紙と同じ値)が、そのまま入る。"""
|
|
78
|
+
pdf = build_pdf(tmp_path, monkeypatch, "template", {"date": "2026年8月版"})
|
|
79
|
+
info = pdf_info(pdf)
|
|
80
|
+
assert "CreationDate" in info and not info["CreationDate"].startswith("D:20260814"), info
|
|
81
|
+
assert info["Title"] == "System_Specification", info
|
|
@@ -279,7 +279,7 @@ class TestProcess:
|
|
|
279
279
|
"path": str(md), "output": str(out), "plugins": plugins}})
|
|
280
280
|
response = self.receive(proc)
|
|
281
281
|
assert response["ok"] is True, response
|
|
282
|
-
assert response["html"] == str(out) and '<h1 data-line="1">見出し</h1>' in out.read_text(encoding="utf-8")
|
|
282
|
+
assert response["html"] == str(out) and '<h1 data-line="1" id="見出し">見出し</h1>' in out.read_text(encoding="utf-8")
|
|
283
283
|
# 失敗(存在しないファイル)でも、ワーカーは、同じまま動き続ける
|
|
284
284
|
self.send(proc, {"id": 2, "method": "render_html", "params": {"path": str(tmp_path / "none.md")}})
|
|
285
285
|
failed = self.receive(proc)
|
|
@@ -193,6 +193,8 @@ class _TokenRenderer(RendererHTML):
|
|
|
193
193
|
def heading_open(self, tokens, idx, options, env):
|
|
194
194
|
if self.owner._title is None and idx + 1 < len(tokens) and tokens[idx + 1].type == "inline":
|
|
195
195
|
self.owner._title = tokens[idx + 1].content
|
|
196
|
+
if idx + 1 < len(tokens) and tokens[idx + 1].type == "inline":
|
|
197
|
+
tokens[idx].attrSet("id", self.owner._heading_id(tokens[idx + 1].content))
|
|
196
198
|
return self.renderToken(tokens, idx, options, env)
|
|
197
199
|
|
|
198
200
|
def blockquote_open(self, tokens, idx, options, env):
|
|
@@ -274,6 +276,14 @@ class HtmlRenderer(TypstRenderer):
|
|
|
274
276
|
# 使う(#170)。存在しないファイルも含める(あとから作られたときに、更新できるように)。
|
|
275
277
|
self.dependencies: set = set()
|
|
276
278
|
|
|
279
|
+
def _heading_id(self, text: str) -> str:
|
|
280
|
+
"""見出しの`id`(文書内リンク`#見出し`の飛び先)。GitHubと同じ規則: 小文字にし、文字・数字・`_`・`-`・空白だけを残し、
|
|
281
|
+
空白を`-`にする。同じ見出しが続くときは、`-1`・`-2`を付ける。Obunzuで、`[…](#見出し)`が飛べるようにするため(#361)。"""
|
|
282
|
+
base = re.sub(r"\s", "-", re.sub(r"[^\w\s-]", "", text.strip().lower())) or "section"
|
|
283
|
+
count = self._heading_ids.get(base, 0)
|
|
284
|
+
self._heading_ids[base] = count + 1
|
|
285
|
+
return base if count == 0 else f"{base}-{count}"
|
|
286
|
+
|
|
277
287
|
def _abs_line(self, token) -> Optional[int]:
|
|
278
288
|
"""トークンの、原稿での行番号(1始まり)。行を持たないトークンは、None。"""
|
|
279
289
|
return token.map[0] + 1 + self._line_base if token.map else None
|
|
@@ -288,6 +298,7 @@ class HtmlRenderer(TypstRenderer):
|
|
|
288
298
|
self.current_dir = os.path.dirname(md_path)
|
|
289
299
|
self.front_matter = {}
|
|
290
300
|
self._title = None
|
|
301
|
+
self._heading_ids = {}
|
|
291
302
|
self._pagebreaks = 0
|
|
292
303
|
self._line_base = 0
|
|
293
304
|
self.dependencies = set()
|
|
@@ -107,3 +107,12 @@
|
|
|
107
107
|
} else {
|
|
108
108
|
none
|
|
109
109
|
}
|
|
110
|
+
|
|
111
|
+
// PDFのメタデータ(#317)用: 日付(document.date)の文字列を、datetimeにする。YYYY-MM-DD(例: 2026-08-14)だけを解釈し、
|
|
112
|
+
// それ以外(自由な文字列・未指定)は、autoにする(Typstが、ビルドの日時を入れる。従来どおり)。
|
|
113
|
+
#let meta-date(d) = if type(d) == str {
|
|
114
|
+
let m = d.trim().match(regex("^(\d{4})-(\d{2})-(\d{2})$"))
|
|
115
|
+
if m != none {
|
|
116
|
+
datetime(year: int(m.captures.at(0)), month: int(m.captures.at(1)), day: int(m.captures.at(2)))
|
|
117
|
+
} else { auto }
|
|
118
|
+
} else { auto }
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
// 補助関数(fit-image・render-graph・callout・render-header・render-footer・render-background)は
|
|
7
7
|
// template.typと同一実装のため_common.typから読み込む(#63)。
|
|
8
|
-
#import "_common.typ": fit-image, render-graph, callout, render-header, render-footer, render-background
|
|
8
|
+
#import "_common.typ": fit-image, render-graph, callout, render-header, render-footer, render-background, meta-date
|
|
9
9
|
|
|
10
10
|
#let conf(
|
|
11
11
|
title: none,
|
|
@@ -31,6 +31,14 @@
|
|
|
31
31
|
logo: none,
|
|
32
32
|
doc,
|
|
33
33
|
) = {
|
|
34
|
+
// PDFのメタデータ(プロパティ)。document:のtitle・subtitle・author・dateを、Title・Subject・Author・作成日に入れる(#317)。
|
|
35
|
+
// 未指定の項目は、設定しない(Typstの既定のまま)。dateは、YYYY-MM-DDのときだけ、作成日にする。
|
|
36
|
+
set document(
|
|
37
|
+
title: if title != none { title } else { auto },
|
|
38
|
+
description: if subtitle != none { subtitle } else { none },
|
|
39
|
+
author: if author != none { author } else { () },
|
|
40
|
+
date: meta-date(date),
|
|
41
|
+
)
|
|
34
42
|
set text(font: "Noto Sans JP", size: 10pt)
|
|
35
43
|
|
|
36
44
|
show raw.where(lang: "dot"): it => if graphviz { align(center)[#render-graph(it.text)] } else { it }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// fit-image・render-graph・callout・render-backgroundは、template.typと同一実装のため_common.typに
|
|
2
2
|
// 置いている(#63)。render-header/render-footerはスライド固有の見た目のため、このファイルに持つ。
|
|
3
|
-
#import "_common.typ": fit-image, render-graph, callout, render-background
|
|
3
|
+
#import "_common.typ": fit-image, render-graph, callout, render-background, meta-date
|
|
4
4
|
|
|
5
5
|
// 本文ページのヘッダー・フッター(#42)。template.typと同じ関数名でエクスポートし、build.py側が
|
|
6
6
|
// テンプレート種別を意識せず同じ呼び出し方でチャプター単位の上書きを再発行できるようにする。
|
|
@@ -56,6 +56,14 @@
|
|
|
56
56
|
logo: none,
|
|
57
57
|
doc,
|
|
58
58
|
) = {
|
|
59
|
+
// PDFのメタデータ(プロパティ)。document:のtitle・subtitle・author・dateを、Title・Subject・Author・作成日に入れる(#317)。
|
|
60
|
+
// 未指定の項目は、設定しない(Typstの既定のまま)。dateは、YYYY-MM-DDのときだけ、作成日にする。
|
|
61
|
+
set document(
|
|
62
|
+
title: if title != none { title } else { auto },
|
|
63
|
+
description: if subtitle != none { subtitle } else { none },
|
|
64
|
+
author: if author != none { author } else { () },
|
|
65
|
+
date: meta-date(date),
|
|
66
|
+
)
|
|
59
67
|
// フォント設定(CJKフォントは build.py が取得・キャッシュした Noto Sans JP を --font-path 経由で渡す。
|
|
60
68
|
// OSフォントは直接指定しない。Noto Sans JP に無いグリフはTypstが自動でシステムフォントにフォールバックする)
|
|
61
69
|
set text(font: "Noto Sans JP", size: 18pt)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 補助関数(fit-image・render-graph・callout・render-header・render-footer・render-background)は
|
|
2
2
|
// _common.typへ切り出した(#63)。build.pyが生成するコードはこのモジュールからも
|
|
3
3
|
// 同じ名前でimportするため、ここで読み込むだけで(再エクスポートされて)そのまま使える。
|
|
4
|
-
#import "_common.typ": fit-image, render-graph, callout, render-header, render-footer, render-background
|
|
4
|
+
#import "_common.typ": fit-image, render-graph, callout, render-header, render-footer, render-background, meta-date
|
|
5
5
|
|
|
6
6
|
#let conf(
|
|
7
7
|
title: none,
|
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
logo: none,
|
|
24
24
|
doc,
|
|
25
25
|
) = {
|
|
26
|
+
// PDFのメタデータ(プロパティ)。document:のtitle・subtitle・author・dateを、Title・Subject・Author・作成日に入れる(#317)。
|
|
27
|
+
// 未指定の項目は、設定しない(Typstの既定のまま)。dateは、YYYY-MM-DDのときだけ、作成日にする。
|
|
28
|
+
set document(
|
|
29
|
+
title: if title != none { title } else { auto },
|
|
30
|
+
description: if subtitle != none { subtitle } else { none },
|
|
31
|
+
author: if author != none { author } else { () },
|
|
32
|
+
date: meta-date(date),
|
|
33
|
+
)
|
|
26
34
|
// フォント設定(CJKフォントは build.py が取得・キャッシュした Noto Sans JP を --font-path 経由で渡す。
|
|
27
35
|
// OSフォントは直接指定しない。Noto Sans JP に無いグリフはTypstが自動でシステムフォントにフォールバックする)
|
|
28
36
|
set text(font: "Noto Sans JP", size: 10.5pt)
|
|
@@ -8,7 +8,7 @@ GUI版Viewerが、Pythonをサブプロセスとして常駐させ、これに
|
|
|
8
8
|
JSONオブジェクトが返る。文字コードは、UTF-8。ワーカーは、依頼を1つずつ順に処理する。
|
|
9
9
|
|
|
10
10
|
起動時に、準備ができたことを示すイベントを1行出す。
|
|
11
|
-
{"event": "ready", "protocol": 1, "version": "0.4.
|
|
11
|
+
{"event": "ready", "protocol": 1, "version": "0.4.3"}
|
|
12
12
|
|
|
13
13
|
依頼: {"id": <任意の値。応答にそのまま返る>, "method": <メソッド名>, "params": {...}}
|
|
14
14
|
- `build` : Markdownをビルドする。params: `path`(必須)・`output`・`template`・`plugins`・
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: text-compositor
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: A tool for composing multiple text files into a PDF document.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/tokudiro/text-compositor
|
|
@@ -192,6 +192,16 @@ This generates `sample/SampleDocument.pdf`. Two more samples show other template
|
|
|
192
192
|
|
|
193
193
|
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.
|
|
194
194
|
|
|
195
|
+
## Policy for adding diagram notations
|
|
196
|
+
|
|
197
|
+
This tool is free for commercial use, runs locally, and puts no restrictions on the people who use it (see chapter 2 of [doc/spec.md](doc/spec.md)). A diagram tool or notation is therefore **not** added when it has one of these:
|
|
198
|
+
|
|
199
|
+
- **A strong license constraint.** Strong copyleft such as GPL or AGPL, a watermark or credit that must stay in the output, a fee, or a usage limit.
|
|
200
|
+
- **A heavy runtime.** It needs Docker or a similar container, or a large runtime that most users do not have.
|
|
201
|
+
- **An external service.** It sends the manuscript to a server.
|
|
202
|
+
|
|
203
|
+
Candidates that fail these checks are closed as "not planned" on [GitHub Issues](https://github.com/tokudiro/text-compositor/issues), with the reason recorded.
|
|
204
|
+
|
|
195
205
|
## License
|
|
196
206
|
|
|
197
207
|
[MIT License](LICENSE)
|
|
@@ -33,6 +33,8 @@ tests/test_math_render.py
|
|
|
33
33
|
tests/test_mermaid_resident.py
|
|
34
34
|
tests/test_pandoc_compat.py
|
|
35
35
|
tests/test_paper_template.py
|
|
36
|
+
tests/test_pdf_fonts.py
|
|
37
|
+
tests/test_pdf_metadata.py
|
|
36
38
|
tests/test_pikchr_render.py
|
|
37
39
|
tests/test_plantuml_batch.py
|
|
38
40
|
tests/test_renderer_structure.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{text_compositor-0.4.2 → text_compositor-0.4.3}/text_compositor.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|