imagespec-designer 0.1.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.
Files changed (34) hide show
  1. imagespec_designer-0.1.0/LICENSE +21 -0
  2. imagespec_designer-0.1.0/MANIFEST.in +2 -0
  3. imagespec_designer-0.1.0/NOTICE +12 -0
  4. imagespec_designer-0.1.0/PKG-INFO +75 -0
  5. imagespec_designer-0.1.0/README.ko.md +30 -0
  6. imagespec_designer-0.1.0/README.md +47 -0
  7. imagespec_designer-0.1.0/docs/development.md +43 -0
  8. imagespec_designer-0.1.0/docs/releasing.md +13 -0
  9. imagespec_designer-0.1.0/imagespec_designer/__init__.py +70 -0
  10. imagespec_designer-0.1.0/imagespec_designer/demo.py +162 -0
  11. imagespec_designer-0.1.0/imagespec_designer/document.py +463 -0
  12. imagespec_designer-0.1.0/imagespec_designer/facade.py +228 -0
  13. imagespec_designer-0.1.0/imagespec_designer/integrations/__init__.py +1 -0
  14. imagespec_designer-0.1.0/imagespec_designer/integrations/homeassistant.py +32 -0
  15. imagespec_designer-0.1.0/imagespec_designer/templates/price-tag.yaml +30 -0
  16. imagespec_designer-0.1.0/imagespec_designer/templates/simple-card.yaml +30 -0
  17. imagespec_designer-0.1.0/imagespec_designer/templates.py +78 -0
  18. imagespec_designer-0.1.0/imagespec_designer/web/designer.js +730 -0
  19. imagespec_designer-0.1.0/imagespec_designer/web/embed.js +88 -0
  20. imagespec_designer-0.1.0/imagespec_designer/web/icons.json +1 -0
  21. imagespec_designer-0.1.0/imagespec_designer/web/index.html +89 -0
  22. imagespec_designer-0.1.0/imagespec_designer/web/locales/en.json +20 -0
  23. imagespec_designer-0.1.0/imagespec_designer/web/locales/ko.json +20 -0
  24. imagespec_designer-0.1.0/imagespec_designer/web/style.css +336 -0
  25. imagespec_designer-0.1.0/imagespec_designer.egg-info/PKG-INFO +75 -0
  26. imagespec_designer-0.1.0/imagespec_designer.egg-info/SOURCES.txt +32 -0
  27. imagespec_designer-0.1.0/imagespec_designer.egg-info/dependency_links.txt +1 -0
  28. imagespec_designer-0.1.0/imagespec_designer.egg-info/entry_points.txt +2 -0
  29. imagespec_designer-0.1.0/imagespec_designer.egg-info/requires.txt +15 -0
  30. imagespec_designer-0.1.0/imagespec_designer.egg-info/top_level.txt +1 -0
  31. imagespec_designer-0.1.0/licenses/Apache-2.0.txt +201 -0
  32. imagespec_designer-0.1.0/pyproject.toml +43 -0
  33. imagespec_designer-0.1.0/setup.cfg +4 -0
  34. imagespec_designer-0.1.0/tests/test_designer.py +338 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 eigger
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.
@@ -0,0 +1,2 @@
1
+ include README.ko.md
2
+ recursive-include docs *.md
@@ -0,0 +1,12 @@
1
+ Third-party and adapted material
2
+
3
+ imagespec_designer/document.py includes geometry mappings and starter
4
+ specifications adapted from hass-ble-esl's BLE ESL designer, copyright
5
+ (c) 2025 eigger. The adapted code is distributed under the MIT License;
6
+ see LICENSE.
7
+
8
+ imagespec_designer/web/icons.json is a modified icon-name-to-glyph map derived
9
+ from Material Design Icons metadata by Pictogrammers, Austin Andrews, and
10
+ contributors. The map is licensed under Apache License 2.0; see
11
+ licenses/Apache-2.0.txt and https://github.com/Templarian/MaterialDesign-Webfont.
12
+ This package includes the map only; it does not include the icon font.
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: imagespec-designer
3
+ Version: 0.1.0
4
+ Summary: Framework-neutral visual editor and document tools for imagespec layouts
5
+ Author: eigger
6
+ License-Expression: MIT AND Apache-2.0
7
+ Project-URL: Homepage, https://github.com/eigger/imagespec-designer
8
+ Project-URL: Repository, https://github.com/eigger/imagespec-designer
9
+ Project-URL: Issues, https://github.com/eigger/imagespec-designer/issues
10
+ Requires-Python: >=3.13
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ License-File: NOTICE
14
+ License-File: licenses/Apache-2.0.txt
15
+ Requires-Dist: imagespec>=1.0.1
16
+ Requires-Dist: Pillow>=10.4
17
+ Requires-Dist: PyYAML>=6.0.1
18
+ Provides-Extra: ha
19
+ Requires-Dist: homeassistant>=2025.1; extra == "ha"
20
+ Provides-Extra: datamatrix
21
+ Requires-Dist: imagespec[datamatrix]>=1.0.1; extra == "datamatrix"
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=8; extra == "dev"
24
+ Requires-Dist: build>=1.2; extra == "dev"
25
+ Requires-Dist: ruff>=0.6; extra == "dev"
26
+ Requires-Dist: twine>=6; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # imagespec-designer
30
+
31
+ [![CI](https://github.com/eigger/imagespec-designer/actions/workflows/ci.yml/badge.svg)](https://github.com/eigger/imagespec-designer/actions/workflows/ci.yml)
32
+ [![PyPI](https://img.shields.io/pypi/v/imagespec-designer)](https://pypi.org/project/imagespec-designer/)
33
+
34
+ A portable visual editor and Python facade for [`imagespec`](https://github.com/eigger/imagespec). It provides a browser editor, YAML import and export, templates, and rendering helpers. The package requires Python 3.13+ and `imagespec>=1.0.1`; version 0.1.0 was tested with imagespec 1.0.1. Check the installed package version with `imagespec_designer.__version__`.
35
+
36
+ ## Install and run
37
+
38
+ ```sh
39
+ python -m pip install imagespec-designer
40
+ imagespec-designer-demo
41
+ ```
42
+
43
+ Open <http://127.0.0.1:8765>. Install `imagespec-designer[datamatrix]` to render Data Matrix elements.
44
+
45
+ ## Python API
46
+
47
+ ```python
48
+ from imagespec_designer import CanvasProfile, render_image
49
+
50
+ profile = CanvasProfile(width=296, height=128, palette="bwr")
51
+ payload = [
52
+ {"type": "rectangle", "x_start": 8, "y_start": 8, "x_end": 287, "y_end": 120, "fill": "black"}
53
+ ]
54
+ render_image(payload, profile).save("preview.png")
55
+ ```
56
+
57
+ `CanvasProfile` describes dimensions, rotation, and palette. `RenderContext` accepts host callbacks for fonts, images, and history. The public API also provides document validation, frame compilation, YAML conversion, and pixel-difference helpers.
58
+
59
+ ## Embed the editor
60
+
61
+ Serve the package's `web` assets and mount an editor instance:
62
+
63
+ ```js
64
+ import { mount } from "/assets/embed.js";
65
+
66
+ await mount(document.querySelector("#designer"), {
67
+ title: "Shelf label",
68
+ apiBase: "/api",
69
+ profile: { width: 296, height: 128, palette: "bwr" },
70
+ });
71
+ ```
72
+
73
+ The host supplies rendering, profile, transport, and any dynamic values. Unresolved templates can be imported and exported, but the host must evaluate them before rendering. This package covers portable imagespec frame documents; Home Assistant sensor/weather components and integration-specific service workflows remain host responsibilities. See [the embedding API](https://github.com/eigger/imagespec-designer/blob/main/imagespec_designer/web/embed.js) and [development guide](https://github.com/eigger/imagespec-designer/blob/main/docs/development.md).
74
+
75
+ Code and adapted BLE ESL geometry are MIT licensed. The modified Material Design Icons glyph map is Apache-2.0 licensed. See [NOTICE](NOTICE) and [licenses/Apache-2.0.txt](licenses/Apache-2.0.txt). See [release instructions](https://github.com/eigger/imagespec-designer/blob/main/docs/releasing.md) for PyPI Trusted Publishing setup.
@@ -0,0 +1,30 @@
1
+ # imagespec-designer
2
+
3
+ [CI](https://github.com/eigger/imagespec-designer/actions/workflows/ci.yml) · [PyPI](https://pypi.org/project/imagespec-designer/)
4
+
5
+ `imagespec`용 독립 시각 편집기와 Python 렌더링 API입니다. YAML 가져오기/내보내기와 디자인 템플릿을 제공합니다. Python 3.13 이상과 `imagespec>=1.0.1`이 필요하며, 0.1.0은 imagespec 1.0.1에서 테스트했습니다. 설치된 패키지 버전은 `imagespec_designer.__version__`으로 확인할 수 있습니다.
6
+
7
+ ## 설치 및 실행
8
+
9
+ ```sh
10
+ python -m pip install imagespec-designer
11
+ imagespec-designer-demo
12
+ ```
13
+
14
+ `http://127.0.0.1:8765`에서 편집기를 엽니다. Data Matrix 렌더링에는 `imagespec-designer[datamatrix]`를 설치하세요.
15
+
16
+ ## Python API
17
+
18
+ ```python
19
+ from imagespec_designer import CanvasProfile, render_image
20
+
21
+ profile = CanvasProfile(width=296, height=128, palette="bwr")
22
+ payload = [
23
+ {"type": "rectangle", "x_start": 8, "y_start": 8, "x_end": 287, "y_end": 120, "fill": "black"}
24
+ ]
25
+ render_image(payload, profile).save("preview.png")
26
+ ```
27
+
28
+ 삽입 방법과 개발 명령은 [영문 README](https://github.com/eigger/imagespec-designer/blob/main/README.md), [개발 안내](https://github.com/eigger/imagespec-designer/blob/main/docs/development.md)를 참고하세요. 템플릿 값은 호스트가 평가한 뒤 렌더링해야 합니다. Home Assistant 센서/날씨와 통합별 서비스 워크플로는 이 패키지의 범위에 포함되지 않습니다.
29
+
30
+ 코드와 BLE ESL에서 가져온 geometry는 MIT, 수정된 Material Design Icons glyph map은 Apache-2.0 라이선스입니다. 자세한 내용은 [NOTICE](https://github.com/eigger/imagespec-designer/blob/main/NOTICE)와 [Apache-2.0 라이선스](https://github.com/eigger/imagespec-designer/blob/main/licenses/Apache-2.0.txt)를 확인하세요. [릴리스 안내](https://github.com/eigger/imagespec-designer/blob/main/docs/releasing.md)에는 첫 PyPI 게시 설정이 있습니다.
@@ -0,0 +1,47 @@
1
+ # imagespec-designer
2
+
3
+ [![CI](https://github.com/eigger/imagespec-designer/actions/workflows/ci.yml/badge.svg)](https://github.com/eigger/imagespec-designer/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/imagespec-designer)](https://pypi.org/project/imagespec-designer/)
5
+
6
+ A portable visual editor and Python facade for [`imagespec`](https://github.com/eigger/imagespec). It provides a browser editor, YAML import and export, templates, and rendering helpers. The package requires Python 3.13+ and `imagespec>=1.0.1`; version 0.1.0 was tested with imagespec 1.0.1. Check the installed package version with `imagespec_designer.__version__`.
7
+
8
+ ## Install and run
9
+
10
+ ```sh
11
+ python -m pip install imagespec-designer
12
+ imagespec-designer-demo
13
+ ```
14
+
15
+ Open <http://127.0.0.1:8765>. Install `imagespec-designer[datamatrix]` to render Data Matrix elements.
16
+
17
+ ## Python API
18
+
19
+ ```python
20
+ from imagespec_designer import CanvasProfile, render_image
21
+
22
+ profile = CanvasProfile(width=296, height=128, palette="bwr")
23
+ payload = [
24
+ {"type": "rectangle", "x_start": 8, "y_start": 8, "x_end": 287, "y_end": 120, "fill": "black"}
25
+ ]
26
+ render_image(payload, profile).save("preview.png")
27
+ ```
28
+
29
+ `CanvasProfile` describes dimensions, rotation, and palette. `RenderContext` accepts host callbacks for fonts, images, and history. The public API also provides document validation, frame compilation, YAML conversion, and pixel-difference helpers.
30
+
31
+ ## Embed the editor
32
+
33
+ Serve the package's `web` assets and mount an editor instance:
34
+
35
+ ```js
36
+ import { mount } from "/assets/embed.js";
37
+
38
+ await mount(document.querySelector("#designer"), {
39
+ title: "Shelf label",
40
+ apiBase: "/api",
41
+ profile: { width: 296, height: 128, palette: "bwr" },
42
+ });
43
+ ```
44
+
45
+ The host supplies rendering, profile, transport, and any dynamic values. Unresolved templates can be imported and exported, but the host must evaluate them before rendering. This package covers portable imagespec frame documents; Home Assistant sensor/weather components and integration-specific service workflows remain host responsibilities. See [the embedding API](https://github.com/eigger/imagespec-designer/blob/main/imagespec_designer/web/embed.js) and [development guide](https://github.com/eigger/imagespec-designer/blob/main/docs/development.md).
46
+
47
+ Code and adapted BLE ESL geometry are MIT licensed. The modified Material Design Icons glyph map is Apache-2.0 licensed. See [NOTICE](NOTICE) and [licenses/Apache-2.0.txt](licenses/Apache-2.0.txt). See [release instructions](https://github.com/eigger/imagespec-designer/blob/main/docs/releasing.md) for PyPI Trusted Publishing setup.
@@ -0,0 +1,43 @@
1
+ # Development
2
+
3
+ Create an environment and install the package with its development tools:
4
+
5
+ ```sh
6
+ python3.13 -m venv .venv
7
+ .venv/bin/python -m pip install -e '.[dev,datamatrix]'
8
+ .venv/bin/ruff check .
9
+ .venv/bin/ruff format --check .
10
+ .venv/bin/pytest -q
11
+ ```
12
+
13
+ On Windows, use `.venv\Scripts\python.exe` and `.venv\Scripts\ruff.exe`.
14
+
15
+ For browser tests, install the locked Node dependencies and Playwright Chromium:
16
+
17
+ ```sh
18
+ npm ci
19
+ npx playwright install chromium
20
+ ```
21
+
22
+ Set `PYTHON` to the environment interpreter before running Playwright so its demo server uses the same installation. On macOS or Linux:
23
+
24
+ ```sh
25
+ PYTHON=.venv/bin/python npm run test:ui
26
+ ```
27
+
28
+ In PowerShell:
29
+
30
+ ```powershell
31
+ $env:PYTHON = ".venv/Scripts/python.exe"
32
+ npm run test:ui
33
+ ```
34
+
35
+ To build and inspect distributions:
36
+
37
+ ```sh
38
+ .venv/bin/python -m build
39
+ .venv/bin/python -m twine check dist/*
40
+ .venv/bin/python scripts/check_dist.py
41
+ ```
42
+
43
+ GitHub Actions runs these checks, tests the lowest allowed direct dependency versions, and smoke-tests an isolated wheel install.
@@ -0,0 +1,13 @@
1
+ # Release
2
+
3
+ Update `project.version` in `pyproject.toml`, merge the change, and create a GitHub Release from the matching `v<version>` tag, such as `v0.1.0`. The release workflow verifies the tag, runs the full CI workflow, then publishes its wheel and source archive to PyPI through OpenID Connect. The GitHub `pypi` environment permits `v*` tags.
4
+
5
+ Before the first release, register a pending GitHub Trusted Publisher on the [PyPI account Publishing page](https://pypi.org/manage/account/publishing/):
6
+
7
+ - Project name: `imagespec-designer`
8
+ - Owner: `eigger`
9
+ - Repository: `imagespec-designer`
10
+ - Workflow: `release.yml`
11
+ - Environment: `pypi`
12
+
13
+ PyPI creates the project after the first successful upload. A pending publisher does not reserve the project name before then. No PyPI API token is needed. Follow [PyPI's guide to creating a project through OIDC](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/) for the account settings steps.
@@ -0,0 +1,70 @@
1
+ """Framework-neutral visual design tools for imagespec."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("imagespec-designer")
7
+ except PackageNotFoundError: # source checkout before installation
8
+ __version__ = "0+unknown"
9
+
10
+ from .document import (
11
+ compile_document,
12
+ count_pixel_differences,
13
+ dump_yaml,
14
+ empty_document,
15
+ geometry_metadata,
16
+ import_payload,
17
+ load_yaml,
18
+ new_element,
19
+ validate_document,
20
+ )
21
+ from .facade import (
22
+ ENGINE_VERSION,
23
+ MIN_IMAGESPEC_VERSION,
24
+ TESTED_IMAGESPEC,
25
+ CanvasProfile,
26
+ DesignerError,
27
+ RenderContext,
28
+ RenderFailure,
29
+ ValidationError,
30
+ dither_methods,
31
+ element_specs,
32
+ known_types,
33
+ palette_names,
34
+ render_image,
35
+ resolve_templates,
36
+ starter_types,
37
+ validate_payload,
38
+ )
39
+ from .templates import apply_template, design_templates
40
+
41
+ __all__ = [
42
+ "ENGINE_VERSION",
43
+ "MIN_IMAGESPEC_VERSION",
44
+ "TESTED_IMAGESPEC",
45
+ "CanvasProfile",
46
+ "DesignerError",
47
+ "RenderContext",
48
+ "RenderFailure",
49
+ "ValidationError",
50
+ "__version__",
51
+ "apply_template",
52
+ "compile_document",
53
+ "count_pixel_differences",
54
+ "design_templates",
55
+ "dither_methods",
56
+ "dump_yaml",
57
+ "element_specs",
58
+ "empty_document",
59
+ "geometry_metadata",
60
+ "import_payload",
61
+ "known_types",
62
+ "load_yaml",
63
+ "new_element",
64
+ "palette_names",
65
+ "render_image",
66
+ "resolve_templates",
67
+ "starter_types",
68
+ "validate_document",
69
+ "validate_payload",
70
+ ]
@@ -0,0 +1,162 @@
1
+ """A tiny HTTP host used to try the designer without an application framework."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import base64
7
+ import json
8
+ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
9
+ from io import BytesIO
10
+ from pathlib import Path
11
+ from urllib.parse import urlparse
12
+
13
+ from . import (
14
+ ENGINE_VERSION,
15
+ MIN_IMAGESPEC_VERSION,
16
+ TESTED_IMAGESPEC,
17
+ CanvasProfile,
18
+ ValidationError,
19
+ __version__,
20
+ compile_document,
21
+ element_specs,
22
+ geometry_metadata,
23
+ new_element,
24
+ render_image,
25
+ starter_types,
26
+ validate_document,
27
+ )
28
+ from .document import dump_yaml, import_payload, load_yaml
29
+ from .templates import apply_template, design_templates
30
+
31
+ WEB = Path(__file__).parent / "web"
32
+
33
+
34
+ class Handler(SimpleHTTPRequestHandler):
35
+ def __init__(self, *args, **kwargs):
36
+ super().__init__(*args, directory=str(WEB), **kwargs)
37
+
38
+ def log_message(self, format, *args):
39
+ print("[designer] " + format % args)
40
+
41
+ def send_json(self, value, status=200):
42
+ data = json.dumps(value, ensure_ascii=False, default=str).encode()
43
+ self.send_response(status)
44
+ self.send_header("content-type", "application/json; charset=utf-8")
45
+ self.send_header("content-length", str(len(data)))
46
+ self.end_headers()
47
+ self.wfile.write(data)
48
+
49
+ def do_GET(self):
50
+ if self.path.startswith("/api/"):
51
+ self.send_json({"error": "POST JSON to this endpoint"}, 405)
52
+ return
53
+ if self.path.startswith("/assets/"):
54
+ self.path = self.path.removeprefix("/assets")
55
+ if self.path in ("/", "/index.html"):
56
+ self.path = "/index.html"
57
+ return super().do_GET()
58
+
59
+ def do_POST(self):
60
+ try:
61
+ length = int(self.headers.get("content-length", "0"))
62
+ if length > 2 * 1024 * 1024:
63
+ raise ValidationError("Request is too large")
64
+ data = json.loads(self.rfile.read(length) or b"{}")
65
+ profile = make_profile(data.get("profile"))
66
+ route = urlparse(self.path).path
67
+ if route == "/api/metadata":
68
+ result = {
69
+ "elements": element_specs(),
70
+ "starters": starter_types(),
71
+ "geometry": geometry_metadata(),
72
+ "designer_version": __version__,
73
+ "imagespec_version": ENGINE_VERSION,
74
+ "imagespec_minimum_version": MIN_IMAGESPEC_VERSION,
75
+ "imagespec_tested_version": TESTED_IMAGESPEC,
76
+ }
77
+ elif route == "/api/new-element":
78
+ result = {"element": new_element(data.get("type", "text"))}
79
+ elif route == "/api/templates":
80
+ result = design_templates()
81
+ elif route == "/api/render":
82
+ doc = validate_document(data.get("document"), profile)
83
+ payload = compile_document(doc)
84
+ image = render_image(payload, profile, background=doc.get("background"))
85
+ buffer = BytesIO()
86
+ image.save(buffer, "PNG")
87
+ result = {
88
+ "data_url": "data:image/png;base64,"
89
+ + base64.b64encode(buffer.getvalue()).decode()
90
+ }
91
+ elif route == "/api/validate":
92
+ result = {"valid": bool(validate_document(data.get("document"), profile))}
93
+ elif route == "/api/dump-yaml":
94
+ result = {"yaml": dump_yaml(data.get("document"))}
95
+ elif route == "/api/dump-payload":
96
+ doc = validate_document(data.get("document"), profile)
97
+ result = {"yaml": dump_yaml(compile_document(doc))}
98
+ elif route == "/api/parse-yaml":
99
+ value = load_yaml(data.get("text", ""))
100
+ if isinstance(value, dict) and value.get("version") == 1:
101
+ doc = validate_document(value, profile)
102
+ issues = []
103
+ else:
104
+ if (
105
+ isinstance(value, dict)
106
+ and "data" in value
107
+ and isinstance(value["data"], dict)
108
+ ):
109
+ value = value["data"]
110
+ if isinstance(value, dict):
111
+ bg = value.get("background")
112
+ value = value.get("payload", value.get("elements"))
113
+ else:
114
+ bg = None
115
+ doc, issues = import_payload(value, profile, background=bg)
116
+ result = {"document": doc, "issues": issues}
117
+ elif route == "/api/apply-template":
118
+ result = {
119
+ "document": apply_template(data.get("id", ""), profile, data.get("values"))
120
+ }
121
+ else:
122
+ self.send_json({"error": "Unknown endpoint"}, 404)
123
+ return
124
+ self.send_json(result)
125
+ except Exception as exc: # noqa: BLE001 - HTTP boundary returns errors to the UI.
126
+ self.send_json({"error": str(exc)}, 400)
127
+
128
+
129
+ def make_profile(value):
130
+ if not isinstance(value, dict):
131
+ value = {}
132
+ allowed = {
133
+ k: value[k]
134
+ for k in (
135
+ "width",
136
+ "height",
137
+ "rotate",
138
+ "rotate_mode",
139
+ "palette",
140
+ "background",
141
+ "name",
142
+ )
143
+ if k in value
144
+ }
145
+ return (
146
+ CanvasProfile(**allowed)
147
+ if "width" in allowed and "height" in allowed
148
+ else CanvasProfile(200, 120, name="Demo display")
149
+ )
150
+
151
+
152
+ def main():
153
+ parser = argparse.ArgumentParser(description=__doc__)
154
+ parser.add_argument("--host", default="127.0.0.1")
155
+ parser.add_argument("--port", type=int, default=8765)
156
+ args = parser.parse_args()
157
+ print(f"imagespec designer demo: http://{args.host}:{args.port}")
158
+ ThreadingHTTPServer((args.host, args.port), Handler).serve_forever()
159
+
160
+
161
+ if __name__ == "__main__":
162
+ main()