text-compositor 0.4.1__tar.gz → 0.4.2__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.
Files changed (94) hide show
  1. {text_compositor-0.4.1 → text_compositor-0.4.2}/PKG-INFO +4 -4
  2. {text_compositor-0.4.1 → text_compositor-0.4.2}/README.md +3 -3
  3. {text_compositor-0.4.1 → text_compositor-0.4.2}/pyproject.toml +1 -1
  4. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_bundled_diagram_tools.py +6 -0
  5. text_compositor-0.4.2/tests/test_cli_single_markdown.py +129 -0
  6. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_emphasis_lint.py +1 -1
  7. text_compositor-0.4.2/tests/test_html_line_numbers.py +94 -0
  8. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_html_output.py +4 -4
  9. text_compositor-0.4.2/tests/test_vega_render.py +366 -0
  10. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_worker.py +64 -6
  11. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/api.py +1 -0
  12. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/build.py +67 -0
  13. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/config.py +1 -1
  14. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/deps.py +46 -0
  15. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/env_check.py +1 -1
  16. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/host_renderers.py +13 -0
  17. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/html_output.py +42 -6
  18. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/mermaid.py +61 -24
  19. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/project.py +2 -1
  20. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer.py +5 -1
  21. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer_diagrams.py +111 -2
  22. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer_layout.py +4 -4
  23. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer_tokens.py +1 -1
  24. text_compositor-0.4.2/text_compositor/vega_render.py +140 -0
  25. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/worker.py +16 -5
  26. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor.egg-info/PKG-INFO +4 -4
  27. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor.egg-info/SOURCES.txt +4 -0
  28. {text_compositor-0.4.1 → text_compositor-0.4.2}/LICENSE +0 -0
  29. {text_compositor-0.4.1 → text_compositor-0.4.2}/setup.cfg +0 -0
  30. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_aggregate_table.py +0 -0
  31. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_api.py +0 -0
  32. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_asset_resolution.py +0 -0
  33. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_bundled_typst_assets.py +0 -0
  34. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_cetz_render.py +0 -0
  35. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_check_pins.py +0 -0
  36. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_common_template.py +0 -0
  37. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_config.py +0 -0
  38. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_config_key_validation.py +0 -0
  39. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_csv_table.py +0 -0
  40. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_diagnostics.py +0 -0
  41. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_diagram_cache_key.py +0 -0
  42. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_diagram_trim.py +0 -0
  43. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_distribution.py +0 -0
  44. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_doc_badges.py +0 -0
  45. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_download.py +0 -0
  46. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_front_matter.py +0 -0
  47. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_graphviz_render.py +0 -0
  48. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_incremental.py +0 -0
  49. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_java_encoding.py +0 -0
  50. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_layout_blocks.py +0 -0
  51. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_line_mapping.py +0 -0
  52. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_markdown_basics.py +0 -0
  53. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_math_render.py +0 -0
  54. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_mermaid_resident.py +0 -0
  55. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_pandoc_compat.py +0 -0
  56. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_paper_template.py +0 -0
  57. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_pikchr_render.py +0 -0
  58. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_plantuml_batch.py +0 -0
  59. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_renderer_structure.py +0 -0
  60. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_revision_history.py +0 -0
  61. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_script_references.py +0 -0
  62. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_sections.py +0 -0
  63. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_soft_break_hints.py +0 -0
  64. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_structurizr_render.py +0 -0
  65. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_svg_trim.py +0 -0
  66. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_table_cell_style.py +0 -0
  67. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_variables.py +0 -0
  68. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_viewer_version.py +0 -0
  69. {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_watch.py +0 -0
  70. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/__init__.py +0 -0
  71. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/cetz_render.py +0 -0
  72. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/changes.py +0 -0
  73. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/chapters.py +0 -0
  74. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/compiler.py +0 -0
  75. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/diagnostics.py +0 -0
  76. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/document.py +0 -0
  77. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/emphasis_lint.py +0 -0
  78. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/graphviz_render.py +0 -0
  79. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/log.py +0 -0
  80. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/math_render.py +0 -0
  81. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/pikchr_render.py +0 -0
  82. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer_inline.py +0 -0
  83. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer_tables.py +0 -0
  84. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/svg_trim.py +0 -0
  85. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/templates/_common.typ +0 -0
  86. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/templates/paper.typ +0 -0
  87. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/templates/slide.typ +0 -0
  88. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/templates/template.typ +0 -0
  89. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/typst_literal.py +0 -0
  90. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/typst_runtime.py +0 -0
  91. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor.egg-info/dependency_links.txt +0 -0
  92. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor.egg-info/entry_points.txt +0 -0
  93. {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor.egg-info/requires.txt +0 -0
  94. {text_compositor-0.4.1 → text_compositor-0.4.2}/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.1
3
+ Version: 0.4.2
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
@@ -87,13 +87,13 @@ This is only needed if your source documents use ` ```mermaid ` fences (or speci
87
87
 
88
88
  ```bash
89
89
  pipx install "text-compositor[mermaid]"
90
- # or, if already installed: pipx inject text-compositor playwright==1.62.0
90
+ # or, if already installed: pipx inject text-compositor playwright==1.63.0
91
91
  ```
92
92
 
93
93
  With Option B (clone and run directly):
94
94
 
95
95
  ```bash
96
- pip install playwright==1.62.0
96
+ pip install playwright==1.63.0
97
97
  ```
98
98
 
99
99
  - **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)
@@ -175,7 +175,7 @@ with Session() as session: # reuses the Mermaid browser and the Typst compiler
175
175
  print(result.ok, result.pdf_path, [d.message for d in result.diagnostics])
176
176
  ```
177
177
 
178
- `session.render_html("doc.md")` (experimental, [#161](https://github.com/tokudiro/text-compositor/issues/161)) writes a self-contained HTML file instead. Diagrams (Mermaid, PlantUML, D2, Structurizr, Graphviz, Pikchr, CeTZ, Fletcher, timeliney, `svg`) become images next to it. `typst-exec` and raw HTML are not supported yet and are shown as code with a warning. Graphviz, Pikchr, CeTZ, Fletcher and timeliney are drawn with the Typst packages `diagraph`, `kip`, `cetz`, `fletcher` and `timeliney`, the same as in the PDF (Graphviz's `shape=record` and a graph-level `label` cannot be drawn and produce a warning).
178
+ `session.render_html("doc.md")` (experimental, [#161](https://github.com/tokudiro/text-compositor/issues/161)) writes a self-contained HTML file instead. Diagrams (Mermaid, PlantUML, D2, Structurizr, Graphviz, Pikchr, CeTZ, Fletcher, timeliney, Vega-Lite, Vega, `svg`) become images next to it. `typst-exec` and raw HTML are not supported yet and are shown as code with a warning. Graphviz, Pikchr, CeTZ, Fletcher and timeliney are drawn with the Typst packages `diagraph`, `kip`, `cetz`, `fletcher` and `timeliney`, the same as in the PDF (Graphviz's `shape=record` and a graph-level `label` cannot be drawn and produce a warning).
179
179
 
180
180
  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.
181
181
 
@@ -69,13 +69,13 @@ This is only needed if your source documents use ` ```mermaid ` fences (or speci
69
69
 
70
70
  ```bash
71
71
  pipx install "text-compositor[mermaid]"
72
- # or, if already installed: pipx inject text-compositor playwright==1.62.0
72
+ # or, if already installed: pipx inject text-compositor playwright==1.63.0
73
73
  ```
74
74
 
75
75
  With Option B (clone and run directly):
76
76
 
77
77
  ```bash
78
- pip install playwright==1.62.0
78
+ pip install playwright==1.63.0
79
79
  ```
80
80
 
81
81
  - **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)
@@ -157,7 +157,7 @@ with Session() as session: # reuses the Mermaid browser and the Typst compiler
157
157
  print(result.ok, result.pdf_path, [d.message for d in result.diagnostics])
158
158
  ```
159
159
 
160
- `session.render_html("doc.md")` (experimental, [#161](https://github.com/tokudiro/text-compositor/issues/161)) writes a self-contained HTML file instead. Diagrams (Mermaid, PlantUML, D2, Structurizr, Graphviz, Pikchr, CeTZ, Fletcher, timeliney, `svg`) become images next to it. `typst-exec` and raw HTML are not supported yet and are shown as code with a warning. Graphviz, Pikchr, CeTZ, Fletcher and timeliney are drawn with the Typst packages `diagraph`, `kip`, `cetz`, `fletcher` and `timeliney`, the same as in the PDF (Graphviz's `shape=record` and a graph-level `label` cannot be drawn and produce a warning).
160
+ `session.render_html("doc.md")` (experimental, [#161](https://github.com/tokudiro/text-compositor/issues/161)) writes a self-contained HTML file instead. Diagrams (Mermaid, PlantUML, D2, Structurizr, Graphviz, Pikchr, CeTZ, Fletcher, timeliney, Vega-Lite, Vega, `svg`) become images next to it. `typst-exec` and raw HTML are not supported yet and are shown as code with a warning. Graphviz, Pikchr, CeTZ, Fletcher and timeliney are drawn with the Typst packages `diagraph`, `kip`, `cetz`, `fletcher` and `timeliney`, the same as in the PDF (Graphviz's `shape=record` and a graph-level `label` cannot be drawn and produce a warning).
161
161
 
162
162
  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.
163
163
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "text-compositor"
7
- version = "0.4.1"
7
+ version = "0.4.2"
8
8
  description = "A tool for composing multiple text files into a PDF document."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -143,3 +143,9 @@ def test_build_dist_pins_the_same_java_plantuml_d2_structurizr_and_mermaid_as_th
143
143
 
144
144
  assert deps_mod.MERMAID_JS_URL in script
145
145
  assert deps_mod.MERMAID_JS_SHA256 in script
146
+
147
+ # Vega・Vega-Lite(#351)
148
+ assert deps_mod.VEGA_JS_URL in script
149
+ assert deps_mod.VEGA_JS_SHA256 in script
150
+ assert deps_mod.VEGA_LITE_JS_URL in script
151
+ assert deps_mod.VEGA_LITE_JS_SHA256 in script
@@ -0,0 +1,129 @@
1
+ """CLI: 単一のMarkdownファイルを直接指定してPDFにする(`text-compositor FILE.md`、#179)のテスト。"""
2
+ import os
3
+ import sys
4
+
5
+ import pytest
6
+
7
+ import text_compositor.build as build_mod
8
+ from text_compositor import log
9
+ import text_compositor.project as project_mod
10
+ from text_compositor.build import _single_markdown_output, parse_args
11
+
12
+
13
+ @pytest.fixture(autouse=True)
14
+ def _restore_verbosity():
15
+ """`build()`は、-q/-vを、プロセスグローバルへ反映する。他のテストへ、残さない。"""
16
+ yield
17
+ log.set_verbosity(False, False)
18
+
19
+
20
+ def parse(monkeypatch, *argv):
21
+ monkeypatch.setattr(sys, "argv", ["text-compositor", *argv])
22
+ return parse_args()
23
+
24
+
25
+ class TestParseArgs:
26
+ def test_a_single_file_with_output_and_template(self, monkeypatch):
27
+ args = parse(monkeypatch, "doc.md", "-o", "out.pdf", "-t", "slide", "-q", "--keep-temp")
28
+ assert (args.markdown, args.output, args.template, args.quiet, args.keep_temp) == ("doc.md", "out.pdf", "slide", True, True)
29
+
30
+ def test_existing_usage_without_a_file_is_unchanged(self, monkeypatch):
31
+ args = parse(monkeypatch, "--config", "c.yaml", "--if-changed")
32
+ assert args.markdown is None and args.config == "c.yaml" and args.if_changed
33
+
34
+ @pytest.mark.parametrize("flags", [
35
+ ["--config", "c.yaml"], ["--config-list", "l.txt"], ["--check-env"], ["--watch"], ["--if-changed"],
36
+ ["--clean"], ["--clean-cache"],
37
+ ])
38
+ def test_options_that_need_a_config_cannot_be_combined_with_a_file(self, monkeypatch, capsys, flags):
39
+ with pytest.raises(SystemExit) as e:
40
+ parse(monkeypatch, "doc.md", *flags)
41
+ assert e.value.code == 2
42
+ assert "同時に指定できません" in capsys.readouterr().err
43
+
44
+ @pytest.mark.parametrize("flags", [["-o", "out.pdf"], ["-t", "slide"]])
45
+ def test_output_and_template_need_a_file(self, monkeypatch, capsys, flags):
46
+ with pytest.raises(SystemExit) as e:
47
+ parse(monkeypatch, *flags)
48
+ assert e.value.code == 2 and "FILE.md" in capsys.readouterr().err
49
+
50
+ def test_only_markdown_files_are_accepted(self, monkeypatch, capsys):
51
+ with pytest.raises(SystemExit) as e:
52
+ parse(monkeypatch, "notes.txt")
53
+ assert e.value.code == 2
54
+ assert parse(monkeypatch, "A.MD").markdown == "A.MD" and parse(monkeypatch, "a.markdown").markdown == "a.markdown"
55
+
56
+
57
+ class TestOutputPath:
58
+ def test_the_default_is_next_to_the_manuscript(self, tmp_path):
59
+ md = str(tmp_path / "sub" / "note.md")
60
+ assert _single_markdown_output(md, None) == str(tmp_path / "sub" / "note.pdf")
61
+
62
+ def test_an_explicit_file_and_a_folder(self, tmp_path, monkeypatch):
63
+ monkeypatch.chdir(tmp_path)
64
+ md = str(tmp_path / "note.md")
65
+ assert _single_markdown_output(md, "out.pdf") == str(tmp_path / "out.pdf")
66
+ (tmp_path / "existing").mkdir()
67
+ assert _single_markdown_output(md, "existing") == str(tmp_path / "existing" / "note.pdf")
68
+ assert _single_markdown_output(md, "new" + os.sep) == str(tmp_path / "new" / "note.pdf")
69
+
70
+
71
+ class TestBuildSingleMarkdown:
72
+ @pytest.fixture
73
+ def calls(self, monkeypatch):
74
+ recorded = []
75
+ monkeypatch.setattr(project_mod, "_build_project", lambda *a, **k: recorded.append((a, k)))
76
+ return recorded
77
+
78
+ def run(self, tmp_path, *argv, monkeypatch):
79
+ monkeypatch.setattr(build_mod, "check_typst_version", lambda *a: None)
80
+ monkeypatch.setattr(build_mod, "ensure_fonts", lambda: "fonts")
81
+ monkeypatch.setattr(sys, "argv", ["text-compositor", *argv])
82
+ build_mod.build()
83
+
84
+ def test_it_builds_the_api_config_and_writes_next_to_the_manuscript(self, tmp_path, calls, monkeypatch):
85
+ md = tmp_path / "note.md"
86
+ md.write_text("# T\n", encoding="utf-8")
87
+ self.run(tmp_path, str(md), monkeypatch=monkeypatch)
88
+ (args, kwargs), = calls
89
+ project_dir, config, chapters = args[3], args[4], args[5]
90
+ assert project_dir == str(tmp_path) and chapters == ["note.md"]
91
+ assert config["document"]["cover"] == "markdown" and config["document"]["title"] == "note" # APIと同じ既定
92
+ assert config["template"]["path"] == "template"
93
+ assert kwargs["out_pdf"] == str(tmp_path / "note.pdf")
94
+
95
+ def test_the_template_name_and_a_typ_path_relative_to_the_cwd(self, tmp_path, calls, monkeypatch):
96
+ md = tmp_path / "note.md"
97
+ md.write_text("# T\n", encoding="utf-8")
98
+ monkeypatch.chdir(tmp_path)
99
+ self.run(tmp_path, "note.md", "-t", "paper", monkeypatch=monkeypatch)
100
+ self.run(tmp_path, "note.md", "-t", "my/own.typ", "-o", "build/o.pdf", monkeypatch=monkeypatch)
101
+ assert calls[0][0][4]["template"]["path"] == "paper"
102
+ assert calls[1][0][4]["template"]["path"] == str(tmp_path / "my" / "own.typ")
103
+ assert calls[1][1]["out_pdf"] == str(tmp_path / "build" / "o.pdf") and (tmp_path / "build").is_dir()
104
+
105
+ def test_a_missing_file_and_an_unknown_template_fail_with_exit_code_1(self, tmp_path, calls, monkeypatch, capsys):
106
+ with pytest.raises(SystemExit) as e:
107
+ self.run(tmp_path, str(tmp_path / "nope.md"), monkeypatch=monkeypatch)
108
+ assert e.value.code == 1 and "Markdown file not found" in capsys.readouterr().out
109
+ md = tmp_path / "note.md"
110
+ md.write_text("# T\n", encoding="utf-8")
111
+ with pytest.raises(SystemExit) as e:
112
+ self.run(tmp_path, str(md), "-t", "nosuch", monkeypatch=monkeypatch)
113
+ assert e.value.code == 1 and "Unknown template" in capsys.readouterr().out
114
+ assert calls == []
115
+
116
+
117
+ def test_a_real_pdf_is_built_without_a_config(tmp_path, monkeypatch):
118
+ """実際に、configなしで、PDFができる(Typstとフォントが、ある環境)。先頭の見出しは、表紙に落とされず、本文に残る。"""
119
+ from text_compositor.deps import ensure_fonts
120
+ try:
121
+ ensure_fonts()
122
+ except SystemExit:
123
+ pytest.skip("fonts are not available")
124
+ md = tmp_path / "note.md"
125
+ md.write_text("# 見出し\n\n本文です。\n", encoding="utf-8")
126
+ monkeypatch.setattr(sys, "argv", ["text-compositor", str(md), "-q"])
127
+ build_mod.build()
128
+ pdf = tmp_path / "note.pdf"
129
+ assert pdf.is_file() and pdf.read_bytes().startswith(b"%PDF")
@@ -93,7 +93,7 @@ class TestHtmlOutput:
93
93
  def test_the_text_is_still_shown(self, tmp_path):
94
94
  result, _ = _html_warnings(tmp_path, "これは**「重要」**です。\n")
95
95
  with open(result.html_path, encoding="utf-8") as f:
96
- assert "<p>これは**「重要」**です。</p>" in f.read()
96
+ assert '<p data-line="1">これは**「重要」**です。</p>' in f.read()
97
97
 
98
98
  def test_a_table_cell_and_a_list_item_are_checked(self, tmp_path):
99
99
  text = "| a | b |\n| - | - |\n| **「x」**です | y |\n\n- 項目**(仮)**です\n"
@@ -0,0 +1,94 @@
1
+ """HTML出力の、ブロック要素の`data-line`(原稿の行番号。Viewerが、ツールバーに出す。#328・#357)のテスト。"""
2
+ import re
3
+
4
+ import pytest
5
+
6
+ from text_compositor.api import render_html
7
+ from text_compositor.html_output import DOCUMENT_CSS, _with_data_line
8
+
9
+ PLAIN = {"mermaid": False, "plantuml": False, "d2": False}
10
+
11
+ DOC = """# 見出し
12
+
13
+ 段落その1です。
14
+ 続きの行。
15
+
16
+ - 項目A
17
+ - 項目B
18
+ - 入れ子B1
19
+
20
+ 1. 番号1
21
+
22
+ ゆるい段落
23
+
24
+ > 引用の段落
25
+
26
+ | a | b |
27
+ | - | - |
28
+ | 1 | 2 |
29
+
30
+ ```python
31
+ print(1)
32
+ ```
33
+
34
+ $$
35
+ a=b
36
+ $$
37
+
38
+ 最後の段落。
39
+ """
40
+
41
+
42
+ def lines_by_tag(tmp_path, markdown):
43
+ md = tmp_path / "doc.md"
44
+ md.write_text(markdown, encoding="utf-8")
45
+ result = render_html(str(md), plugins=PLAIN)
46
+ assert result.ok, result.diagnostics
47
+ html = open(result.html_path, encoding="utf-8").read()
48
+ body = html[html.index("<body"):]
49
+ return [(m.group(1), int(m.group(2))) for m in re.finditer(r'<(\w+)[^>]*\sdata-line="(\d+)"', body)], body
50
+
51
+
52
+ def test_block_elements_carry_their_manuscript_line(tmp_path):
53
+ found, _ = lines_by_tag(tmp_path, DOC)
54
+ assert found == [
55
+ ("h1", 1), ("p", 3),
56
+ ("li", 6), ("li", 7), ("li", 8), # 緊密なリスト: 項目が持つ(中の段落は、隠れている)
57
+ ("p", 10), ("p", 12), # ゆるいリスト: 項目ではなく、中の段落が持つ
58
+ ("p", 14), # 引用: 中の段落が持つ
59
+ ("table", 16), ("pre", 20), ("div", 24), # 表・フェンス・数式
60
+ ("p", 28),
61
+ ]
62
+
63
+
64
+ def test_a_diagram_fence_carries_the_line_on_its_wrapper(tmp_path):
65
+ svg = tmp_path / "x.svg"
66
+ svg.write_text('<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"></svg>', encoding="utf-8")
67
+ found, body = lines_by_tag(tmp_path, "# T\n\n```svg\n" + svg.read_text(encoding="utf-8") + "\n```\n")
68
+ assert ("div", 3) in found and '<div data-line="3" class="diagram diagram-svg">' in body
69
+
70
+
71
+ def test_a_line_is_not_added_twice_or_to_the_wrong_element(tmp_path):
72
+ _, body = lines_by_tag(tmp_path, DOC)
73
+ assert len(re.findall(r'data-line="10"', body)) == 1 and len(re.findall(r'data-line="6"', body)) == 1
74
+ assert "<hr" not in body or 'hr data-line' not in body
75
+
76
+
77
+ class TestWithDataLine:
78
+ def test_it_adds_the_line_to_the_first_tag_only(self):
79
+ assert _with_data_line('<div class="a"><p>x</p></div>\n', 7) == '<div data-line="7" class="a"><p>x</p></div>\n'
80
+
81
+ @pytest.mark.parametrize("html,line", [('<pre>x</pre>', None), ('<pre>x</pre>', 0), ('text', 3), ('', 3)])
82
+ def test_it_leaves_html_without_a_line_or_a_leading_tag_alone(self, html, line):
83
+ assert _with_data_line(html, line) == html
84
+
85
+
86
+ class TestNoBadgeCss:
87
+ """行番号は、ツールバーに出す(#357)。文書のCSSには、バッジ(疑似要素)を、持たせない。"""
88
+
89
+ def test_the_document_css_has_no_badge(self):
90
+ assert "data-line-badge" not in DOCUMENT_CSS and "attr(data-line)" not in DOCUMENT_CSS
91
+
92
+ def test_the_document_still_has_no_script(self, tmp_path):
93
+ _, body = lines_by_tag(tmp_path, DOC)
94
+ assert "<script" not in body
@@ -76,7 +76,7 @@ class TestApi:
76
76
  assert result.html_path == str(tmp_path / ".text-compositor" / "preview.html")
77
77
  assert html.startswith("<!DOCTYPE html>")
78
78
  assert "<title>見出し</title>" in html
79
- assert "<h1>見出し</h1>" in html
79
+ assert '<h1 data-line="1">見出し</h1>' in html
80
80
  assert "<script" not in html
81
81
  # スクリプトは、ブラウザ側でも禁止する(Viewerは、JavaScriptを有効にしたビューで開く)
82
82
  assert "script-src 'none'" in html and "object-src 'none'" in html
@@ -142,7 +142,7 @@ class TestMarkdown:
142
142
  assert "<strong>b</strong>" in b and "<em>i</em>" in b and "<s>s</s>" in b and "<code>c</code>" in b
143
143
  assert '<a href="https://example.com">l</a>' in b
144
144
  assert "<ul>" in b and "<ol>" in b
145
- assert '<pre><code class="language-python">print(1)\n</code></pre>' in b
145
+ assert re.search(r'<pre data-line="\d+"><code class="language-python">print\(1\)\n</code></pre>', b)
146
146
 
147
147
  def test_line_breaks_are_kept_like_the_pdf(self, tmp_path):
148
148
  _, html = convert(tmp_path, "one\ntwo\n")
@@ -467,12 +467,12 @@ class TestFences:
467
467
 
468
468
  def test_a_disabled_plugin_falls_back_to_code_without_a_warning(self, tmp_path):
469
469
  result, html = convert(tmp_path, "```mermaid\ngraph TD\n A-->B\n```\n\n```plantuml\n@startuml\n@enduml\n```\n\n```d2\na -> b\n```\n")
470
- assert body(html).count("<pre>") == 3 and "<img" not in html
470
+ assert len(re.findall(r"<pre[ >]", body(html))) == 3 and "<img" not in html
471
471
  assert not result.warnings
472
472
 
473
473
  def test_other_languages_are_plain_code_blocks(self, tmp_path):
474
474
  _, html = convert(tmp_path, "```\n<b>&\n```\n")
475
- assert "<pre><code>&lt;b&gt;&amp;\n</code></pre>" in html
475
+ assert '<pre data-line="1"><code>&lt;b&gt;&amp;\n</code></pre>' in html
476
476
 
477
477
  def test_a_diagram_source_file_is_a_page_with_that_diagram(self, tmp_path):
478
478
  result, html = convert(tmp_path, "graph TD\n A-->B\n", name="flow.mmd")