rtflite 2.5.4__tar.gz → 2.6.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.
- {rtflite-2.5.4 → rtflite-2.6.0}/PKG-INFO +14 -15
- rtflite-2.6.0/pyproject.toml +131 -0
- rtflite-2.5.4/pyproject.toml → rtflite-2.6.0/pyproject.toml.orig +7 -10
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/convert.py +141 -69
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/encode.py +9 -8
- rtflite-2.5.4/.gitignore +0 -28
- rtflite-2.5.4/.python-version +0 -1
- rtflite-2.5.4/CHANGELOG.md +0 -359
- rtflite-2.5.4/LICENSE +0 -20
- rtflite-2.5.4/LICENSES_THIRD_PARTY +0 -20
- {rtflite-2.5.4 → rtflite-2.6.0}/README.md +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/__init__.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/assemble.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/attributes.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/core/__init__.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/core/config.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/core/constants.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/data/HAMD17.parquet +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/data/__init__.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/data/adae.parquet +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/data/adsl.parquet +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/data/baseline.parquet +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/data/tbl1.parquet +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/data/tbl2.parquet +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/data/tbl3.parquet +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/dictionary/__init__.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/dictionary/color_table.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/dictionary/libreoffice.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/dictionary/unicode_latex.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/encoding/__init__.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/encoding/base.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/encoding/engine.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/encoding/renderer.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/encoding/unified_encoder.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/figure.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/fonts/README.md +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/fonts/__init__.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/fonts/cros/Caladea-Regular.ttf +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/fonts/cros/Carlito-Regular.ttf +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/fonts/cros/Gelasio-Regular.ttf +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/fonts/liberation/LiberationMono-Regular.ttf +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/fonts/liberation/LiberationSans-Regular.ttf +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/fonts/liberation/LiberationSerif-Regular.ttf +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/fonts_mapping.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/input.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/pagination/__init__.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/pagination/core.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/pagination/processor.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/pagination/strategies/__init__.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/pagination/strategies/base.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/pagination/strategies/defaults.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/pagination/strategies/grouping.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/pagination/strategies/registry.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/row.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/rtf/__init__.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/rtf/syntax.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/services/__init__.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/services/color_service.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/services/document_service.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/services/encoding_service.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/services/figure_service.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/services/grouping_service.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/services/text_conversion_service.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/strwidth.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/text_conversion/__init__.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/text_conversion/converter.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/text_conversion/symbols.py +0 -0
- {rtflite-2.5.4 → rtflite-2.6.0}/src/rtflite/type_guards.py +0 -0
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rtflite
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: Lightweight RTF composer for Python
|
|
5
|
-
|
|
6
|
-
Project-URL: Documentation, https://pharmaverse.github.io/rtflite/
|
|
7
|
-
Project-URL: Repository, https://github.com/pharmaverse/rtflite
|
|
8
|
-
Project-URL: Issues, https://github.com/pharmaverse/rtflite/issues
|
|
9
|
-
Project-URL: Changelog, https://github.com/pharmaverse/rtflite/blob/main/CHANGELOG.md
|
|
5
|
+
Author: Yilong Zhang, Nan Xiao
|
|
10
6
|
Author-email: Yilong Zhang <elong0527@gmail.com>, Nan Xiao <me@nanx.me>
|
|
11
|
-
License-
|
|
12
|
-
License-File: LICENSES_THIRD_PARTY
|
|
7
|
+
License-Expression: MIT
|
|
13
8
|
Classifier: Development Status :: 3 - Alpha
|
|
14
9
|
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
11
|
Classifier: Intended Audience :: Healthcare Industry
|
|
16
12
|
Classifier: Intended Audience :: Information Technology
|
|
17
|
-
Classifier:
|
|
18
|
-
Classifier:
|
|
13
|
+
Classifier: Topic :: Text Processing
|
|
14
|
+
Classifier: Topic :: Office/Business :: Office Suites
|
|
19
15
|
Classifier: Programming Language :: Python :: 3
|
|
20
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
24
20
|
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
-
Classifier: Topic :: Office/Business :: Office Suites
|
|
26
|
-
Classifier: Topic :: Text Processing
|
|
27
|
-
Requires-Python: >=3.10
|
|
28
21
|
Requires-Dist: narwhals>=1.0.0
|
|
29
22
|
Requires-Dist: pillow>=8.0.0
|
|
30
23
|
Requires-Dist: polars>=1.0.0
|
|
31
24
|
Requires-Dist: pydantic>=2.0.0
|
|
25
|
+
Requires-Dist: python-docx>=1.0.0 ; extra == 'docx'
|
|
26
|
+
Requires-Dist: pypdf>=5.0.0 ; extra == 'pdf'
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Project-URL: Homepage, https://pharmaverse.github.io/rtflite/
|
|
29
|
+
Project-URL: Documentation, https://pharmaverse.github.io/rtflite/
|
|
30
|
+
Project-URL: Repository, https://github.com/pharmaverse/rtflite
|
|
31
|
+
Project-URL: Issues, https://github.com/pharmaverse/rtflite/issues
|
|
32
|
+
Project-URL: Changelog, https://github.com/pharmaverse/rtflite/blob/main/CHANGELOG.md
|
|
32
33
|
Provides-Extra: docx
|
|
33
|
-
Requires-Dist: python-docx>=1.0.0; extra == 'docx'
|
|
34
34
|
Provides-Extra: pdf
|
|
35
|
-
Requires-Dist: pypdf>=5.0.0; extra == 'pdf'
|
|
36
35
|
Description-Content-Type: text/markdown
|
|
37
36
|
|
|
38
37
|
# rtflite <img src="https://github.com/pharmaverse/rtflite/raw/main/docs/assets/logo.png" align="right" width="120" />
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rtflite"
|
|
3
|
+
version = "2.6.0"
|
|
4
|
+
description = "Lightweight RTF composer for Python"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"narwhals>=1.0.0",
|
|
7
|
+
"pillow>=8.0.0",
|
|
8
|
+
"polars>=1.0.0",
|
|
9
|
+
"pydantic>=2.0.0",
|
|
10
|
+
]
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
license = "MIT"
|
|
13
|
+
requires-python = ">= 3.10"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Intended Audience :: Healthcare Industry",
|
|
19
|
+
"Intended Audience :: Information Technology",
|
|
20
|
+
"Topic :: Text Processing",
|
|
21
|
+
"Topic :: Office/Business :: Office Suites",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[[project.authors]]
|
|
31
|
+
name = "Yilong Zhang"
|
|
32
|
+
email = "elong0527@gmail.com"
|
|
33
|
+
|
|
34
|
+
[[project.authors]]
|
|
35
|
+
name = "Nan Xiao"
|
|
36
|
+
email = "me@nanx.me"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
docx = ["python-docx>=1.0.0"]
|
|
40
|
+
pdf = ["pypdf>=5.0.0"]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://pharmaverse.github.io/rtflite/"
|
|
44
|
+
Documentation = "https://pharmaverse.github.io/rtflite/"
|
|
45
|
+
Repository = "https://github.com/pharmaverse/rtflite"
|
|
46
|
+
Issues = "https://github.com/pharmaverse/rtflite/issues"
|
|
47
|
+
Changelog = "https://github.com/pharmaverse/rtflite/blob/main/CHANGELOG.md"
|
|
48
|
+
|
|
49
|
+
[build-system]
|
|
50
|
+
requires = ["uv_build>=0.12.10,<0.13.0"]
|
|
51
|
+
build-backend = "uv_build"
|
|
52
|
+
|
|
53
|
+
[tool.uv.build-backend]
|
|
54
|
+
source-exclude = [
|
|
55
|
+
"/docs",
|
|
56
|
+
"/tests",
|
|
57
|
+
"/scripts",
|
|
58
|
+
"/example_output",
|
|
59
|
+
"/htmlcov",
|
|
60
|
+
"/.github",
|
|
61
|
+
"/site",
|
|
62
|
+
"/zensical.toml",
|
|
63
|
+
"/AGENTS.md",
|
|
64
|
+
"/CLAUDE.md",
|
|
65
|
+
"/CONTRIBUTING.md",
|
|
66
|
+
"/CODE_OF_CONDUCT.md",
|
|
67
|
+
"/coverage.xml",
|
|
68
|
+
"/uv.lock",
|
|
69
|
+
"**/.gitignore",
|
|
70
|
+
"/.claude",
|
|
71
|
+
"/.mypy_cache",
|
|
72
|
+
"/.pytest_cache",
|
|
73
|
+
"/.ruff_cache",
|
|
74
|
+
"/.coverage",
|
|
75
|
+
]
|
|
76
|
+
wheel-exclude = [
|
|
77
|
+
"/docs",
|
|
78
|
+
"/tests",
|
|
79
|
+
"/scripts",
|
|
80
|
+
"/example_output",
|
|
81
|
+
"/htmlcov",
|
|
82
|
+
"/.github",
|
|
83
|
+
"/site",
|
|
84
|
+
"/zensical.toml",
|
|
85
|
+
"/AGENTS.md",
|
|
86
|
+
"/CLAUDE.md",
|
|
87
|
+
"/CONTRIBUTING.md",
|
|
88
|
+
"/CODE_OF_CONDUCT.md",
|
|
89
|
+
"/coverage.xml",
|
|
90
|
+
"/uv.lock",
|
|
91
|
+
"**/.gitignore",
|
|
92
|
+
"/.claude",
|
|
93
|
+
"/.mypy_cache",
|
|
94
|
+
"/.pytest_cache",
|
|
95
|
+
"/.ruff_cache",
|
|
96
|
+
"/.coverage",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
[tool.ruff.lint]
|
|
100
|
+
select = [
|
|
101
|
+
"E",
|
|
102
|
+
"F",
|
|
103
|
+
"UP",
|
|
104
|
+
"B",
|
|
105
|
+
"SIM",
|
|
106
|
+
"I",
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
[[tool.mypy.overrides]]
|
|
110
|
+
module = [
|
|
111
|
+
"docx",
|
|
112
|
+
"docx.*",
|
|
113
|
+
"pypdf",
|
|
114
|
+
"pypdf.*",
|
|
115
|
+
]
|
|
116
|
+
ignore_missing_imports = true
|
|
117
|
+
|
|
118
|
+
[dependency-groups]
|
|
119
|
+
dev = [
|
|
120
|
+
"griffe-pydantic>=1.1.8",
|
|
121
|
+
"isort>=5.13.2",
|
|
122
|
+
"markdown-exec[ansi]>=1.11.0",
|
|
123
|
+
"matplotlib>=3.10.5",
|
|
124
|
+
"mkdocstrings-python>=1.12.2",
|
|
125
|
+
"mypy>=1.17.1",
|
|
126
|
+
"pytest>=8.3.3",
|
|
127
|
+
"pytest-cov>=6.0.0",
|
|
128
|
+
"pytest-r-snapshot>=0.1.0",
|
|
129
|
+
"ruff>=0.7.4",
|
|
130
|
+
"zensical>=0.0.15",
|
|
131
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "rtflite"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.6.0"
|
|
4
4
|
description = "Lightweight RTF composer for Python"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Yilong Zhang", email = "elong0527@gmail.com" },
|
|
@@ -13,6 +13,7 @@ dependencies = [
|
|
|
13
13
|
"pydantic>=2.0.0",
|
|
14
14
|
]
|
|
15
15
|
readme = "README.md"
|
|
16
|
+
license = "MIT"
|
|
16
17
|
requires-python = ">= 3.10"
|
|
17
18
|
classifiers = [
|
|
18
19
|
"Development Status :: 3 - Alpha",
|
|
@@ -25,8 +26,6 @@ classifiers = [
|
|
|
25
26
|
"Topic :: Text Processing",
|
|
26
27
|
"Topic :: Office/Business :: Office Suites",
|
|
27
28
|
|
|
28
|
-
"License :: OSI Approved :: MIT License",
|
|
29
|
-
|
|
30
29
|
"Programming Language :: Python :: 3",
|
|
31
30
|
"Programming Language :: Python :: 3.10",
|
|
32
31
|
"Programming Language :: Python :: 3.11",
|
|
@@ -47,11 +46,11 @@ Issues = "https://github.com/pharmaverse/rtflite/issues"
|
|
|
47
46
|
Changelog = "https://github.com/pharmaverse/rtflite/blob/main/CHANGELOG.md"
|
|
48
47
|
|
|
49
48
|
[build-system]
|
|
50
|
-
requires = ["
|
|
51
|
-
build-backend = "
|
|
49
|
+
requires = ["uv_build>=0.12.10,<0.13.0"]
|
|
50
|
+
build-backend = "uv_build"
|
|
52
51
|
|
|
53
|
-
[tool.
|
|
54
|
-
exclude = [
|
|
52
|
+
[tool.uv.build-backend]
|
|
53
|
+
source-exclude = [
|
|
55
54
|
"/docs",
|
|
56
55
|
"/tests",
|
|
57
56
|
"/scripts",
|
|
@@ -73,9 +72,7 @@ exclude = [
|
|
|
73
72
|
"/.ruff_cache",
|
|
74
73
|
"/.coverage",
|
|
75
74
|
]
|
|
76
|
-
|
|
77
|
-
[tool.hatch.build.targets.wheel]
|
|
78
|
-
exclude = [
|
|
75
|
+
wheel-exclude = [
|
|
79
76
|
"/docs",
|
|
80
77
|
"/tests",
|
|
81
78
|
"/scripts",
|
|
@@ -3,11 +3,11 @@ import platform
|
|
|
3
3
|
import re
|
|
4
4
|
import shutil
|
|
5
5
|
import subprocess
|
|
6
|
+
import tempfile
|
|
6
7
|
from collections.abc import Sequence
|
|
8
|
+
from math import isfinite
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
|
|
9
|
-
from packaging import version
|
|
10
|
-
|
|
11
11
|
from .dictionary.libreoffice import DEFAULT_PATHS, MIN_VERSION
|
|
12
12
|
|
|
13
13
|
|
|
@@ -27,17 +27,28 @@ class LibreOfficeConverter:
|
|
|
27
27
|
This makes it suitable for server environments and automated workflows.
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
def __init__(
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
executable_path: str | Path | None = None,
|
|
33
|
+
*,
|
|
34
|
+
timeout: float | None = 120,
|
|
35
|
+
) -> None:
|
|
31
36
|
"""Initialize converter with optional executable path.
|
|
32
37
|
|
|
33
38
|
Args:
|
|
34
39
|
executable_path: Path (or executable name) to LibreOffice. If None,
|
|
35
40
|
searches standard installation locations for each platform.
|
|
41
|
+
timeout: Maximum seconds for each LibreOffice process, including
|
|
42
|
+
the version check. Defaults to 120. Use None to disable.
|
|
36
43
|
|
|
37
44
|
Raises:
|
|
38
45
|
FileNotFoundError: If LibreOffice executable cannot be found.
|
|
39
|
-
ValueError: If
|
|
46
|
+
ValueError: If timeout is invalid or the version cannot be parsed.
|
|
47
|
+
RuntimeError: If LibreOffice is too old, fails to start, or times out.
|
|
40
48
|
"""
|
|
49
|
+
if timeout is not None and (not isfinite(timeout) or timeout <= 0):
|
|
50
|
+
raise ValueError("timeout must be a positive finite number or None.")
|
|
51
|
+
self.timeout = timeout
|
|
41
52
|
self.executable_path = self._resolve_executable_path(executable_path)
|
|
42
53
|
|
|
43
54
|
self._verify_version()
|
|
@@ -53,15 +64,15 @@ class LibreOfficeConverter:
|
|
|
53
64
|
executable = os.fspath(executable_path)
|
|
54
65
|
expanded = os.path.expanduser(executable)
|
|
55
66
|
candidate = Path(expanded)
|
|
56
|
-
candidate_str = str(candidate)
|
|
57
67
|
looks_like_path = (
|
|
58
|
-
|
|
59
|
-
or
|
|
60
|
-
or
|
|
68
|
+
isinstance(executable_path, Path)
|
|
69
|
+
or candidate.is_absolute()
|
|
70
|
+
or os.sep in expanded
|
|
71
|
+
or (os.altsep is not None and os.altsep in expanded)
|
|
61
72
|
)
|
|
62
73
|
if looks_like_path:
|
|
63
74
|
if candidate.is_file():
|
|
64
|
-
return candidate
|
|
75
|
+
return candidate.absolute()
|
|
65
76
|
raise FileNotFoundError(
|
|
66
77
|
f"LibreOffice executable not found at: {candidate}."
|
|
67
78
|
)
|
|
@@ -69,16 +80,20 @@ class LibreOfficeConverter:
|
|
|
69
80
|
resolved_executable = shutil.which(executable)
|
|
70
81
|
if resolved_executable is None:
|
|
71
82
|
raise FileNotFoundError(f"Can't find LibreOffice executable: {executable}.")
|
|
72
|
-
return Path(resolved_executable)
|
|
83
|
+
return Path(resolved_executable).absolute()
|
|
73
84
|
|
|
74
85
|
def _find_executable(self) -> Path | None:
|
|
75
86
|
"""Find LibreOffice executable in default locations."""
|
|
76
|
-
|
|
87
|
+
system = platform.system()
|
|
88
|
+
# Windows needs the console launcher to capture output and wait for exit.
|
|
89
|
+
names: tuple[str, ...] = ("soffice", "libreoffice")
|
|
90
|
+
if system == "Windows":
|
|
91
|
+
names = ("soffice.com", *names)
|
|
92
|
+
for name in names:
|
|
77
93
|
resolved = shutil.which(name)
|
|
78
94
|
if resolved is not None:
|
|
79
|
-
return Path(resolved)
|
|
95
|
+
return Path(resolved).absolute()
|
|
80
96
|
|
|
81
|
-
system = platform.system()
|
|
82
97
|
if system not in DEFAULT_PATHS:
|
|
83
98
|
raise RuntimeError(f"Unsupported operating system: {system}.")
|
|
84
99
|
|
|
@@ -88,34 +103,53 @@ class LibreOfficeConverter:
|
|
|
88
103
|
return candidate
|
|
89
104
|
return None
|
|
90
105
|
|
|
91
|
-
def
|
|
92
|
-
|
|
106
|
+
def _run_command(
|
|
107
|
+
self, cmd: list[str], action: str
|
|
108
|
+
) -> subprocess.CompletedProcess[str]:
|
|
109
|
+
"""Run LibreOffice with bounded execution and useful diagnostics."""
|
|
93
110
|
try:
|
|
94
|
-
|
|
95
|
-
|
|
111
|
+
return subprocess.run(
|
|
112
|
+
cmd,
|
|
96
113
|
capture_output=True,
|
|
97
114
|
text=True,
|
|
115
|
+
errors="replace",
|
|
98
116
|
check=True,
|
|
117
|
+
timeout=self.timeout,
|
|
99
118
|
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
raise ValueError(
|
|
105
|
-
f"Can't parse LibreOffice version from: {version_str}."
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
current_version = version.parse(match.group(1))
|
|
109
|
-
min_version = version.parse(MIN_VERSION)
|
|
110
|
-
|
|
111
|
-
if current_version < min_version:
|
|
112
|
-
raise RuntimeError(
|
|
113
|
-
"LibreOffice version "
|
|
114
|
-
f"{current_version} is below minimum required "
|
|
115
|
-
f"version {min_version}."
|
|
116
|
-
)
|
|
119
|
+
except subprocess.TimeoutExpired as e:
|
|
120
|
+
raise RuntimeError(
|
|
121
|
+
f"LibreOffice {action} timed out after {self.timeout} seconds."
|
|
122
|
+
) from e
|
|
117
123
|
except subprocess.CalledProcessError as e:
|
|
118
|
-
raise RuntimeError(
|
|
124
|
+
raise RuntimeError(
|
|
125
|
+
f"LibreOffice {action} failed (exit code {e.returncode}):\n"
|
|
126
|
+
f"Command output: {e.stdout}\n"
|
|
127
|
+
f"Error output: {e.stderr}"
|
|
128
|
+
) from e
|
|
129
|
+
except OSError as e:
|
|
130
|
+
raise RuntimeError(
|
|
131
|
+
f"Failed to run LibreOffice at {self.executable_path}: {e}"
|
|
132
|
+
) from e
|
|
133
|
+
|
|
134
|
+
def _verify_version(self) -> None:
|
|
135
|
+
"""Verify LibreOffice version meets minimum requirement."""
|
|
136
|
+
result = self._run_command(
|
|
137
|
+
[str(self.executable_path), "--version"], "version check"
|
|
138
|
+
)
|
|
139
|
+
version_str = result.stdout.strip()
|
|
140
|
+
match = re.search(r"LibreOffice\s+(\d+\.\d+(?:\.\d+)*)", version_str)
|
|
141
|
+
if not match:
|
|
142
|
+
raise ValueError(f"Can't parse LibreOffice version from: {version_str}.")
|
|
143
|
+
|
|
144
|
+
# LibreOffice uses numeric versions, including calendar versions (24+).
|
|
145
|
+
# Compare numerically without requiring the optional packaging library.
|
|
146
|
+
current_version = tuple(int(part) for part in match.group(1).split("."))
|
|
147
|
+
min_version = tuple(int(part) for part in MIN_VERSION.split("."))
|
|
148
|
+
if current_version < min_version:
|
|
149
|
+
raise RuntimeError(
|
|
150
|
+
f"LibreOffice version {match.group(1)} is below minimum required "
|
|
151
|
+
f"version {MIN_VERSION}."
|
|
152
|
+
)
|
|
119
153
|
|
|
120
154
|
def convert(
|
|
121
155
|
self,
|
|
@@ -142,16 +176,30 @@ class LibreOfficeConverter:
|
|
|
142
176
|
- `'html'`: HTML Document
|
|
143
177
|
- `'odt'`: OpenDocument Text
|
|
144
178
|
- `'txt'`: Plain Text
|
|
179
|
+
|
|
180
|
+
Also accepts LibreOffice's `extension:filter[:options]` syntax,
|
|
181
|
+
for example `'pdf:writer_pdf_Export'` or
|
|
182
|
+
`'txt:Text (encoded):UTF8'`. Filter names and options are passed
|
|
183
|
+
through unchanged; the extension determines the output filename.
|
|
145
184
|
overwrite: If `True`, overwrites existing files in output directory.
|
|
146
|
-
If `False`, raises error if output file already exists.
|
|
185
|
+
If `False`, raises error if output file already exists. Existing
|
|
186
|
+
output is preserved if LibreOffice fails to convert the input.
|
|
147
187
|
|
|
148
188
|
Returns:
|
|
149
189
|
Path | Sequence[Path]: For single file input, returns Path to the
|
|
150
190
|
converted file. For multiple files, returns list of Paths.
|
|
151
191
|
|
|
152
192
|
Raises:
|
|
193
|
+
FileNotFoundError: If an input file is missing or is not a file.
|
|
153
194
|
FileExistsError: If output file exists and overwrite=False.
|
|
154
|
-
|
|
195
|
+
ValueError: If format does not start with a valid file extension.
|
|
196
|
+
RuntimeError: If LibreOffice conversion fails or times out.
|
|
197
|
+
|
|
198
|
+
Note:
|
|
199
|
+
Each file is converted with a temporary, isolated LibreOffice user
|
|
200
|
+
profile, independent of an open desktop session or other conversions.
|
|
201
|
+
Personal LibreOffice settings and extensions are not used. Batch
|
|
202
|
+
inputs are processed sequentially, with a new process for each file.
|
|
155
203
|
|
|
156
204
|
Examples:
|
|
157
205
|
Single file conversion:
|
|
@@ -178,21 +226,26 @@ class LibreOfficeConverter:
|
|
|
178
226
|
print(f"Converted: {path}")
|
|
179
227
|
```
|
|
180
228
|
"""
|
|
181
|
-
|
|
182
|
-
if not
|
|
183
|
-
|
|
229
|
+
extension = format.split(":", 1)[0]
|
|
230
|
+
if not re.fullmatch(r"[A-Za-z0-9]+", extension):
|
|
231
|
+
raise ValueError(
|
|
232
|
+
"format must be an extension or extension:filter[:options], "
|
|
233
|
+
f"got {format!r}."
|
|
234
|
+
)
|
|
235
|
+
output_dir = Path(output_dir).expanduser()
|
|
236
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
184
237
|
|
|
185
238
|
# Handle single input file
|
|
186
239
|
if isinstance(input_files, (str, Path)):
|
|
187
|
-
input_path = Path(
|
|
188
|
-
if not input_path.
|
|
240
|
+
input_path = Path(input_files).expanduser()
|
|
241
|
+
if not input_path.is_file():
|
|
189
242
|
raise FileNotFoundError(f"Input file not found: {input_path}.")
|
|
190
243
|
return self._convert_single_file(input_path, output_dir, format, overwrite)
|
|
191
244
|
|
|
192
245
|
# Handle multiple input files
|
|
193
|
-
input_paths = [Path(
|
|
246
|
+
input_paths = [Path(f).expanduser() for f in input_files]
|
|
194
247
|
for path in input_paths:
|
|
195
|
-
if not path.
|
|
248
|
+
if not path.is_file():
|
|
196
249
|
raise FileNotFoundError(f"Input file not found: {path}.")
|
|
197
250
|
|
|
198
251
|
return [
|
|
@@ -204,7 +257,8 @@ class LibreOfficeConverter:
|
|
|
204
257
|
self, input_file: Path, output_dir: Path, format: str, overwrite: bool
|
|
205
258
|
) -> Path:
|
|
206
259
|
"""Convert a single file using LibreOffice."""
|
|
207
|
-
|
|
260
|
+
extension = format.split(":", 1)[0]
|
|
261
|
+
output_file = output_dir / f"{input_file.stem}.{extension}"
|
|
208
262
|
|
|
209
263
|
if output_file.exists() and not overwrite:
|
|
210
264
|
raise FileExistsError(
|
|
@@ -212,33 +266,51 @@ class LibreOfficeConverter:
|
|
|
212
266
|
"Use overwrite=True to force."
|
|
213
267
|
)
|
|
214
268
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
"
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
269
|
+
# Stage output so an old file cannot be mistaken for a successful export.
|
|
270
|
+
# Keep it on the destination filesystem for the final file replacement.
|
|
271
|
+
with tempfile.TemporaryDirectory(prefix=".rtflite-", dir=output_dir) as tmpdir:
|
|
272
|
+
work_dir = Path(tmpdir).resolve()
|
|
273
|
+
converted_dir = work_dir / "output"
|
|
274
|
+
converted_dir.mkdir()
|
|
275
|
+
cmd = [
|
|
276
|
+
str(self.executable_path),
|
|
277
|
+
f"-env:UserInstallation={(work_dir / 'profile').as_uri()}",
|
|
278
|
+
"--headless",
|
|
279
|
+
"--nologo",
|
|
280
|
+
"--norestore",
|
|
281
|
+
"--convert-to",
|
|
282
|
+
format,
|
|
283
|
+
"--outdir",
|
|
284
|
+
str(converted_dir),
|
|
285
|
+
str(input_file.absolute()),
|
|
286
|
+
]
|
|
287
|
+
result = self._run_command(cmd, "conversion")
|
|
288
|
+
converted_file = converted_dir / output_file.name
|
|
289
|
+
if not converted_file.is_file():
|
|
231
290
|
raise RuntimeError(
|
|
232
291
|
f"Conversion failed: Output file not created.\n"
|
|
233
292
|
f"Command output: {result.stdout}\n"
|
|
234
293
|
f"Error output: {result.stderr}"
|
|
235
294
|
)
|
|
236
295
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
296
|
+
# Include companion files/directories created by formats such as HTML.
|
|
297
|
+
generated_paths = list(converted_dir.iterdir())
|
|
298
|
+
if not overwrite:
|
|
299
|
+
for path in generated_paths:
|
|
300
|
+
destination = output_dir / path.name
|
|
301
|
+
if destination.exists():
|
|
302
|
+
raise FileExistsError(
|
|
303
|
+
f"Output file already exists: {destination}. "
|
|
304
|
+
"Use overwrite=True to force."
|
|
305
|
+
)
|
|
306
|
+
for path in generated_paths:
|
|
307
|
+
if path == converted_file:
|
|
308
|
+
continue
|
|
309
|
+
destination = output_dir / path.name
|
|
310
|
+
if path.is_dir():
|
|
311
|
+
shutil.copytree(path, destination, dirs_exist_ok=overwrite)
|
|
312
|
+
else:
|
|
313
|
+
path.replace(destination)
|
|
314
|
+
converted_file.replace(output_file)
|
|
315
|
+
|
|
316
|
+
return output_file
|
|
@@ -574,9 +574,9 @@ class RTFDocument(BaseModel):
|
|
|
574
574
|
```
|
|
575
575
|
|
|
576
576
|
Note:
|
|
577
|
-
LibreOffice may create
|
|
578
|
-
|
|
579
|
-
|
|
577
|
+
LibreOffice may create companion image files or directories for
|
|
578
|
+
embedded resources. These are saved alongside the requested output
|
|
579
|
+
path and must be kept with the HTML file.
|
|
580
580
|
"""
|
|
581
581
|
target_path = Path(file_path).expanduser()
|
|
582
582
|
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -602,12 +602,13 @@ class RTFDocument(BaseModel):
|
|
|
602
602
|
f"{type(converted)!r} with value {converted!r}."
|
|
603
603
|
)
|
|
604
604
|
html_path = converted
|
|
605
|
-
resources_dir = html_path.with_name(f"{html_path.name}_files")
|
|
606
605
|
shutil.move(str(html_path), target_path)
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
606
|
+
for resource in Path(convert_tmpdir).iterdir():
|
|
607
|
+
destination = target_path.parent / resource.name
|
|
608
|
+
if resource.is_dir():
|
|
609
|
+
shutil.copytree(resource, destination, dirs_exist_ok=True)
|
|
610
|
+
else:
|
|
611
|
+
shutil.move(str(resource), destination)
|
|
611
612
|
|
|
612
613
|
print(target_path)
|
|
613
614
|
|
rtflite-2.5.4/.gitignore
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Python-generated files
|
|
2
|
-
__pycache__/
|
|
3
|
-
*.py[oc]
|
|
4
|
-
build/
|
|
5
|
-
dist/
|
|
6
|
-
site/
|
|
7
|
-
wheels/
|
|
8
|
-
*.egg-info
|
|
9
|
-
*.xml
|
|
10
|
-
|
|
11
|
-
# Virtual environments
|
|
12
|
-
.venv
|
|
13
|
-
|
|
14
|
-
# OS
|
|
15
|
-
.DS_Store
|
|
16
|
-
|
|
17
|
-
# pytest
|
|
18
|
-
.coverage
|
|
19
|
-
|
|
20
|
-
# r2rtf
|
|
21
|
-
Rplots.pdf
|
|
22
|
-
|
|
23
|
-
# markdown-exec artifacts
|
|
24
|
-
/docs/articles/pdf/
|
|
25
|
-
/docs/articles/images/
|
|
26
|
-
|
|
27
|
-
# Generated docx artifacts (not bitwise deterministic)
|
|
28
|
-
/docs/articles/rtf/*.docx
|
rtflite-2.5.4/.python-version
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3.14.3
|
rtflite-2.5.4/CHANGELOG.md
DELETED
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## rtflite 2.5.4
|
|
4
|
-
|
|
5
|
-
### Bug fixes
|
|
6
|
-
|
|
7
|
-
- Normalize `rtf_body` sequences at the document boundary and fix mypy
|
|
8
|
-
type narrowing for multi-section `rtf_body` handling (#202).
|
|
9
|
-
`RTFDocument.rtf_body` is now typed as `RTFBody | list[RTFBody] | None`,
|
|
10
|
-
with a field validator that converts non-list `Sequence[RTFBody]` inputs
|
|
11
|
-
to `list[RTFBody]`. Single-section documents now reject multi-body
|
|
12
|
-
containers with a clear error instead of failing with attribute access
|
|
13
|
-
errors downstream.
|
|
14
|
-
|
|
15
|
-
### Dependencies
|
|
16
|
-
|
|
17
|
-
- Remove pandas and pyarrow from development dependencies (#194).
|
|
18
|
-
|
|
19
|
-
### Maintenance
|
|
20
|
-
|
|
21
|
-
- Add a script to automate sorting dependencies in `pyproject.toml` and
|
|
22
|
-
re-sort pyproject dependencies using the script (#193).
|
|
23
|
-
|
|
24
|
-
## rtflite 2.5.3
|
|
25
|
-
|
|
26
|
-
### Documentation
|
|
27
|
-
|
|
28
|
-
- Update mkdocstrings settings to render Pydantic field metadata
|
|
29
|
-
via `griffe-pydantic` in API reference docs (#189).
|
|
30
|
-
- Declare `RTFDocument._table_space` as a Pydantic private attribute to
|
|
31
|
-
prevent mkdocstrings render errors in static mode (#189).
|
|
32
|
-
- Add Code Wiki link as README badge and site navigation link.
|
|
33
|
-
This is a useful resource that helps developers and AI agents
|
|
34
|
-
understand the codebase fast (#191).
|
|
35
|
-
|
|
36
|
-
## rtflite 2.5.2
|
|
37
|
-
|
|
38
|
-
### Documentation
|
|
39
|
-
|
|
40
|
-
- Migrated documentation site to use Zensical (#184, #185).
|
|
41
|
-
|
|
42
|
-
## rtflite 2.5.1
|
|
43
|
-
|
|
44
|
-
### Testing
|
|
45
|
-
|
|
46
|
-
- Migrated the RTF snapshot tests implementation to a proper solution
|
|
47
|
-
`pytest-r-snapshot` to reduce boilerplate code and improve maintainability.
|
|
48
|
-
The legacy fixture generation workflow is removed and the snapshots are
|
|
49
|
-
stored under the standard location `tests/__r_snapshots__/` (#181).
|
|
50
|
-
|
|
51
|
-
## rtflite 2.5.0
|
|
52
|
-
|
|
53
|
-
### New features
|
|
54
|
-
|
|
55
|
-
- Added `RTFDocument.write_html` and `RTFDocument.write_pdf` for exporting RTF
|
|
56
|
-
documents to HTML and PDF via LibreOffice, matching the `write_docx`
|
|
57
|
-
conversion workflow (#176).
|
|
58
|
-
|
|
59
|
-
### Testing
|
|
60
|
-
|
|
61
|
-
- Added parameterized tests covering DOCX, HTML, and PDF exports, with a
|
|
62
|
-
new `pdf` extra (`pypdf`) for PDF text extraction.
|
|
63
|
-
Improved LibreOffice availability checks to skip integration tests
|
|
64
|
-
when conversion is not working (#176).
|
|
65
|
-
|
|
66
|
-
## rtflite 2.4.0
|
|
67
|
-
|
|
68
|
-
### Breaking changes
|
|
69
|
-
|
|
70
|
-
- Removed the `executable_path` argument from `RTFDocument.write_docx`; pass
|
|
71
|
-
`converter=LibreOfficeConverter(executable_path=...)` instead (#172).
|
|
72
|
-
|
|
73
|
-
### Improvements
|
|
74
|
-
|
|
75
|
-
- `RTFDocument.write_docx` now accepts a `converter=` instance to configure
|
|
76
|
-
LibreOffice invocation (including custom executable paths) and to enable
|
|
77
|
-
reusing a converter across multiple conversions (#172).
|
|
78
|
-
|
|
79
|
-
### Converters
|
|
80
|
-
|
|
81
|
-
- `LibreOfficeConverter(executable_path=...)` now accepts `Path` objects
|
|
82
|
-
and resolves executable names via `PATH` when given
|
|
83
|
-
(for example, `"soffice"`) (#172).
|
|
84
|
-
|
|
85
|
-
## rtflite 2.3.1
|
|
86
|
-
|
|
87
|
-
### Improvements
|
|
88
|
-
|
|
89
|
-
- Added `executable_path` parameter to `write_docx()` method (#170).
|
|
90
|
-
- Added `/tmp/soffice` and `/tmp/libreoffice` paths for Linux (#170).
|
|
91
|
-
|
|
92
|
-
## rtflite 2.3.0
|
|
93
|
-
|
|
94
|
-
### New features
|
|
95
|
-
|
|
96
|
-
- Added `concatenate_docx` function to merge DOCX outputs without manual
|
|
97
|
-
field refreshes, preserving per-section orientation (#160).
|
|
98
|
-
|
|
99
|
-
### Testing
|
|
100
|
-
|
|
101
|
-
- Added DOCX concatenation coverage and centralized optional dependency
|
|
102
|
-
skip markers for `python-docx` and LibreOffice to keep tests gated
|
|
103
|
-
appropriately (#160).
|
|
104
|
-
|
|
105
|
-
### Documentation
|
|
106
|
-
|
|
107
|
-
- Updated the assembly article to use `concatenate_docx` in code examples and
|
|
108
|
-
added a reference page for assemble function to the documentation site (#160).
|
|
109
|
-
|
|
110
|
-
## rtflite 2.2.0
|
|
111
|
-
|
|
112
|
-
### New features
|
|
113
|
-
|
|
114
|
-
- Added `RTFDocument.write_docx` to export tables as DOCX via LibreOffice,
|
|
115
|
-
with `str`/`Path` input support and automatic parent directory creation (#156).
|
|
116
|
-
- Improved `write_rtf` to accept `Path` inputs and create missing output
|
|
117
|
-
directories (#156).
|
|
118
|
-
|
|
119
|
-
### Documentation
|
|
120
|
-
|
|
121
|
-
- Expanded DOCX assembly guidance with toggle field caveats and `python-docx`
|
|
122
|
-
concatenation examples (#157).
|
|
123
|
-
- Documented installing the `docx` extra in the README and assemble article,
|
|
124
|
-
including `uv sync --extra docx` for developers (#155).
|
|
125
|
-
|
|
126
|
-
## rtflite 2.1.1
|
|
127
|
-
|
|
128
|
-
### Bug fixes
|
|
129
|
-
|
|
130
|
-
- Reduced packaging files size by excluding unnecessary files and directories (#152).
|
|
131
|
-
- Fixed an issue when paginating tables with `page_by` and `subline_by` (#152).
|
|
132
|
-
|
|
133
|
-
## rtflite 2.1.0
|
|
134
|
-
|
|
135
|
-
### New features
|
|
136
|
-
|
|
137
|
-
- Added `assemble_rtf` and `assemble_docx` functions for RTF and DOCX assembly (#142).
|
|
138
|
-
|
|
139
|
-
### Bug fixes
|
|
140
|
-
|
|
141
|
-
- Fixed an issue where `RTFPage` orientation was not correctly respected during assembly in certain edge cases (#141).
|
|
142
|
-
|
|
143
|
-
## rtflite 2.0.0
|
|
144
|
-
|
|
145
|
-
### Breaking changes
|
|
146
|
-
|
|
147
|
-
- Removed legacy pagination/encoding APIs, including `ContentDistributor`,
|
|
148
|
-
`PageDict`/`AdvancedPaginationService`, and the `SinglePageStrategy`/
|
|
149
|
-
`PaginatedStrategy` classes; dropped backwards-compatibility helpers
|
|
150
|
-
such as `text_convert` and `get_color_index` (#138).
|
|
151
|
-
|
|
152
|
-
### Refactors
|
|
153
|
-
|
|
154
|
-
- Introduced a unified rendering pipeline (`UnifiedRTFEncoder`) with a strategy
|
|
155
|
-
registry, page feature processor, and renderer that handle pagination,
|
|
156
|
-
borders, and page headers consistently, including combined `page_by` and
|
|
157
|
-
`subline_by` grouping (#138).
|
|
158
|
-
- Simplified public exports by re-exporting `RTFEncodingEngine`,
|
|
159
|
-
`TableAttributes`, `RTFSubline`, and `get_string_width` from the top-level
|
|
160
|
-
package while reorganizing core config/constants imports (#138).
|
|
161
|
-
|
|
162
|
-
### Testing and documentation
|
|
163
|
-
|
|
164
|
-
- Added regression tests for combined grouping and `page_by` column alignment,
|
|
165
|
-
and removed obsolete advanced pagination tests; cleaned up RTF doc fixtures
|
|
166
|
-
and trimmed pagination reference docs to match the new architecture (#138).
|
|
167
|
-
|
|
168
|
-
## rtflite 1.2.0
|
|
169
|
-
|
|
170
|
-
### Bug fixes
|
|
171
|
-
|
|
172
|
-
- Fixed `page_by` pagination when using `RTFPage(orientation="landscape")`,
|
|
173
|
-
covering single- and multi-page tables and aligning documentation examples
|
|
174
|
-
(#128, #131, #134).
|
|
175
|
-
|
|
176
|
-
### Maintenance
|
|
177
|
-
|
|
178
|
-
- Added GitHub Actions workflow to run `ruff check` for code linting,
|
|
179
|
-
updated GitHub Actions workflows to use `actions/checkout@v6`,
|
|
180
|
-
and updated badges in `README.md` (#133).
|
|
181
|
-
|
|
182
|
-
## rtflite 1.1.1
|
|
183
|
-
|
|
184
|
-
### New features
|
|
185
|
-
|
|
186
|
-
- Enhanced group-by divider filtering functionality to support the
|
|
187
|
-
`-----` syntax (#118).
|
|
188
|
-
|
|
189
|
-
### Bug fixes
|
|
190
|
-
|
|
191
|
-
- Fixed text conversion issue for greater than or equal (>=) and less than or
|
|
192
|
-
equal (<=) symbols (#119).
|
|
193
|
-
|
|
194
|
-
## rtflite 1.1.0
|
|
195
|
-
|
|
196
|
-
### Python version support
|
|
197
|
-
|
|
198
|
-
- Added Python 3.14 support by conditionally requiring pyarrow >= 22.0.0
|
|
199
|
-
under Python 3.14 (#114).
|
|
200
|
-
|
|
201
|
-
### Linting and typing
|
|
202
|
-
|
|
203
|
-
- Added ruff linter configuration to `pyproject.toml` with popular rule sets
|
|
204
|
-
and fixed all linting issues (#115).
|
|
205
|
-
- Refactored type annotations to use built-in generics and abstract base classes
|
|
206
|
-
following modern typing best practices (#107).
|
|
207
|
-
|
|
208
|
-
### Documentation
|
|
209
|
-
|
|
210
|
-
- Added pharmaverse badge to the README (#108).
|
|
211
|
-
- Added `AGENTS.md` with guidelines for AI coding agents (#109).
|
|
212
|
-
|
|
213
|
-
### Maintenance
|
|
214
|
-
|
|
215
|
-
- Updated GitHub Actions workflows to use the latest `checkout` and
|
|
216
|
-
`setup-python` versions (#114).
|
|
217
|
-
- Refactored the logo generation script to use ImageMagick, removing the
|
|
218
|
-
R and hexSticker dependency (#111).
|
|
219
|
-
|
|
220
|
-
## rtflite 1.0.2
|
|
221
|
-
|
|
222
|
-
### Typing
|
|
223
|
-
|
|
224
|
-
- Adopted modern typing best practices: use `|` unions instead of
|
|
225
|
-
`Union`/`Optional`, and built-in generics instead of `typing` aliases (#95).
|
|
226
|
-
- Resolved all mypy issues; type checks now pass cleanly (#97, #99, #101).
|
|
227
|
-
- Added a mypy GitHub Actions workflow for continuous type checking (#100).
|
|
228
|
-
|
|
229
|
-
### Testing
|
|
230
|
-
|
|
231
|
-
- Added a developer script to compare current RTF outputs with snapshots
|
|
232
|
-
generated from documentation site articles (#102).
|
|
233
|
-
|
|
234
|
-
### Documentation
|
|
235
|
-
|
|
236
|
-
- Updated `CLAUDE.md` to replace outdated `.qmd`-based rendering instructions
|
|
237
|
-
with the markdown-exec approach (#93).
|
|
238
|
-
|
|
239
|
-
## rtflite 1.0.1
|
|
240
|
-
|
|
241
|
-
### Bug fixes
|
|
242
|
-
|
|
243
|
-
- Fixed hard-coded font in pagination calculations to properly use
|
|
244
|
-
user-selected fonts (#91).
|
|
245
|
-
|
|
246
|
-
### Dependencies
|
|
247
|
-
|
|
248
|
-
- Lowered minimum Pillow version to 8.0.0 with automatic font size type
|
|
249
|
-
coercion to `int` for Pillow < 10.0.0 compatibility (#89).
|
|
250
|
-
|
|
251
|
-
### Documentation
|
|
252
|
-
|
|
253
|
-
- Improved readability and technical accuracy of documentation (#85, #86, #87).
|
|
254
|
-
|
|
255
|
-
## rtflite 1.0.0
|
|
256
|
-
|
|
257
|
-
This major release marks rtflite as production-ready for table, listing,
|
|
258
|
-
and figure generation in RTF format.
|
|
259
|
-
It introduces advanced pagination features, enhanced group handling,
|
|
260
|
-
complete color support, and significant architectural improvements
|
|
261
|
-
for better maintainability and performance.
|
|
262
|
-
|
|
263
|
-
### New features
|
|
264
|
-
|
|
265
|
-
- **Advanced pagination features**
|
|
266
|
-
- Added the `subline_by` parameter for creating paragraph headers before each page group.
|
|
267
|
-
- Enhanced `group_by` functionality with hierarchical value suppression within groups.
|
|
268
|
-
- Implemented page context restoration for multi-page tables with `group_by`.
|
|
269
|
-
|
|
270
|
-
- **Enhanced color system**
|
|
271
|
-
- Complete 657-color support with full r2rtf R package compatibility.
|
|
272
|
-
|
|
273
|
-
- **Text conversion improvements**
|
|
274
|
-
- Text conversion (LaTeX to Unicode) enabled by default for all components.
|
|
275
|
-
- Better handling of special characters and symbols.
|
|
276
|
-
- Enhanced validation for text conversion operations.
|
|
277
|
-
|
|
278
|
-
- **Table formatting**
|
|
279
|
-
- Added the `as_table` parameter for `RTFFootnote` and `RTFSource` components.
|
|
280
|
-
- Auto-inheritance of `col_rel_width` from `rtf_body` to `rtf_column_header`.
|
|
281
|
-
- Improved handling of table borders and footnote placement.
|
|
282
|
-
|
|
283
|
-
### Architecture improvements
|
|
284
|
-
|
|
285
|
-
- **Service-oriented architecture**
|
|
286
|
-
- Introduced dedicated service layer for complex operations.
|
|
287
|
-
- Implemented strategy pattern for encoding (`SinglePageStrategy`, `PaginatedStrategy`).
|
|
288
|
-
- Created `RTFEncodingEngine` for strategy orchestration.
|
|
289
|
-
|
|
290
|
-
- **Code organization**
|
|
291
|
-
- Consolidated constants and eliminated magic numbers throughout the codebase.
|
|
292
|
-
- Method decomposition and improved input validation.
|
|
293
|
-
- Cleaner public interfaces with thorough error handling.
|
|
294
|
-
|
|
295
|
-
### Dependency changes
|
|
296
|
-
|
|
297
|
-
- Removed numpy and pandas as hard dependencies.
|
|
298
|
-
- Moved pyarrow to development dependencies.
|
|
299
|
-
- Now uses narwhals for DataFrame abstraction.
|
|
300
|
-
- Prefer polars as the primary DataFrame interface.
|
|
301
|
-
|
|
302
|
-
### Documentation
|
|
303
|
-
|
|
304
|
-
- Added vignette-style articles to document the new features.
|
|
305
|
-
- Reorganized API reference for better user experience.
|
|
306
|
-
- Renamed documentation files to use hyphens consistently.
|
|
307
|
-
- Updated all examples to use modern best practices.
|
|
308
|
-
- Fixed Polars `DataOrientationWarning` in documentation examples.
|
|
309
|
-
|
|
310
|
-
### Testing
|
|
311
|
-
|
|
312
|
-
- Added single-page RTF tests with fixtures generated by r2rtf.
|
|
313
|
-
- Added extensive multi-page tests for the `as_table` feature.
|
|
314
|
-
|
|
315
|
-
## rtflite 0.1.3
|
|
316
|
-
|
|
317
|
-
### Documentation
|
|
318
|
-
|
|
319
|
-
- Add contributing guidelines to make it easy for onboarding new developers
|
|
320
|
-
to the recommended development workflow (#25).
|
|
321
|
-
- Update `README.md` to add hyperlink to the R package r2rtf (#24).
|
|
322
|
-
|
|
323
|
-
### Maintenance
|
|
324
|
-
|
|
325
|
-
- Remove the strict version requirement for the development dependency
|
|
326
|
-
mkdocs-autorefs (#21).
|
|
327
|
-
|
|
328
|
-
## rtflite 0.1.2
|
|
329
|
-
|
|
330
|
-
### Maintenance
|
|
331
|
-
|
|
332
|
-
- Manage project with uv (#19).
|
|
333
|
-
- Update the logo image generation workflow to use web fonts (#18).
|
|
334
|
-
|
|
335
|
-
## rtflite 0.1.1
|
|
336
|
-
|
|
337
|
-
### Documentation
|
|
338
|
-
|
|
339
|
-
- Use absolute URL to replace relative path for logo image in `README.md`,
|
|
340
|
-
for proper rendering on PyPI (#16).
|
|
341
|
-
|
|
342
|
-
## rtflite 0.1.0
|
|
343
|
-
|
|
344
|
-
### New features
|
|
345
|
-
|
|
346
|
-
- Introduced core RTF document components, such as `RTFDocument`, `RTFPage`,
|
|
347
|
-
`RTFTitle`, `RTFColumnHeader`, and `RTFBody`. These classes establish the
|
|
348
|
-
foundation for composing structured RTF documents with a text encoding
|
|
349
|
-
pipeline. Use Pydantic for data validation.
|
|
350
|
-
- Implemented string width calculation using Pillow with metric-compatible fonts.
|
|
351
|
-
This will be incorporated in the pagination and layout algorithms in
|
|
352
|
-
future releases.
|
|
353
|
-
- Implemented a LibreOffice-based document converter for RTF to PDF conversion
|
|
354
|
-
with automatic LibreOffice detection mechanisms under Linux, macOS, and Windows.
|
|
355
|
-
|
|
356
|
-
### Documentation
|
|
357
|
-
|
|
358
|
-
- Added an article on creating baseline characteristics tables.
|
|
359
|
-
- Integrated code coverage reports via pytest-cov into the documentation site.
|
rtflite-2.5.4/LICENSE
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
Copyright (c) 2025 pharmaverse
|
|
2
|
-
|
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
-
a copy of this software and associated documentation files (the
|
|
5
|
-
"Software"), to deal in the Software without restriction, including
|
|
6
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
-
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
-
the following conditions:
|
|
10
|
-
|
|
11
|
-
The above copyright notice and this permission notice shall be
|
|
12
|
-
included in all copies or substantial portions of the Software.
|
|
13
|
-
|
|
14
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
rtflite uses third-party Python packages which may be distributed under
|
|
2
|
-
different licenses. We have listed all of these third party packages and
|
|
3
|
-
their licenses below. However, the most up-to-date information can be found
|
|
4
|
-
via inspecting the dependencies field in the pyproject.toml file.
|
|
5
|
-
|
|
6
|
-
You must agree to the terms of these licenses, in addition to the rtflite
|
|
7
|
-
source code license, in order to use this software.
|
|
8
|
-
|
|
9
|
-
--------------------------------------------------
|
|
10
|
-
Third party Python packages listed by license type
|
|
11
|
-
[Format: Name - URL]
|
|
12
|
-
--------------------------------------------------
|
|
13
|
-
|
|
14
|
-
MIT License (https://opensource.org/license/mit)
|
|
15
|
-
* narwhals - https://github.com/narwhals-dev/narwhals/blob/main/LICENSE.md
|
|
16
|
-
* polars - https://github.com/pola-rs/polars/blob/main/LICENSE
|
|
17
|
-
* pydantic - https://github.com/pydantic/pydantic/blob/main/LICENSE
|
|
18
|
-
|
|
19
|
-
MIT-CMU License (https://opensource.org/license/cmu-license)
|
|
20
|
-
* pillow - https://github.com/python-pillow/Pillow/blob/main/LICENSE
|
|
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
|