rtflite 2.2.0__tar.gz → 2.3.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.2.0 → rtflite-2.3.0}/CHANGELOG.md +18 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/PKG-INFO +1 -1
- {rtflite-2.2.0 → rtflite-2.3.0}/pyproject.toml +1 -1
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/__init__.py +2 -1
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/assemble.py +102 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/.gitignore +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/.python-version +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/LICENSE +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/LICENSES_THIRD_PARTY +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/README.md +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/attributes.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/convert.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/core/__init__.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/core/config.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/core/constants.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/data/HAMD17.parquet +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/data/__init__.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/data/adae.parquet +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/data/adsl.parquet +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/data/baseline.parquet +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/data/tbl1.parquet +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/data/tbl2.parquet +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/data/tbl3.parquet +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/dictionary/__init__.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/dictionary/color_table.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/dictionary/libreoffice.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/dictionary/unicode_latex.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/encode.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/encoding/__init__.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/encoding/base.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/encoding/engine.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/encoding/renderer.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/encoding/unified_encoder.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/figure.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/fonts/README.md +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/fonts/__init__.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/fonts/cros/Caladea-Regular.ttf +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/fonts/cros/Carlito-Regular.ttf +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/fonts/cros/Gelasio-Regular.ttf +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/fonts/liberation/LiberationMono-Regular.ttf +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/fonts/liberation/LiberationSans-Regular.ttf +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/fonts/liberation/LiberationSerif-Regular.ttf +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/fonts_mapping.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/input.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/pagination/__init__.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/pagination/core.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/pagination/processor.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/pagination/strategies/__init__.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/pagination/strategies/base.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/pagination/strategies/defaults.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/pagination/strategies/grouping.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/pagination/strategies/registry.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/row.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/rtf/__init__.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/rtf/syntax.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/services/__init__.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/services/color_service.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/services/document_service.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/services/encoding_service.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/services/figure_service.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/services/grouping_service.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/services/text_conversion_service.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/strwidth.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/text_conversion/__init__.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/text_conversion/converter.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/text_conversion/symbols.py +0 -0
- {rtflite-2.2.0 → rtflite-2.3.0}/src/rtflite/type_guards.py +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## rtflite 2.3.0
|
|
4
|
+
|
|
5
|
+
### New features
|
|
6
|
+
|
|
7
|
+
- Added `concatenate_docx` function to merge DOCX outputs without manual
|
|
8
|
+
field refreshes, preserving per-section orientation (#160).
|
|
9
|
+
|
|
10
|
+
### Testing
|
|
11
|
+
|
|
12
|
+
- Added DOCX concatenation coverage and centralized optional dependency
|
|
13
|
+
skip markers for `python-docx` and LibreOffice to keep tests gated
|
|
14
|
+
appropriately (#160).
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- Updated the assembly article to use `concatenate_docx` in code examples and
|
|
19
|
+
added a reference page for assemble function to the mkdocs site (#160).
|
|
20
|
+
|
|
3
21
|
## rtflite 2.2.0
|
|
4
22
|
|
|
5
23
|
### New features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""rtflite: A Python library for creating RTF documents."""
|
|
2
2
|
|
|
3
|
-
from .assemble import assemble_docx, assemble_rtf
|
|
3
|
+
from .assemble import assemble_docx, assemble_rtf, concatenate_docx
|
|
4
4
|
from .attributes import TableAttributes
|
|
5
5
|
from .convert import LibreOfficeConverter
|
|
6
6
|
from .core.config import RTFConfiguration
|
|
@@ -46,4 +46,5 @@ __all__ = [
|
|
|
46
46
|
"LibreOfficeConverter",
|
|
47
47
|
"assemble_rtf",
|
|
48
48
|
"assemble_docx",
|
|
49
|
+
"concatenate_docx",
|
|
49
50
|
]
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
"""Assemble multiple RTF files into a single RTF or DOCX file."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
10
|
+
from docx.document import Document as DocxDocument
|
|
11
|
+
from docx.section import Section
|
|
4
12
|
|
|
5
13
|
# from .input import RTFPage # Unused
|
|
6
14
|
|
|
@@ -197,3 +205,97 @@ def _add_field(paragraph, field_code):
|
|
|
197
205
|
fldChar = OxmlElement("w:fldChar")
|
|
198
206
|
fldChar.set(qn("w:fldCharType"), "end")
|
|
199
207
|
r.append(fldChar)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def concatenate_docx(
|
|
211
|
+
input_files: Sequence[str | os.PathLike[str]],
|
|
212
|
+
output_file: str | os.PathLike[str],
|
|
213
|
+
landscape: bool | Sequence[bool] = False,
|
|
214
|
+
) -> None:
|
|
215
|
+
"""Concatenate DOCX files without relying on Word field toggles.
|
|
216
|
+
|
|
217
|
+
This helper is useful when `RTFDocument.write_docx` already produced DOCX
|
|
218
|
+
files and you need to stitch them together into a single document that can
|
|
219
|
+
be distributed without refreshing fields in Microsoft Word.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
input_files: Ordered collection of DOCX file paths to combine. The
|
|
223
|
+
first document becomes the base; subsequent documents are appended
|
|
224
|
+
as new sections.
|
|
225
|
+
output_file: Path to the combined DOCX file.
|
|
226
|
+
landscape: Whether each appended section should be landscape. Accepts
|
|
227
|
+
a single boolean applied to every section or a list/tuple matching
|
|
228
|
+
``input_files``.
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
ImportError: If ``python-docx`` is not installed.
|
|
232
|
+
ValueError: If ``input_files`` is empty or the ``landscape`` list length
|
|
233
|
+
does not match ``input_files``.
|
|
234
|
+
FileNotFoundError: If any input file is missing.
|
|
235
|
+
"""
|
|
236
|
+
try:
|
|
237
|
+
from docx import Document # type: ignore
|
|
238
|
+
from docx.enum.section import WD_SECTION # type: ignore
|
|
239
|
+
except ImportError as exc:
|
|
240
|
+
raise ImportError(
|
|
241
|
+
"python-docx is required for concatenate_docx. "
|
|
242
|
+
"Install it with: pip install 'rtflite[docx]'"
|
|
243
|
+
) from exc
|
|
244
|
+
|
|
245
|
+
paths = [Path(path).expanduser() for path in input_files]
|
|
246
|
+
if not paths:
|
|
247
|
+
raise ValueError("Input files list cannot be empty")
|
|
248
|
+
|
|
249
|
+
missing_files = [str(path) for path in paths if not path.exists()]
|
|
250
|
+
if missing_files:
|
|
251
|
+
raise FileNotFoundError(f"Missing files: {', '.join(missing_files)}")
|
|
252
|
+
|
|
253
|
+
orientation_flags = _coerce_landscape_flags(landscape, len(paths))
|
|
254
|
+
|
|
255
|
+
combined_doc = Document(str(paths[0]))
|
|
256
|
+
_set_section_orientation(combined_doc.sections[0], orientation_flags[0])
|
|
257
|
+
|
|
258
|
+
for source_path, is_landscape in zip(paths[1:], orientation_flags[1:], strict=True):
|
|
259
|
+
combined_doc.add_section(WD_SECTION.NEW_PAGE)
|
|
260
|
+
_set_section_orientation(combined_doc.sections[-1], is_landscape)
|
|
261
|
+
_append_document_body(combined_doc, Document(str(source_path)))
|
|
262
|
+
|
|
263
|
+
output_path = Path(output_file).expanduser()
|
|
264
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
265
|
+
combined_doc.save(str(output_path))
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _coerce_landscape_flags(
|
|
269
|
+
landscape: bool | Sequence[bool],
|
|
270
|
+
expected_length: int,
|
|
271
|
+
) -> list[bool]:
|
|
272
|
+
"""Normalize the ``landscape`` argument to a list and validate its length."""
|
|
273
|
+
if isinstance(landscape, bool):
|
|
274
|
+
return [landscape] * expected_length
|
|
275
|
+
|
|
276
|
+
flags = list(landscape)
|
|
277
|
+
if len(flags) != expected_length:
|
|
278
|
+
raise ValueError("Length of landscape list must match input files")
|
|
279
|
+
|
|
280
|
+
return flags
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _set_section_orientation(section: "Section", landscape: bool) -> None:
|
|
284
|
+
"""Set section orientation and swap dimensions if needed."""
|
|
285
|
+
from docx.enum.section import WD_ORIENT # type: ignore
|
|
286
|
+
|
|
287
|
+
section.orientation = WD_ORIENT.LANDSCAPE if landscape else WD_ORIENT.PORTRAIT
|
|
288
|
+
width, height = section.page_width, section.page_height
|
|
289
|
+
if width is None or height is None:
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
if (landscape and width < height) or (not landscape and width > height):
|
|
293
|
+
section.page_width, section.page_height = height, width
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _append_document_body(target: "DocxDocument", source: "DocxDocument") -> None:
|
|
297
|
+
"""Copy body content from ``source`` into ``target`` without section props."""
|
|
298
|
+
for element in list(source.element.body):
|
|
299
|
+
if element.tag.endswith("}sectPr"):
|
|
300
|
+
continue
|
|
301
|
+
target.element.body.append(deepcopy(element))
|
|
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
|