pylopdf 0.4.1__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.
- pylopdf-0.4.1/LICENSE +21 -0
- pylopdf-0.4.1/PKG-INFO +157 -0
- pylopdf-0.4.1/README.md +134 -0
- pylopdf-0.4.1/pyproject.toml +82 -0
- pylopdf-0.4.1/rust/Cargo.lock +1396 -0
- pylopdf-0.4.1/rust/Cargo.toml +24 -0
- pylopdf-0.4.1/rust/README.md +134 -0
- pylopdf-0.4.1/rust/src/document.rs +413 -0
- pylopdf-0.4.1/rust/src/lib.rs +9 -0
- pylopdf-0.4.1/src/pylopdf/__init__.py +183 -0
- pylopdf-0.4.1/src/pylopdf/py.typed +0 -0
- pylopdf-0.4.1/src/pylopdf/pylopdf_core.pyi +22 -0
pylopdf-0.4.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Yusuke Hayashi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pylopdf-0.4.1/PKG-INFO
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pylopdf
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Classifier: Programming Language :: Rust
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Summary: PDF editing and rendering for Python, powered by Rust (lopdf + hayro). MIT, zero dependencies, lightweight wheels.
|
|
18
|
+
Home-Page: https://github.com/yhay81/pylopdf
|
|
19
|
+
Author-email: Yusuke Hayashi <yusuke8h@gmail.com>
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
22
|
+
|
|
23
|
+
# pylopdf
|
|
24
|
+
|
|
25
|
+
[日本語版 README はこちら](README.ja.md)
|
|
26
|
+
|
|
27
|
+
PDF editing and rendering for Python, powered by Rust — [lopdf](https://github.com/J-F-Liu/lopdf) for editing and [hayro](https://github.com/LaurenzV/hayro) (the pure-Rust PDF renderer adopted by typst) for rendering.
|
|
28
|
+
|
|
29
|
+
**MIT licensed, zero runtime dependencies, lightweight wheels.** Covers the common pymupdf use cases without the AGPL.
|
|
30
|
+
|
|
31
|
+
## Why pylopdf?
|
|
32
|
+
|
|
33
|
+
| | pylopdf | pymupdf | pypdf | pypdfium2 |
|
|
34
|
+
|---|---|---|---|---|
|
|
35
|
+
| License | **MIT** | AGPL / commercial | BSD | Apache/BSD |
|
|
36
|
+
| Wheel size | **~3.5 MB** | ~40 MB+ | small (pure Python) | ~8 MB |
|
|
37
|
+
| Editing (merge / split / metadata) | ✅ | ✅ | ✅ | limited |
|
|
38
|
+
| Rendering (PNG / SVG) | ✅ | ✅ | ❌ | ✅ (PNG) |
|
|
39
|
+
| Text extraction | ✅ (basic) | ✅ (advanced) | ✅ | ✅ |
|
|
40
|
+
| Implementation | **pure Rust** | C | Python | C++ (PDFium) |
|
|
41
|
+
|
|
42
|
+
- Fits size-constrained environments such as AWS Lambda
|
|
43
|
+
- Safe for commercial projects that need to avoid the AGPL
|
|
44
|
+
- abi3: one wheel covers Python 3.10–3.14
|
|
45
|
+
- API modeled after [pymupdf](https://github.com/pymupdf/PyMuPDF)
|
|
46
|
+
|
|
47
|
+
**Limitations**: no precise layout analysis, no annotation/form editing, no encrypted PDFs. Use pymupdf if you need those.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install pylopdf # (after PyPI release)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Building from source (requires a Rust toolchain):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv sync
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import pylopdf
|
|
65
|
+
|
|
66
|
+
# Open from a path or bytes
|
|
67
|
+
doc = pylopdf.open("input.pdf")
|
|
68
|
+
doc = pylopdf.open(stream=pdf_bytes)
|
|
69
|
+
|
|
70
|
+
# Page count
|
|
71
|
+
print(doc.page_count) # same as len(doc)
|
|
72
|
+
|
|
73
|
+
# Metadata
|
|
74
|
+
print(doc.metadata["title"])
|
|
75
|
+
doc.set_metadata({"title": "Monthly Report", "author": "Alice"})
|
|
76
|
+
|
|
77
|
+
# Text extraction (0-based page numbers)
|
|
78
|
+
text = doc.get_page_text(0)
|
|
79
|
+
|
|
80
|
+
# Rendering
|
|
81
|
+
png: bytes = doc.render_page(0) # 72 dpi
|
|
82
|
+
png2x: bytes = doc.render_page(0, scale=2) # 144 dpi
|
|
83
|
+
svg: str = doc.render_page_svg(0)
|
|
84
|
+
|
|
85
|
+
# Delete pages (split)
|
|
86
|
+
doc.delete_page(0)
|
|
87
|
+
doc.delete_pages([1, 2])
|
|
88
|
+
|
|
89
|
+
# Keep/reorder pages
|
|
90
|
+
doc.select([2, 0])
|
|
91
|
+
|
|
92
|
+
# Merge
|
|
93
|
+
merged = pylopdf.Document()
|
|
94
|
+
merged.insert_pdf(pylopdf.open("a.pdf"))
|
|
95
|
+
merged.insert_pdf(pylopdf.open("b.pdf"))
|
|
96
|
+
|
|
97
|
+
# Save
|
|
98
|
+
merged.save("merged.pdf")
|
|
99
|
+
data: bytes = merged.tobytes()
|
|
100
|
+
|
|
101
|
+
# Context manager
|
|
102
|
+
with pylopdf.open("input.pdf") as doc:
|
|
103
|
+
print(doc.metadata)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## API
|
|
107
|
+
|
|
108
|
+
`pylopdf.Document` (`pylopdf.open()` is an alias constructor):
|
|
109
|
+
|
|
110
|
+
| Method / property | Description |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `Document(filename=None, stream=None)` | Open from a path or bytes; empty document if both are None |
|
|
113
|
+
| `page_count` / `len(doc)` | Number of pages |
|
|
114
|
+
| `metadata` | Metadata dict (title, author, subject, keywords, creator, producer, creationDate, modDate, format) |
|
|
115
|
+
| `set_metadata(dict)` | Set metadata (empty string deletes the entry) |
|
|
116
|
+
| `get_page_text(pno)` | Extract text from a page |
|
|
117
|
+
| `render_page(pno, scale=1.0)` | Render a page to PNG bytes |
|
|
118
|
+
| `render_page_svg(pno)` | Render a page to an SVG string |
|
|
119
|
+
| `select(page_numbers)` | Keep only the given pages, in the given order |
|
|
120
|
+
| `delete_page(pno)` / `delete_pages(iterable)` | Delete pages |
|
|
121
|
+
| `insert_pdf(other)` | Append all pages of another document |
|
|
122
|
+
| `save(filename)` / `tobytes()` | Save |
|
|
123
|
+
| `close()` | Close (supports `with`) |
|
|
124
|
+
|
|
125
|
+
For low-level access, use `pylopdf.pylopdf_core._Document` (a thin lopdf wrapper) directly.
|
|
126
|
+
|
|
127
|
+
## Architecture
|
|
128
|
+
|
|
129
|
+
Follows the division of labor in the 2026 Rust PDF ecosystem:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
pylopdf.Document (Python, pymupdf-style API)
|
|
133
|
+
└─ _Document (PyO3)
|
|
134
|
+
├─ lopdf 0.44 … editing: open → modify → save
|
|
135
|
+
└─ hayro 0.7 … rendering: PNG / SVG (standard fonts embedded)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
rust/ # PyO3 bindings
|
|
140
|
+
src/pylopdf/ # Python high-level API
|
|
141
|
+
tests/ # pytest (Rust behavior is verified through Python tests)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
uv sync # build + install dependencies
|
|
146
|
+
uv run pytest # tests
|
|
147
|
+
uv run ruff check . # lint
|
|
148
|
+
uv run mypy src tests # type check
|
|
149
|
+
uv build --wheel # build a wheel
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`uv sync` detects Rust source changes and rebuilds automatically (via `tool.uv.cache-keys`).
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT (lopdf is MIT; hayro is MIT/Apache-2.0)
|
|
157
|
+
|
pylopdf-0.4.1/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# pylopdf
|
|
2
|
+
|
|
3
|
+
[日本語版 README はこちら](README.ja.md)
|
|
4
|
+
|
|
5
|
+
PDF editing and rendering for Python, powered by Rust — [lopdf](https://github.com/J-F-Liu/lopdf) for editing and [hayro](https://github.com/LaurenzV/hayro) (the pure-Rust PDF renderer adopted by typst) for rendering.
|
|
6
|
+
|
|
7
|
+
**MIT licensed, zero runtime dependencies, lightweight wheels.** Covers the common pymupdf use cases without the AGPL.
|
|
8
|
+
|
|
9
|
+
## Why pylopdf?
|
|
10
|
+
|
|
11
|
+
| | pylopdf | pymupdf | pypdf | pypdfium2 |
|
|
12
|
+
|---|---|---|---|---|
|
|
13
|
+
| License | **MIT** | AGPL / commercial | BSD | Apache/BSD |
|
|
14
|
+
| Wheel size | **~3.5 MB** | ~40 MB+ | small (pure Python) | ~8 MB |
|
|
15
|
+
| Editing (merge / split / metadata) | ✅ | ✅ | ✅ | limited |
|
|
16
|
+
| Rendering (PNG / SVG) | ✅ | ✅ | ❌ | ✅ (PNG) |
|
|
17
|
+
| Text extraction | ✅ (basic) | ✅ (advanced) | ✅ | ✅ |
|
|
18
|
+
| Implementation | **pure Rust** | C | Python | C++ (PDFium) |
|
|
19
|
+
|
|
20
|
+
- Fits size-constrained environments such as AWS Lambda
|
|
21
|
+
- Safe for commercial projects that need to avoid the AGPL
|
|
22
|
+
- abi3: one wheel covers Python 3.10–3.14
|
|
23
|
+
- API modeled after [pymupdf](https://github.com/pymupdf/PyMuPDF)
|
|
24
|
+
|
|
25
|
+
**Limitations**: no precise layout analysis, no annotation/form editing, no encrypted PDFs. Use pymupdf if you need those.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install pylopdf # (after PyPI release)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Building from source (requires a Rust toolchain):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv sync
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import pylopdf
|
|
43
|
+
|
|
44
|
+
# Open from a path or bytes
|
|
45
|
+
doc = pylopdf.open("input.pdf")
|
|
46
|
+
doc = pylopdf.open(stream=pdf_bytes)
|
|
47
|
+
|
|
48
|
+
# Page count
|
|
49
|
+
print(doc.page_count) # same as len(doc)
|
|
50
|
+
|
|
51
|
+
# Metadata
|
|
52
|
+
print(doc.metadata["title"])
|
|
53
|
+
doc.set_metadata({"title": "Monthly Report", "author": "Alice"})
|
|
54
|
+
|
|
55
|
+
# Text extraction (0-based page numbers)
|
|
56
|
+
text = doc.get_page_text(0)
|
|
57
|
+
|
|
58
|
+
# Rendering
|
|
59
|
+
png: bytes = doc.render_page(0) # 72 dpi
|
|
60
|
+
png2x: bytes = doc.render_page(0, scale=2) # 144 dpi
|
|
61
|
+
svg: str = doc.render_page_svg(0)
|
|
62
|
+
|
|
63
|
+
# Delete pages (split)
|
|
64
|
+
doc.delete_page(0)
|
|
65
|
+
doc.delete_pages([1, 2])
|
|
66
|
+
|
|
67
|
+
# Keep/reorder pages
|
|
68
|
+
doc.select([2, 0])
|
|
69
|
+
|
|
70
|
+
# Merge
|
|
71
|
+
merged = pylopdf.Document()
|
|
72
|
+
merged.insert_pdf(pylopdf.open("a.pdf"))
|
|
73
|
+
merged.insert_pdf(pylopdf.open("b.pdf"))
|
|
74
|
+
|
|
75
|
+
# Save
|
|
76
|
+
merged.save("merged.pdf")
|
|
77
|
+
data: bytes = merged.tobytes()
|
|
78
|
+
|
|
79
|
+
# Context manager
|
|
80
|
+
with pylopdf.open("input.pdf") as doc:
|
|
81
|
+
print(doc.metadata)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## API
|
|
85
|
+
|
|
86
|
+
`pylopdf.Document` (`pylopdf.open()` is an alias constructor):
|
|
87
|
+
|
|
88
|
+
| Method / property | Description |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `Document(filename=None, stream=None)` | Open from a path or bytes; empty document if both are None |
|
|
91
|
+
| `page_count` / `len(doc)` | Number of pages |
|
|
92
|
+
| `metadata` | Metadata dict (title, author, subject, keywords, creator, producer, creationDate, modDate, format) |
|
|
93
|
+
| `set_metadata(dict)` | Set metadata (empty string deletes the entry) |
|
|
94
|
+
| `get_page_text(pno)` | Extract text from a page |
|
|
95
|
+
| `render_page(pno, scale=1.0)` | Render a page to PNG bytes |
|
|
96
|
+
| `render_page_svg(pno)` | Render a page to an SVG string |
|
|
97
|
+
| `select(page_numbers)` | Keep only the given pages, in the given order |
|
|
98
|
+
| `delete_page(pno)` / `delete_pages(iterable)` | Delete pages |
|
|
99
|
+
| `insert_pdf(other)` | Append all pages of another document |
|
|
100
|
+
| `save(filename)` / `tobytes()` | Save |
|
|
101
|
+
| `close()` | Close (supports `with`) |
|
|
102
|
+
|
|
103
|
+
For low-level access, use `pylopdf.pylopdf_core._Document` (a thin lopdf wrapper) directly.
|
|
104
|
+
|
|
105
|
+
## Architecture
|
|
106
|
+
|
|
107
|
+
Follows the division of labor in the 2026 Rust PDF ecosystem:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
pylopdf.Document (Python, pymupdf-style API)
|
|
111
|
+
└─ _Document (PyO3)
|
|
112
|
+
├─ lopdf 0.44 … editing: open → modify → save
|
|
113
|
+
└─ hayro 0.7 … rendering: PNG / SVG (standard fonts embedded)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
rust/ # PyO3 bindings
|
|
118
|
+
src/pylopdf/ # Python high-level API
|
|
119
|
+
tests/ # pytest (Rust behavior is verified through Python tests)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
uv sync # build + install dependencies
|
|
124
|
+
uv run pytest # tests
|
|
125
|
+
uv run ruff check . # lint
|
|
126
|
+
uv run mypy src tests # type check
|
|
127
|
+
uv build --wheel # build a wheel
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`uv sync` detects Rust source changes and rebuilds automatically (via `tool.uv.cache-keys`).
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT (lopdf is MIT; hayro is MIT/Apache-2.0)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pylopdf"
|
|
3
|
+
version = "0.4.1"
|
|
4
|
+
description = "PDF editing and rendering for Python, powered by Rust (lopdf + hayro). MIT, zero dependencies, lightweight wheels."
|
|
5
|
+
authors = [{ "name" = "Yusuke Hayashi", "email" = "yusuke8h@gmail.com" }]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
license = { file = "LICENSE" }
|
|
9
|
+
dependencies = []
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Programming Language :: Python :: 3.14",
|
|
20
|
+
"Programming Language :: Rust",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
"Typing :: Typed",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["maturin>=1.14,<2.0"]
|
|
27
|
+
build-backend = "maturin"
|
|
28
|
+
|
|
29
|
+
[tool.maturin]
|
|
30
|
+
manifest-path = "rust/Cargo.toml"
|
|
31
|
+
python-source = "src"
|
|
32
|
+
module-name = "pylopdf.pylopdf_core"
|
|
33
|
+
features = ["pyo3/extension-module"]
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"maturin>=1.14.1",
|
|
38
|
+
"mypy>=1.14",
|
|
39
|
+
"pre-commit>=4.6.1",
|
|
40
|
+
"pytest>=8.3",
|
|
41
|
+
"ruff>=0.9",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.uv]
|
|
45
|
+
# Rust ソース変更時に uv が拡張モジュールを再ビルドするためのキャッシュキー
|
|
46
|
+
cache-keys = [
|
|
47
|
+
{ file = "pyproject.toml" },
|
|
48
|
+
{ file = "rust/Cargo.toml" },
|
|
49
|
+
{ file = "rust/src/*.rs" },
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
testpaths = ["tests"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
line-length = 120
|
|
57
|
+
src = ["src"]
|
|
58
|
+
target-version = "py310"
|
|
59
|
+
# python/ と lambda_test/ は本ライブラリの対象外(別作業の同居ファイル)
|
|
60
|
+
extend-exclude = ["python", "lambda_test"]
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint]
|
|
63
|
+
select = ["ALL"]
|
|
64
|
+
ignore = [
|
|
65
|
+
"D100",
|
|
66
|
+
"D104",
|
|
67
|
+
# ruff format と競合するルール
|
|
68
|
+
"COM812",
|
|
69
|
+
"ISC001",
|
|
70
|
+
"D203",
|
|
71
|
+
"D213",
|
|
72
|
+
# 日本語ドキュメント方針(CLAUDE.md)と衝突するルール
|
|
73
|
+
"D400", # 「。」を終端句読点と認識しない
|
|
74
|
+
"D415", # 同上
|
|
75
|
+
"D403", # 日本語文中の英単語先頭を大文字化してしまう
|
|
76
|
+
"RUF001", # 全角記号を ambiguous unicode として誤検出する
|
|
77
|
+
"RUF002",
|
|
78
|
+
"RUF003",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
[tool.ruff.lint.per-file-ignores]
|
|
82
|
+
"tests/*" = ["INP001", "S101", "D103", "PLR2004", "TC003"]
|