roborean-documents-image 0.1.2__py3-none-any.whl

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,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()
@@ -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,5 @@
1
+ roborean_documents_image/driver.py,sha256=WOtIrphWEy2EvKQdR9VG-rkm1z2na49Lm3tHKk4v0nE,4428
2
+ roborean_documents_image-0.1.2.dist-info/METADATA,sha256=KVQrq_PHAY8P4ReqBFddZG9hBHI17V6jt5tiYGShZSQ,393
3
+ roborean_documents_image-0.1.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
4
+ roborean_documents_image-0.1.2.dist-info/entry_points.txt,sha256=xkNIP9b0V4lHUXDPUVipDgWSINbgMw2cvYs15bJuuC8,91
5
+ roborean_documents_image-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [roborean.document_drivers]
2
+ roborean.image = roborean_documents_image.driver:create_driver