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.
- imagespec_designer-0.1.0/LICENSE +21 -0
- imagespec_designer-0.1.0/MANIFEST.in +2 -0
- imagespec_designer-0.1.0/NOTICE +12 -0
- imagespec_designer-0.1.0/PKG-INFO +75 -0
- imagespec_designer-0.1.0/README.ko.md +30 -0
- imagespec_designer-0.1.0/README.md +47 -0
- imagespec_designer-0.1.0/docs/development.md +43 -0
- imagespec_designer-0.1.0/docs/releasing.md +13 -0
- imagespec_designer-0.1.0/imagespec_designer/__init__.py +70 -0
- imagespec_designer-0.1.0/imagespec_designer/demo.py +162 -0
- imagespec_designer-0.1.0/imagespec_designer/document.py +463 -0
- imagespec_designer-0.1.0/imagespec_designer/facade.py +228 -0
- imagespec_designer-0.1.0/imagespec_designer/integrations/__init__.py +1 -0
- imagespec_designer-0.1.0/imagespec_designer/integrations/homeassistant.py +32 -0
- imagespec_designer-0.1.0/imagespec_designer/templates/price-tag.yaml +30 -0
- imagespec_designer-0.1.0/imagespec_designer/templates/simple-card.yaml +30 -0
- imagespec_designer-0.1.0/imagespec_designer/templates.py +78 -0
- imagespec_designer-0.1.0/imagespec_designer/web/designer.js +730 -0
- imagespec_designer-0.1.0/imagespec_designer/web/embed.js +88 -0
- imagespec_designer-0.1.0/imagespec_designer/web/icons.json +1 -0
- imagespec_designer-0.1.0/imagespec_designer/web/index.html +89 -0
- imagespec_designer-0.1.0/imagespec_designer/web/locales/en.json +20 -0
- imagespec_designer-0.1.0/imagespec_designer/web/locales/ko.json +20 -0
- imagespec_designer-0.1.0/imagespec_designer/web/style.css +336 -0
- imagespec_designer-0.1.0/imagespec_designer.egg-info/PKG-INFO +75 -0
- imagespec_designer-0.1.0/imagespec_designer.egg-info/SOURCES.txt +32 -0
- imagespec_designer-0.1.0/imagespec_designer.egg-info/dependency_links.txt +1 -0
- imagespec_designer-0.1.0/imagespec_designer.egg-info/entry_points.txt +2 -0
- imagespec_designer-0.1.0/imagespec_designer.egg-info/requires.txt +15 -0
- imagespec_designer-0.1.0/imagespec_designer.egg-info/top_level.txt +1 -0
- imagespec_designer-0.1.0/licenses/Apache-2.0.txt +201 -0
- imagespec_designer-0.1.0/pyproject.toml +43 -0
- imagespec_designer-0.1.0/setup.cfg +4 -0
- 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,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
|
+
[](https://github.com/eigger/imagespec-designer/actions/workflows/ci.yml)
|
|
32
|
+
[](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
|
+
[](https://github.com/eigger/imagespec-designer/actions/workflows/ci.yml)
|
|
4
|
+
[](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()
|