roborean-documents-image 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
venv/
|
|
2
|
+
venv-qt5/
|
|
3
|
+
venv-qt6/
|
|
4
|
+
.venv/
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.mypy_cache/
|
|
9
|
+
.coverage
|
|
10
|
+
htmlcov/
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
*.tsbuildinfo
|
|
15
|
+
node_modules/
|
|
16
|
+
.pnpm-store/
|
|
17
|
+
.DS_Store
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/*.local
|
|
20
|
+
playground/
|
|
21
|
+
.e2e-ai/
|
|
22
|
+
**/test-results/
|
|
23
|
+
**/playwright-report/
|
|
24
|
+
research/
|
|
25
|
+
.roborean/
|
|
26
|
+
.roborean-sql-artifacts/
|
|
27
|
+
*.db
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: roborean-documents-image
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Raster image document driver for Roborean
|
|
5
|
+
Project-URL: Homepage, https://github.com/TNick/roborean
|
|
6
|
+
Project-URL: Repository, https://github.com/TNick/roborean
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: pillow>=10.0
|
|
10
|
+
Requires-Dist: roborean-documents-base>=0.1.1
|
|
11
|
+
Requires-Dist: roborean-spec>=0.1.1
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "roborean-documents-image"
|
|
7
|
+
version = "0.1.2"
|
|
8
|
+
description = "Raster image document driver for Roborean"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"roborean-documents-base>=0.1.1",
|
|
13
|
+
"roborean-spec>=0.1.1",
|
|
14
|
+
"Pillow>=10.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.entry-points."roborean.document_drivers"]
|
|
18
|
+
"roborean.image" = "roborean_documents_image.driver:create_driver"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/TNick/roborean"
|
|
23
|
+
Repository = "https://github.com/TNick/roborean"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/roborean_documents_image"]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Raster image document driver using Pillow."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
from typing import Any, Mapping
|
|
7
|
+
|
|
8
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
9
|
+
from roborean_documents_base.capabilities import assert_op_allowed
|
|
10
|
+
from roborean_spec import (
|
|
11
|
+
DocumentDriverManifest,
|
|
12
|
+
DocumentOperation,
|
|
13
|
+
DocumentPreview,
|
|
14
|
+
TemplateManifest,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ImageSession:
|
|
20
|
+
"""Pillow image session."""
|
|
21
|
+
|
|
22
|
+
document_id: str
|
|
23
|
+
driver_id: str
|
|
24
|
+
image: Any
|
|
25
|
+
draw: Any
|
|
26
|
+
ops_applied: list[dict[str, Any]] = field(default_factory=list)
|
|
27
|
+
drawn_texts: list[dict[str, Any]] = field(default_factory=list)
|
|
28
|
+
format: str = "PNG"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ImageDocumentDriver:
|
|
32
|
+
"""PNG/JPEG raster driver."""
|
|
33
|
+
|
|
34
|
+
driver_id = "roborean.image"
|
|
35
|
+
manifest = DocumentDriverManifest(
|
|
36
|
+
driverId="roborean.image",
|
|
37
|
+
version="0.3.0",
|
|
38
|
+
irFamily="raster",
|
|
39
|
+
capabilities=[
|
|
40
|
+
"set_metadata",
|
|
41
|
+
"raster.draw_text",
|
|
42
|
+
"raster.set_size",
|
|
43
|
+
"finalize",
|
|
44
|
+
],
|
|
45
|
+
supportsPreview=True,
|
|
46
|
+
supportsBrowserExecution=False,
|
|
47
|
+
supportsDiff=True,
|
|
48
|
+
requiresBackend=True,
|
|
49
|
+
templateMediaTypes=["image/png"],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
"""Initialize empty template state."""
|
|
54
|
+
self._template: bytes | None = None
|
|
55
|
+
self._manifest: TemplateManifest | None = None
|
|
56
|
+
self._settings: dict[str, Any] = {}
|
|
57
|
+
|
|
58
|
+
def load_template(self, template_ref, *, store, manifest) -> None:
|
|
59
|
+
"""Load optional base image bytes."""
|
|
60
|
+
self._template = store.load_bytes(template_ref)
|
|
61
|
+
self._manifest = manifest
|
|
62
|
+
|
|
63
|
+
def begin_session(
|
|
64
|
+
self, workspace, metadata: Mapping[str, Any]
|
|
65
|
+
) -> ImageSession:
|
|
66
|
+
"""Create a canvas from template or blank settings."""
|
|
67
|
+
settings = dict(metadata.get("settings") or {})
|
|
68
|
+
self._settings = settings
|
|
69
|
+
if self._template:
|
|
70
|
+
image = Image.open(io.BytesIO(self._template)).convert("RGB")
|
|
71
|
+
else:
|
|
72
|
+
width = int(settings.get("width", 800))
|
|
73
|
+
height = int(settings.get("height", 600))
|
|
74
|
+
background = settings.get("background", "#FFFFFF")
|
|
75
|
+
image = Image.new("RGB", (width, height), background)
|
|
76
|
+
return ImageSession(
|
|
77
|
+
document_id=str(metadata.get("documentId", "")),
|
|
78
|
+
driver_id=self.driver_id,
|
|
79
|
+
image=image,
|
|
80
|
+
draw=ImageDraw.Draw(image),
|
|
81
|
+
format=str(settings.get("format", "PNG")).upper(),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def apply_operation(
|
|
85
|
+
self, session: ImageSession, op: DocumentOperation
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Apply raster operations."""
|
|
88
|
+
assert_op_allowed(self.manifest, op)
|
|
89
|
+
data = op.model_dump(mode="python", by_alias=True)
|
|
90
|
+
session.ops_applied.append(op.model_dump(mode="json", by_alias=True))
|
|
91
|
+
if op.op == "raster.set_size":
|
|
92
|
+
size = (int(data["width"]), int(data["height"]))
|
|
93
|
+
session.image = session.image.resize(size)
|
|
94
|
+
session.draw = ImageDraw.Draw(session.image)
|
|
95
|
+
elif op.op == "raster.draw_text":
|
|
96
|
+
anchor = (int(data["anchor"][0]), int(data["anchor"][1]))
|
|
97
|
+
text = str(data["text"])
|
|
98
|
+
font = ImageFont.load_default()
|
|
99
|
+
session.draw.text(anchor, text, fill="black", font=font)
|
|
100
|
+
session.drawn_texts.append({"text": text, "anchor": list(anchor)})
|
|
101
|
+
|
|
102
|
+
def finalize(self, session: ImageSession) -> None:
|
|
103
|
+
"""No-op finalize."""
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
def serialize(self, session: ImageSession) -> bytes:
|
|
107
|
+
"""Return image bytes."""
|
|
108
|
+
buffer = io.BytesIO()
|
|
109
|
+
session.image.save(buffer, format=session.format)
|
|
110
|
+
return buffer.getvalue()
|
|
111
|
+
|
|
112
|
+
def preview(self, session: ImageSession) -> DocumentPreview:
|
|
113
|
+
"""Return drawing metadata for semantic preview."""
|
|
114
|
+
return DocumentPreview(
|
|
115
|
+
documentId=session.document_id,
|
|
116
|
+
mode="drawing-json",
|
|
117
|
+
body={
|
|
118
|
+
"size": list(session.image.size),
|
|
119
|
+
"texts": session.drawn_texts,
|
|
120
|
+
},
|
|
121
|
+
warnings=[],
|
|
122
|
+
generatedAt=datetime.now(UTC).isoformat(),
|
|
123
|
+
renderer={
|
|
124
|
+
"package": "roborean-documents-image",
|
|
125
|
+
"version": "0.3.0",
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def create_driver() -> ImageDocumentDriver:
|
|
131
|
+
"""Entry-point factory."""
|
|
132
|
+
return ImageDocumentDriver()
|