edof 3.0.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.
- edof-3.0.0/CHANGELOG.md +92 -0
- edof-3.0.0/LICENSE +21 -0
- edof-3.0.0/MANIFEST.in +5 -0
- edof-3.0.0/PKG-INFO +473 -0
- edof-3.0.0/README.md +405 -0
- edof-3.0.0/edof/__init__.py +91 -0
- edof-3.0.0/edof/api/__init__.py +0 -0
- edof-3.0.0/edof/api/commands.py +326 -0
- edof-3.0.0/edof/engine/__init__.py +1 -0
- edof-3.0.0/edof/engine/color.py +90 -0
- edof-3.0.0/edof/engine/renderer.py +259 -0
- edof-3.0.0/edof/engine/text_engine.py +251 -0
- edof-3.0.0/edof/engine/transform.py +241 -0
- edof-3.0.0/edof/exceptions.py +78 -0
- edof-3.0.0/edof/export/__init__.py +1 -0
- edof-3.0.0/edof/export/bitmap.py +92 -0
- edof-3.0.0/edof/export/pdf.py +62 -0
- edof-3.0.0/edof/export/printer.py +119 -0
- edof-3.0.0/edof/format/__init__.py +1 -0
- edof-3.0.0/edof/format/document.py +509 -0
- edof-3.0.0/edof/format/objects.py +358 -0
- edof-3.0.0/edof/format/serializer.py +149 -0
- edof-3.0.0/edof/format/styles.py +176 -0
- edof-3.0.0/edof/format/variables.py +155 -0
- edof-3.0.0/edof/gui/__init__.py +0 -0
- edof-3.0.0/edof/gui/pyqt6_widget.py +254 -0
- edof-3.0.0/edof/gui/tkinter_canvas.py +541 -0
- edof-3.0.0/edof/py.typed +0 -0
- edof-3.0.0/edof/utils/__init__.py +1 -0
- edof-3.0.0/edof/utils/compat.py +82 -0
- edof-3.0.0/edof/utils/qr.py +69 -0
- edof-3.0.0/edof/version.py +38 -0
- edof-3.0.0/edof.egg-info/PKG-INFO +473 -0
- edof-3.0.0/edof.egg-info/SOURCES.txt +39 -0
- edof-3.0.0/edof.egg-info/dependency_links.txt +1 -0
- edof-3.0.0/edof.egg-info/requires.txt +24 -0
- edof-3.0.0/edof.egg-info/top_level.txt +1 -0
- edof-3.0.0/pyproject.toml +91 -0
- edof-3.0.0/setup.cfg +4 -0
- edof-3.0.0/tests/test_document.py +110 -0
- edof-3.0.0/tests/test_transform.py +111 -0
edof-3.0.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **edof** are documented here.
|
|
4
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · Versioning: [SemVer](https://semver.org/)
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## [3.0.0] – 2025-01-01
|
|
9
|
+
|
|
10
|
+
Initial public release.
|
|
11
|
+
|
|
12
|
+
### Library
|
|
13
|
+
|
|
14
|
+
#### Document model
|
|
15
|
+
- `Document`, `Page`, `ResourceStore` – root structure
|
|
16
|
+
- Object types: `TextBox`, `ImageBox`, `Shape` (rect/ellipse/polygon/arrow), `Line` (two absolute points), `QRCode`, `Group`
|
|
17
|
+
- Common object properties: `id`, `name`, `variable`, `layer`, `locked`, `visible`, `editable`, `tags`, `opacity`, `shadow`
|
|
18
|
+
- `TextStyle` – font, size, bold/italic/underline/strikethrough, color, alignment (H+V), line height, word wrap, overflow
|
|
19
|
+
- `auto_shrink` – shrinks font to fit the box, never enlarges (font_size = maximum)
|
|
20
|
+
- `auto_fill` – fills the box by finding the largest fitting font size (grows + shrinks)
|
|
21
|
+
- `StrokeStyle`, `FillStyle`, `ShadowStyle` – RGBA color support throughout
|
|
22
|
+
- `Transform` – position/size in mm, clockwise rotation in °, flip H/V; full chain API
|
|
23
|
+
|
|
24
|
+
#### Variable / template system
|
|
25
|
+
- `VariableStore` – named placeholders with type validation (`text`, `number`, `date`, `image`, `qr`, `url`, `bool`)
|
|
26
|
+
- `doc.fill_variables({"name": "Jan"})` – batch fill for template rendering
|
|
27
|
+
- `ImageBox` variable – value can be a file path or HTTP URL loaded at render time
|
|
28
|
+
- Fallback: if variable is unset/empty, `obj.text` is displayed (non-destructive in editor)
|
|
29
|
+
|
|
30
|
+
#### Rendering
|
|
31
|
+
- Pillow RGBA compositor; color spaces: RGB, RGBA, L, 1, CMYK; bit depths: 8 and 16
|
|
32
|
+
- Configurable DPI per page
|
|
33
|
+
- Correct `pt → px` DPI conversion throughout (font sizing, auto-shrink/fill)
|
|
34
|
+
- Rotation applies to the entire object surface (fill + border + text as one unit)
|
|
35
|
+
- QR colorization: generates B&W first, colorizes pixel-by-pixel → all fg/bg colors work
|
|
36
|
+
|
|
37
|
+
#### File format (`.edof`)
|
|
38
|
+
- ZIP archive: `manifest.json` + `document.json` + `resources/<uuid>` (embedded fonts, images)
|
|
39
|
+
- `EdofSerializer` – `save`, `load`, `to_bytes`, `from_bytes`, `peek`
|
|
40
|
+
- Forward compat: newer file → `EdofNewerVersionWarning` (non-fatal, execution continues)
|
|
41
|
+
- Backward compat: older files migrated automatically via `edof.utils.compat`
|
|
42
|
+
|
|
43
|
+
#### Export & print
|
|
44
|
+
- Bitmap: PNG, JPEG, TIFF, BMP; all pages with `{page}` pattern; in-memory bytes
|
|
45
|
+
- PDF via reportlab (`pip install edof[pdf]`)
|
|
46
|
+
- Print: `QPrintPreviewDialog` (PyQt6), `os.startfile` (Windows), `lpr`/`lp` (macOS/Linux)
|
|
47
|
+
|
|
48
|
+
#### Text engine
|
|
49
|
+
- System font discovery on Windows / macOS / Linux via `font.getname()` metadata
|
|
50
|
+
- Bold/italic variant resolution, font cache, `list_system_fonts()`
|
|
51
|
+
- Text wrap, multi-line, vertical alignment, underline, strikethrough
|
|
52
|
+
|
|
53
|
+
#### QR codes
|
|
54
|
+
- `edof[qr]` optional dep; error correction L/M/Q/H; RGBA colors
|
|
55
|
+
- Standalone helpers: `edof.utils.qr.generate_qr_image()`, `generate_qr_bytes()`
|
|
56
|
+
|
|
57
|
+
#### Command API & undo/redo
|
|
58
|
+
- `edof.api.commands.execute(doc, {"cmd": "...", ...})` – string-based dispatch
|
|
59
|
+
- `CommandHistory` – snapshot-based undo/redo stack
|
|
60
|
+
|
|
61
|
+
#### GUI
|
|
62
|
+
- `EdofTkCanvas` – Tkinter canvas widget
|
|
63
|
+
- `EdofQtWidget` – PyQt6 QGraphicsView widget
|
|
64
|
+
|
|
65
|
+
### Applications
|
|
66
|
+
|
|
67
|
+
#### EDOF Editor (`edof_editor.py`) — requires PyQt6
|
|
68
|
+
- Async rendering (background thread, UI never blocks)
|
|
69
|
+
- Resize handles (8 directions) + rotation handle; rotated object resize keeps anchor fixed
|
|
70
|
+
- Shift = snap rotation to 15°, Alt = free rotation
|
|
71
|
+
- Inline text editing (double-click): WYSIWYG font size matches canvas zoom and screen DPI
|
|
72
|
+
- Double-click QR → inline data/URL editor; double-click Image → file picker
|
|
73
|
+
- Ghost outlines for hidden objects (still selectable)
|
|
74
|
+
- Type-aware property panel: TextBox / ImageBox / Shape / Line / QRCode each has own controls
|
|
75
|
+
- Custom color dialog: hex `#RRGGBBAA`, RGBA sliders, live swatch
|
|
76
|
+
- Object list panel, page list panel, layer ordering (front/back/up/down)
|
|
77
|
+
- Print preview via `QPrintPreviewDialog`, renders at ≤150 dpi using raw PIL bytes (bypasses Qt 256 MB limit)
|
|
78
|
+
- Internationalisation via `editor_lang/XX.json`
|
|
79
|
+
|
|
80
|
+
#### EDOF CLI (`edof_cli.py`)
|
|
81
|
+
- `info`, `objects`, `validate`, `export` sub-commands
|
|
82
|
+
- `--set key=value`, `--json-vars '{...}'`, `--all-pages`, `--dpi`, `--format`, `--color-space`
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Unreleased
|
|
87
|
+
|
|
88
|
+
Nothing yet.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
> Versions 1.x and 2.x were internal development iterations and were never publicly released.
|
edof-3.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 EDOF Contributors
|
|
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.
|
edof-3.0.0/MANIFEST.in
ADDED
edof-3.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: edof
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Easy Document Format – programmatic document creation, editing and export
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2025 EDOF Contributors
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Project-URL: Homepage, https://github.com/DavidSchobl/edof
|
|
28
|
+
Project-URL: Documentation, https://github.com/DavidSchobl/edof#readme
|
|
29
|
+
Project-URL: Repository, https://github.com/DavidSchobl/edof
|
|
30
|
+
Project-URL: Bug Tracker, https://github.com/DavidSchobl/edof/issues
|
|
31
|
+
Project-URL: Changelog, https://github.com/DavidSchobl/edof/blob/main/CHANGELOG.md
|
|
32
|
+
Keywords: document,format,pdf,template,edof,print
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
43
|
+
Classifier: Topic :: Office/Business
|
|
44
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
45
|
+
Requires-Python: >=3.9
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
License-File: LICENSE
|
|
48
|
+
Requires-Dist: Pillow>=9.1
|
|
49
|
+
Provides-Extra: pdf
|
|
50
|
+
Requires-Dist: reportlab>=4.0; extra == "pdf"
|
|
51
|
+
Provides-Extra: qr
|
|
52
|
+
Requires-Dist: qrcode[pil]>=7.4; extra == "qr"
|
|
53
|
+
Provides-Extra: pyqt6
|
|
54
|
+
Requires-Dist: PyQt6>=6.6; extra == "pyqt6"
|
|
55
|
+
Provides-Extra: all
|
|
56
|
+
Requires-Dist: reportlab>=4.0; extra == "all"
|
|
57
|
+
Requires-Dist: qrcode[pil]>=7.4; extra == "all"
|
|
58
|
+
Requires-Dist: PyQt6>=6.6; extra == "all"
|
|
59
|
+
Provides-Extra: dev
|
|
60
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
61
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
62
|
+
Requires-Dist: black; extra == "dev"
|
|
63
|
+
Requires-Dist: ruff; extra == "dev"
|
|
64
|
+
Requires-Dist: mypy; extra == "dev"
|
|
65
|
+
Requires-Dist: build; extra == "dev"
|
|
66
|
+
Requires-Dist: twine; extra == "dev"
|
|
67
|
+
Dynamic: license-file
|
|
68
|
+
|
|
69
|
+
# edof – Easy Document Format 3.0
|
|
70
|
+
|
|
71
|
+
[](LICENSE)
|
|
72
|
+
[](https://pypi.org/project/edof/)
|
|
73
|
+
[](https://github.com/DavidSchobl/edof/actions)
|
|
74
|
+
[](https://pypi.org/project/edof/)
|
|
75
|
+
|
|
76
|
+
**edof** is a Python library for programmatic document creation, template filling and high-quality export.
|
|
77
|
+
Documents are stored as `.edof` files – a versioned ZIP archive with a JSON document tree and all embedded resources (fonts, images).
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import edof
|
|
81
|
+
|
|
82
|
+
doc = edof.new(width=210, height=297, title="My Certificate")
|
|
83
|
+
page = doc.add_page(dpi=300)
|
|
84
|
+
|
|
85
|
+
tb = page.add_textbox(10, 20, 190, 30, "")
|
|
86
|
+
tb.variable = "recipient" # bound to a template variable
|
|
87
|
+
tb.style.font_size = 48
|
|
88
|
+
tb.style.auto_shrink = True # shrinks if text doesn't fit, never enlarges
|
|
89
|
+
tb.style.alignment = "center"
|
|
90
|
+
|
|
91
|
+
doc.define_variable("recipient", required=True)
|
|
92
|
+
doc.fill_variables({"recipient": "Jan Novák"})
|
|
93
|
+
doc.save("certificate.edof")
|
|
94
|
+
doc.export_bitmap("certificate.png", dpi=300)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Installation
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Core library (Pillow only)
|
|
103
|
+
pip install edof
|
|
104
|
+
|
|
105
|
+
# With PDF export
|
|
106
|
+
pip install edof[pdf]
|
|
107
|
+
|
|
108
|
+
# With QR code generation
|
|
109
|
+
pip install edof[qr]
|
|
110
|
+
|
|
111
|
+
# With PyQt6 editor / widget
|
|
112
|
+
pip install edof[pyqt6]
|
|
113
|
+
|
|
114
|
+
# Everything
|
|
115
|
+
pip install edof[all]
|
|
116
|
+
|
|
117
|
+
# Development
|
|
118
|
+
pip install edof[dev]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Python 3.9 – 3.13** supported.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Quick Start
|
|
126
|
+
|
|
127
|
+
### Create a document
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
import edof
|
|
131
|
+
|
|
132
|
+
doc = edof.new(width=210, height=297) # A4 in mm, 300 dpi default
|
|
133
|
+
page = doc.add_page()
|
|
134
|
+
|
|
135
|
+
# Text box
|
|
136
|
+
tb = page.add_textbox(x=10, y=10, width=190, height=30, text="Hello!")
|
|
137
|
+
tb.style.font_size = 36
|
|
138
|
+
tb.style.alignment = "center"
|
|
139
|
+
tb.style.bold = True
|
|
140
|
+
|
|
141
|
+
# Image
|
|
142
|
+
rid = doc.add_resource_from_file("logo.png")
|
|
143
|
+
page.add_image(rid, x=10, y=50, width=80, height=50, fit_mode="contain")
|
|
144
|
+
|
|
145
|
+
# Shape
|
|
146
|
+
sh = page.add_shape("rect", x=10, y=110, width=190, height=1)
|
|
147
|
+
sh.fill.color = (0, 0, 0, 255)
|
|
148
|
+
|
|
149
|
+
# QR code (requires pip install edof[qr])
|
|
150
|
+
page.add_qrcode("https://github.com/DavidSchobl/edof", x=160, y=120, size=30)
|
|
151
|
+
|
|
152
|
+
doc.save("doc.edof")
|
|
153
|
+
doc.export_bitmap("doc.png", dpi=300) # PNG
|
|
154
|
+
doc.export_pdf("doc.pdf") # PDF (requires edof[pdf])
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Templates & Batch Fill
|
|
160
|
+
|
|
161
|
+
Objects can be bound to named **variables**. At render time the variable value replaces the object's content.
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
doc = edof.new()
|
|
165
|
+
page = doc.add_page()
|
|
166
|
+
|
|
167
|
+
# Define variables with types and defaults
|
|
168
|
+
doc.define_variable("name", type="text", default="—", required=True)
|
|
169
|
+
doc.define_variable("score", type="number", default="0")
|
|
170
|
+
doc.define_variable("logo", type="image", default="default.png") # file path or URL
|
|
171
|
+
|
|
172
|
+
# Bind objects to variables
|
|
173
|
+
tb_name = page.add_textbox(10, 10, 100, 20, "")
|
|
174
|
+
tb_name.variable = "name"
|
|
175
|
+
tb_name.style.auto_shrink = True # font shrinks if name is long
|
|
176
|
+
|
|
177
|
+
img = page.add_image(None, 150, 10, 40, 40)
|
|
178
|
+
img.variable = "logo" # value = file path or HTTP URL
|
|
179
|
+
|
|
180
|
+
# Fill once
|
|
181
|
+
doc.fill_variables({"name": "Alice", "score": "98", "logo": "alice.png"})
|
|
182
|
+
doc.export_bitmap("alice.png")
|
|
183
|
+
|
|
184
|
+
# Batch loop
|
|
185
|
+
for row in [("Bob", "87", "bob.png"), ("Carol", "95", "carol.png")]:
|
|
186
|
+
doc.fill_variables({"name": row[0], "score": row[1], "logo": row[2]})
|
|
187
|
+
doc.export_bitmap(f"{row[0]}.png")
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Missing variables are non-destructive
|
|
191
|
+
If a variable has no value set, the object shows its `obj.text` fallback — useful for previewing a template in the editor without filling all fields first.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Object Types
|
|
196
|
+
|
|
197
|
+
### TextBox
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
tb = page.add_textbox(x, y, width, height, text="")
|
|
201
|
+
|
|
202
|
+
# Sizing modes (mutually exclusive):
|
|
203
|
+
tb.style.auto_shrink = True # font_size = maximum; shrinks to fit; never enlarges
|
|
204
|
+
tb.style.auto_fill = True # fills the box (grows and shrinks up to max_font_size)
|
|
205
|
+
# both False = fixed font size
|
|
206
|
+
|
|
207
|
+
tb.style.font_family = "Arial"
|
|
208
|
+
tb.style.font_size = 18.0 # pt
|
|
209
|
+
tb.style.min_font_size = 4.0 # floor for auto-shrink/fill
|
|
210
|
+
tb.style.max_font_size = 200.0 # ceiling for auto-fill
|
|
211
|
+
tb.style.bold = True
|
|
212
|
+
tb.style.italic = True
|
|
213
|
+
tb.style.underline = True
|
|
214
|
+
tb.style.strikethrough = True
|
|
215
|
+
tb.style.color = (0, 0, 0) # RGB
|
|
216
|
+
tb.style.alignment = "center" # left|center|right|justify
|
|
217
|
+
tb.style.vertical_align= "middle" # top|middle|bottom
|
|
218
|
+
tb.style.line_height = 1.2
|
|
219
|
+
tb.style.wrap = True
|
|
220
|
+
tb.style.overflow_hidden = True
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### ImageBox
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
img = page.add_image(resource_id, x, y, width, height,
|
|
227
|
+
fit_mode="contain") # contain|cover|fill|stretch|none
|
|
228
|
+
# fit_mode="contain" – letterboxed, preserves aspect ratio
|
|
229
|
+
# fit_mode="cover" – cropped, fills the box
|
|
230
|
+
# fit_mode="fill" – alias for cover
|
|
231
|
+
# fit_mode="stretch" – distorts to fill exactly
|
|
232
|
+
|
|
233
|
+
rid = doc.add_resource_from_file("photo.jpg")
|
|
234
|
+
# or: rid = doc.add_resource(bytes_data, "photo.jpg", "image/jpeg")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Shape
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
sh = page.add_shape("rect", x, y, width, height)
|
|
241
|
+
# Types: "rect" | "ellipse" | "polygon" | "arrow"
|
|
242
|
+
|
|
243
|
+
sh.fill.color = (100, 149, 237, 255) # RGBA
|
|
244
|
+
sh.stroke.color = (50, 80, 180, 255)
|
|
245
|
+
sh.stroke.width = 1.5 # pt
|
|
246
|
+
sh.corner_radius = 4.0 # mm (for rect)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Line
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
sh = page.add_shape("line", 0, 0, 1, 1)
|
|
253
|
+
sh.points = [[x1_mm, y1_mm], [x2_mm, y2_mm]] # two absolute page coordinates
|
|
254
|
+
sh.stroke.color = (0, 0, 0, 255)
|
|
255
|
+
sh.stroke.width = 2.0 # pt
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### QR Code
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
qr = page.add_qrcode(data="https://example.com", x=10, y=10,
|
|
262
|
+
size=40, error_correction="M")
|
|
263
|
+
# error_correction: "L" | "M" | "Q" | "H"
|
|
264
|
+
qr.fg_color = (0, 0, 0, 255) # RGBA – any color works
|
|
265
|
+
qr.bg_color = (255, 255, 255, 255)
|
|
266
|
+
qr.variable = "url" # dynamic data from variable
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Transform API
|
|
272
|
+
|
|
273
|
+
Every object supports a full transform chain:
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
obj.move_to(20, 30) # absolute position (mm)
|
|
277
|
+
obj.move(10, 5) # relative translate
|
|
278
|
+
obj.rotate_to(45) # absolute rotation (°, clockwise)
|
|
279
|
+
obj.rotate(15) # add 15° to current rotation
|
|
280
|
+
obj.resize_uniform(1.5) # scale both axes, keep centre
|
|
281
|
+
obj.resize(100, 40) # set absolute width × height (mm)
|
|
282
|
+
obj.resize(100, 40, anchor="center") # resize keeping centre fixed
|
|
283
|
+
obj.flip_h()
|
|
284
|
+
obj.flip_v()
|
|
285
|
+
|
|
286
|
+
# Chainable
|
|
287
|
+
obj.move_to(10, 10).resize(80, 30).rotate_to(15)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Color Spaces & Bit Depth
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
page = doc.add_page(color_space="L", bit_depth=8) # grayscale
|
|
296
|
+
page = doc.add_page(color_space="1") # black & white
|
|
297
|
+
page = doc.add_page(color_space="RGB", bit_depth=16) # 16-bit
|
|
298
|
+
|
|
299
|
+
# Override on export
|
|
300
|
+
doc.export_bitmap("gray.tiff", color_space="L", format="TIFF")
|
|
301
|
+
doc.export_bitmap("bw.png", color_space="1")
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Supported values: `"RGB"`, `"RGBA"`, `"L"` (grayscale), `"1"` (B&W), `"CMYK"`.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## File Format
|
|
309
|
+
|
|
310
|
+
`.edof` is a plain ZIP archive — inspectable with any ZIP tool:
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
document.edof
|
|
314
|
+
├── manifest.json ← version header, quick metadata
|
|
315
|
+
├── document.json ← full document tree (no binary blobs)
|
|
316
|
+
└── resources/
|
|
317
|
+
├── <uuid> ← embedded image / font (raw bytes)
|
|
318
|
+
└── …
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Version compatibility
|
|
322
|
+
|
|
323
|
+
| File version vs library | Behaviour |
|
|
324
|
+
|---|---|
|
|
325
|
+
| Same | Full compatibility |
|
|
326
|
+
| File older | Loaded and migrated automatically, non-fatal notice in `doc.errors` |
|
|
327
|
+
| File newer | `EdofNewerVersionWarning` emitted; content loaded best-effort |
|
|
328
|
+
| File too old (major < 1) | `EdofVersionError` raised |
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
import warnings
|
|
332
|
+
from edof.exceptions import EdofNewerVersionWarning
|
|
333
|
+
|
|
334
|
+
with warnings.catch_warnings(record=True) as w:
|
|
335
|
+
warnings.simplefilter("always")
|
|
336
|
+
doc = edof.load("newer.edof")
|
|
337
|
+
|
|
338
|
+
print(doc.errors) # non-fatal notices from load
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Command API
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
from edof.api.commands import execute, CommandHistory
|
|
347
|
+
|
|
348
|
+
doc = edof.new(); doc.add_page()
|
|
349
|
+
|
|
350
|
+
oid = execute(doc, {"cmd": "add_textbox", "page": 0,
|
|
351
|
+
"x": 10, "y": 10, "width": 80, "height": 20,
|
|
352
|
+
"text": "Hello API"})
|
|
353
|
+
|
|
354
|
+
execute(doc, {"cmd": "set_style", "object_id": oid, "page": 0,
|
|
355
|
+
"style": {"font_size": 24, "auto_shrink": True}})
|
|
356
|
+
|
|
357
|
+
execute(doc, {"cmd": "export_bitmap", "page": 0,
|
|
358
|
+
"path": "out.png", "dpi": 300})
|
|
359
|
+
|
|
360
|
+
# Undo / redo
|
|
361
|
+
history = CommandHistory(max_undo=50)
|
|
362
|
+
history.push(doc, "initial state")
|
|
363
|
+
# … make changes …
|
|
364
|
+
doc = history.undo(doc) # returns restored Document or None
|
|
365
|
+
doc = history.redo(doc)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Available commands: `add_page`, `remove_page`, `add_textbox`, `add_image`, `add_shape`, `add_qrcode`, `remove_object`, `set_text`, `set_variable`, `fill_variables`, `move_object`, `resize_object`, `rotate_object`, `set_style`, `set_visibility`, `set_layer`, `export_bitmap`, `export_pdf`, `save`, `validate`.
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## EDOF Editor
|
|
373
|
+
|
|
374
|
+
A full desktop document editor ships with the repository:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
pip install edof[pyqt6]
|
|
378
|
+
python edof_editor.py
|
|
379
|
+
python edof_editor.py myfile.edof
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**Canvas features:**
|
|
383
|
+
- 8 resize handles (corners + edges) with correct rotated-object resize
|
|
384
|
+
- Rotation handle — **Shift** = snap to 15°, **Alt** = free rotation
|
|
385
|
+
- Double-click `TextBox` → WYSIWYG inline text editor (font size matches canvas zoom)
|
|
386
|
+
- Double-click `QRCode` → inline data/URL editor
|
|
387
|
+
- Double-click `ImageBox` → file picker to replace the source
|
|
388
|
+
- Ghost outlines for hidden objects — still selectable
|
|
389
|
+
- Middle-mouse pan · Scroll-wheel zoom
|
|
390
|
+
|
|
391
|
+
**Property panel** — type-aware:
|
|
392
|
+
|
|
393
|
+
| Object | Properties shown |
|
|
394
|
+
|---|---|
|
|
395
|
+
| TextBox | Content (live update), font, size, color + alpha, alignment, sizing mode radio buttons |
|
|
396
|
+
| ImageBox | Fit mode, replace image |
|
|
397
|
+
| Shape | Fill color + alpha, stroke color + alpha + width, corner radius |
|
|
398
|
+
| Line | X1/Y1/X2/Y2 endpoints, stroke |
|
|
399
|
+
| QRCode | Data/URL (live update), error correction, FG/BG color + alpha |
|
|
400
|
+
|
|
401
|
+
**Other editor features:**
|
|
402
|
+
- Object list panel (layer order, type, name, variable, visibility, lock)
|
|
403
|
+
- Custom RGBA color dialog (`#RRGGBBAA` hex + sliders)
|
|
404
|
+
- Layer ordering: bring to front / forward / backward / send to back
|
|
405
|
+
- Variables dialog with live re-render on apply
|
|
406
|
+
- Print preview (`QPrintPreviewDialog`) with correct page rendering
|
|
407
|
+
- Export PNG / JPEG / TIFF / PDF
|
|
408
|
+
- 60-step undo/redo
|
|
409
|
+
- Internationalisation — add `editor_lang/XX.json` for any language (base: `en.json`)
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## EDOF CLI
|
|
414
|
+
|
|
415
|
+
Fill templates and export from the command line without opening the editor:
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
# Inspect a template
|
|
419
|
+
python edof_cli.py info template.edof
|
|
420
|
+
python edof_cli.py objects template.edof
|
|
421
|
+
python edof_cli.py validate template.edof
|
|
422
|
+
|
|
423
|
+
# Export with variables
|
|
424
|
+
python edof_cli.py export template.edof output.png \
|
|
425
|
+
--set name="Jan Novák" \
|
|
426
|
+
--set date="2025-01-01" \
|
|
427
|
+
--dpi 300
|
|
428
|
+
|
|
429
|
+
# JSON variables
|
|
430
|
+
python edof_cli.py export template.edof output.png \
|
|
431
|
+
--json-vars '{"name":"Jan","score":"98"}'
|
|
432
|
+
|
|
433
|
+
# All pages (use {page} or {n} in filename)
|
|
434
|
+
python edof_cli.py export template.edof page_{page}.png --all-pages
|
|
435
|
+
|
|
436
|
+
# PDF
|
|
437
|
+
python edof_cli.py export template.edof output.pdf
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
| Flag | Short | Description |
|
|
441
|
+
|---|---|---|
|
|
442
|
+
| `--set KEY=VALUE` | `-s` | Set one variable (repeatable) |
|
|
443
|
+
| `--json-vars JSON` | `-j` | Set multiple variables as JSON |
|
|
444
|
+
| `--page N` | `-p` | Page index (0-based, default 0) |
|
|
445
|
+
| `--all-pages` | `-A` | Export all pages |
|
|
446
|
+
| `--format` | `-f` | `png` `jpg` `tiff` `bmp` `pdf` |
|
|
447
|
+
| `--dpi N` | `-d` | Resolution (default 300) |
|
|
448
|
+
| `--color-space` | `-c` | `RGB` `RGBA` `L` `1` `CMYK` |
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Running Tests
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
pip install edof[dev]
|
|
456
|
+
pytest
|
|
457
|
+
pytest --cov=edof --cov-report=html # with coverage report
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## License
|
|
463
|
+
|
|
464
|
+
MIT – see [LICENSE](LICENSE).
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Links
|
|
469
|
+
|
|
470
|
+
- **PyPI:** https://pypi.org/project/edof/
|
|
471
|
+
- **GitHub:** https://github.com/DavidSchobl/edof
|
|
472
|
+
- **Issues:** https://github.com/DavidSchobl/edof/issues
|
|
473
|
+
- **Changelog:** [CHANGELOG.md](CHANGELOG.md)
|