cutf 0.0.8__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.
- cutf-0.0.8/.github/workflows/ci.yml +36 -0
- cutf-0.0.8/.github/workflows/publish-chocolatey.yml +63 -0
- cutf-0.0.8/.github/workflows/publish-dockerhub.yml +62 -0
- cutf-0.0.8/.github/workflows/publish-pypi.yml +58 -0
- cutf-0.0.8/.github/workflows/release.yml +169 -0
- cutf-0.0.8/.gitignore +5 -0
- cutf-0.0.8/.mk-scripts/convert_logo.py +187 -0
- cutf-0.0.8/.mk-scripts/gen-project-py +70 -0
- cutf-0.0.8/.run/Makefile Targets.run.xml +350 -0
- cutf-0.0.8/LICENSE +21 -0
- cutf-0.0.8/Makefile +829 -0
- cutf-0.0.8/PKG-INFO +190 -0
- cutf-0.0.8/README.md +153 -0
- cutf-0.0.8/changelog.md +1 -0
- cutf-0.0.8/cutf/__init__.py +0 -0
- cutf-0.0.8/cutf/app.py +201 -0
- cutf-0.0.8/cutf/controller/__init__.py +0 -0
- cutf-0.0.8/cutf/controller/fileChecker.py +86 -0
- cutf-0.0.8/cutf/controller/fileController.py +119 -0
- cutf-0.0.8/cutf/controller/resultHandler.py +174 -0
- cutf-0.0.8/cutf/model/AppSetting.py +29 -0
- cutf-0.0.8/cutf/model/FileScanResult.py +31 -0
- cutf-0.0.8/cutf/model/MissingCharResult.py +23 -0
- cutf-0.0.8/cutf/model/__init__.py +8 -0
- cutf-0.0.8/cutf/util/__init__.py +0 -0
- cutf-0.0.8/cutf/util/code.py +43 -0
- cutf-0.0.8/cutf/util/iconv.py +51 -0
- cutf-0.0.8/cutf/util/log.py +35 -0
- cutf-0.0.8/cutf/util/path.py +38 -0
- cutf-0.0.8/genexe.txt +1 -0
- cutf-0.0.8/project.mk +262 -0
- cutf-0.0.8/pyproject.toml +48 -0
- cutf-0.0.8/ruff.toml +6 -0
- cutf-0.0.8/tests/test_app.py +56 -0
- cutf-0.0.8/tests/test_code_utils.py +20 -0
- cutf-0.0.8/tests/test_file_checker.py +17 -0
- cutf-0.0.8/tests/test_file_controller.py +104 -0
- cutf-0.0.8/tests/test_iconv_utils.py +21 -0
- cutf-0.0.8/tests/test_log_utils.py +14 -0
- cutf-0.0.8/tests/test_models.py +35 -0
- cutf-0.0.8/tests/test_path_utils.py +24 -0
- cutf-0.0.8/tests/test_result_handler.py +40 -0
- cutf-0.0.8/ty.toml +3 -0
- cutf-0.0.8/uv.lock +435 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
quality-and-build:
|
|
15
|
+
name: Quality And Build
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Install uv
|
|
23
|
+
uses: astral-sh/setup-uv@v6
|
|
24
|
+
with:
|
|
25
|
+
version: "latest"
|
|
26
|
+
enable-cache: true
|
|
27
|
+
|
|
28
|
+
- name: Setup CI Environment
|
|
29
|
+
run: make ci-setup
|
|
30
|
+
|
|
31
|
+
- name: Run Quality Gates
|
|
32
|
+
run: make ci-quality
|
|
33
|
+
|
|
34
|
+
- name: Build Package
|
|
35
|
+
run: make build
|
|
36
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
name: Publish Chocolatey
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
ci-config:
|
|
14
|
+
name: Read CI Config
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
outputs:
|
|
17
|
+
enable_chocolatey_publish: ${{ steps.config.outputs.enable_chocolatey_publish }}
|
|
18
|
+
chocolatey_environment: ${{ steps.config.outputs.chocolatey_environment }}
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Export Config To GitHub Output
|
|
25
|
+
id: config
|
|
26
|
+
run: make ci-export-config
|
|
27
|
+
|
|
28
|
+
publish:
|
|
29
|
+
name: Publish To Chocolatey
|
|
30
|
+
needs: ci-config
|
|
31
|
+
if: needs.ci-config.outputs.enable_chocolatey_publish == '1'
|
|
32
|
+
runs-on: windows-latest
|
|
33
|
+
environment: ${{ needs.ci-config.outputs.chocolatey_environment }}
|
|
34
|
+
|
|
35
|
+
steps:
|
|
36
|
+
- name: Checkout
|
|
37
|
+
uses: actions/checkout@v4
|
|
38
|
+
with:
|
|
39
|
+
fetch-depth: 0
|
|
40
|
+
|
|
41
|
+
- name: Install Make
|
|
42
|
+
run: choco install make
|
|
43
|
+
|
|
44
|
+
- name: Install uv
|
|
45
|
+
uses: astral-sh/setup-uv@v6
|
|
46
|
+
with:
|
|
47
|
+
version: "latest"
|
|
48
|
+
enable-cache: true
|
|
49
|
+
|
|
50
|
+
- name: Setup CI Environment
|
|
51
|
+
run: make ci-setup
|
|
52
|
+
|
|
53
|
+
- name: Build Windows Assets
|
|
54
|
+
run: make ci-build-chocolatey-assets
|
|
55
|
+
|
|
56
|
+
- name: Pack Chocolatey Package
|
|
57
|
+
run: make ci-pack-chocolatey
|
|
58
|
+
|
|
59
|
+
- name: Publish Chocolatey Package
|
|
60
|
+
run: make ci-publish-chocolatey
|
|
61
|
+
env:
|
|
62
|
+
CHOCOLATEY_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }}
|
|
63
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: Publish Docker Hub
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
ci-config:
|
|
14
|
+
name: Read CI Config
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
outputs:
|
|
17
|
+
enable_dockerhub_publish: ${{ steps.config.outputs.enable_dockerhub_publish }}
|
|
18
|
+
dockerhub_environment: ${{ steps.config.outputs.dockerhub_environment }}
|
|
19
|
+
dockerhub_image: ${{ steps.config.outputs.dockerhub_image }}
|
|
20
|
+
dockerfile: ${{ steps.config.outputs.dockerfile }}
|
|
21
|
+
docker_build_context: ${{ steps.config.outputs.docker_build_context }}
|
|
22
|
+
docker_push_latest: ${{ steps.config.outputs.docker_push_latest }}
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- name: Checkout
|
|
26
|
+
uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Export Config To GitHub Output
|
|
29
|
+
id: config
|
|
30
|
+
run: make ci-export-config
|
|
31
|
+
|
|
32
|
+
publish:
|
|
33
|
+
name: Publish Docker Image
|
|
34
|
+
needs: ci-config
|
|
35
|
+
if: needs.ci-config.outputs.enable_dockerhub_publish == '1'
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
environment: ${{ needs.ci-config.outputs.dockerhub_environment }}
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- name: Checkout
|
|
41
|
+
uses: actions/checkout@v4
|
|
42
|
+
with:
|
|
43
|
+
fetch-depth: 0
|
|
44
|
+
|
|
45
|
+
- name: Setup Docker Buildx
|
|
46
|
+
uses: docker/setup-buildx-action@v3
|
|
47
|
+
|
|
48
|
+
- name: Docker Hub Login
|
|
49
|
+
run: make ci-docker-login
|
|
50
|
+
env:
|
|
51
|
+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
52
|
+
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
53
|
+
|
|
54
|
+
- name: Resolve Docker Image Tag
|
|
55
|
+
id: docker_tag
|
|
56
|
+
run: make ci-export-docker-tag
|
|
57
|
+
|
|
58
|
+
- name: Build And Push Docker Image
|
|
59
|
+
run: make ci-publish-dockerhub
|
|
60
|
+
env:
|
|
61
|
+
DOCKER_IMAGE_TAG: ${{ steps.docker_tag.outputs.docker_image_tag }}
|
|
62
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Publish PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
ci-config:
|
|
15
|
+
name: Read CI Config
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
outputs:
|
|
18
|
+
enable_pypi_publish: ${{ steps.config.outputs.enable_pypi_publish }}
|
|
19
|
+
pypi_environment: ${{ steps.config.outputs.pypi_environment }}
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Export Config To GitHub Output
|
|
26
|
+
id: config
|
|
27
|
+
run: make ci-export-config
|
|
28
|
+
|
|
29
|
+
publish:
|
|
30
|
+
name: Publish To PyPI
|
|
31
|
+
needs: ci-config
|
|
32
|
+
if: needs.ci-config.outputs.enable_pypi_publish == '1'
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
environment: ${{ needs.ci-config.outputs.pypi_environment }}
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- name: Checkout
|
|
38
|
+
uses: actions/checkout@v4
|
|
39
|
+
with:
|
|
40
|
+
fetch-depth: 0
|
|
41
|
+
|
|
42
|
+
- name: Install uv
|
|
43
|
+
uses: astral-sh/setup-uv@v6
|
|
44
|
+
with:
|
|
45
|
+
version: "latest"
|
|
46
|
+
enable-cache: true
|
|
47
|
+
|
|
48
|
+
- name: Setup CI Environment
|
|
49
|
+
run: make ci-setup
|
|
50
|
+
|
|
51
|
+
- name: Build Package
|
|
52
|
+
run: make build-uv
|
|
53
|
+
|
|
54
|
+
- name: Publish Package
|
|
55
|
+
run: make ci-publish-pypi
|
|
56
|
+
env:
|
|
57
|
+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
58
|
+
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
ci-config:
|
|
14
|
+
name: Read CI Config
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
outputs:
|
|
17
|
+
release_branch: ${{ steps.config.outputs.release_branch }}
|
|
18
|
+
windows_installer_enabled: ${{ steps.config.outputs.windows_installer_enabled }}
|
|
19
|
+
build_linux: ${{ steps.config.outputs.build_linux }}
|
|
20
|
+
build_macos: ${{ steps.config.outputs.build_macos }}
|
|
21
|
+
build_windows: ${{ steps.config.outputs.build_windows }}
|
|
22
|
+
enable_release_docs: ${{ steps.config.outputs.enable_release_docs }}
|
|
23
|
+
release_artifacts: ${{ steps.config.outputs.release_artifacts }}
|
|
24
|
+
changelog_file: ${{ steps.config.outputs.changelog_file }}
|
|
25
|
+
release_notes_file: ${{ steps.config.outputs.release_notes_file }}
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout
|
|
29
|
+
uses: actions/checkout@v4
|
|
30
|
+
|
|
31
|
+
- name: Export Config To GitHub Output
|
|
32
|
+
id: config
|
|
33
|
+
run: make ci-export-config
|
|
34
|
+
|
|
35
|
+
changelog-and-release:
|
|
36
|
+
name: Generate Changelog And Create Release
|
|
37
|
+
needs: ci-config
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
|
|
40
|
+
steps:
|
|
41
|
+
- name: Checkout
|
|
42
|
+
uses: actions/checkout@v4
|
|
43
|
+
with:
|
|
44
|
+
fetch-depth: 0
|
|
45
|
+
|
|
46
|
+
- name: Install uv
|
|
47
|
+
uses: astral-sh/setup-uv@v6
|
|
48
|
+
with:
|
|
49
|
+
version: "latest"
|
|
50
|
+
enable-cache: true
|
|
51
|
+
|
|
52
|
+
- name: Setup CI Environment
|
|
53
|
+
run: make ci-setup
|
|
54
|
+
|
|
55
|
+
- name: Install git-cliff
|
|
56
|
+
run: make ci-install-git-cliff
|
|
57
|
+
|
|
58
|
+
- name: Generate Changelog
|
|
59
|
+
run: make ci-generate-changelog
|
|
60
|
+
|
|
61
|
+
- name: Commit Changelog
|
|
62
|
+
run: make ci-commit-changelog
|
|
63
|
+
|
|
64
|
+
- name: Generate Docs
|
|
65
|
+
if: needs.ci-config.outputs.enable_release_docs == '1'
|
|
66
|
+
run: make ci-generate-docs
|
|
67
|
+
|
|
68
|
+
- name: Commit Docs
|
|
69
|
+
if: needs.ci-config.outputs.enable_release_docs == '1'
|
|
70
|
+
run: make ci-commit-docs
|
|
71
|
+
|
|
72
|
+
- name: Generate Release Notes
|
|
73
|
+
run: make ci-generate-release-notes
|
|
74
|
+
|
|
75
|
+
- name: Create Release
|
|
76
|
+
uses: softprops/action-gh-release@v2
|
|
77
|
+
with:
|
|
78
|
+
body_path: ${{ needs.ci-config.outputs.release_notes_file }}
|
|
79
|
+
|
|
80
|
+
build-linux:
|
|
81
|
+
name: Build Assets (Linux)
|
|
82
|
+
needs:
|
|
83
|
+
- ci-config
|
|
84
|
+
- changelog-and-release
|
|
85
|
+
if: needs.ci-config.outputs.build_linux == '1'
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
|
|
88
|
+
steps:
|
|
89
|
+
- name: Checkout
|
|
90
|
+
uses: actions/checkout@v4
|
|
91
|
+
|
|
92
|
+
- name: Install uv
|
|
93
|
+
uses: astral-sh/setup-uv@v6
|
|
94
|
+
with:
|
|
95
|
+
version: "latest"
|
|
96
|
+
enable-cache: true
|
|
97
|
+
|
|
98
|
+
- name: Setup CI Environment
|
|
99
|
+
run: make ci-setup
|
|
100
|
+
|
|
101
|
+
- name: Build Release Assets
|
|
102
|
+
run: make ci-build-release-assets
|
|
103
|
+
|
|
104
|
+
- name: Upload Artifacts
|
|
105
|
+
uses: softprops/action-gh-release@v2
|
|
106
|
+
with:
|
|
107
|
+
files: ${{ needs.ci-config.outputs.release_artifacts }}
|
|
108
|
+
|
|
109
|
+
build-macos:
|
|
110
|
+
name: Build Assets (macOS)
|
|
111
|
+
needs:
|
|
112
|
+
- ci-config
|
|
113
|
+
- changelog-and-release
|
|
114
|
+
if: needs.ci-config.outputs.build_macos == '1'
|
|
115
|
+
runs-on: macos-latest
|
|
116
|
+
|
|
117
|
+
steps:
|
|
118
|
+
- name: Checkout
|
|
119
|
+
uses: actions/checkout@v4
|
|
120
|
+
|
|
121
|
+
- name: Install uv
|
|
122
|
+
uses: astral-sh/setup-uv@v6
|
|
123
|
+
with:
|
|
124
|
+
version: "latest"
|
|
125
|
+
enable-cache: true
|
|
126
|
+
|
|
127
|
+
- name: Setup CI Environment
|
|
128
|
+
run: make ci-setup
|
|
129
|
+
|
|
130
|
+
- name: Build Release Assets
|
|
131
|
+
run: make ci-build-release-assets
|
|
132
|
+
|
|
133
|
+
- name: Upload Artifacts
|
|
134
|
+
uses: softprops/action-gh-release@v2
|
|
135
|
+
with:
|
|
136
|
+
files: ${{ needs.ci-config.outputs.release_artifacts }}
|
|
137
|
+
|
|
138
|
+
build-windows:
|
|
139
|
+
name: Build Assets (Windows)
|
|
140
|
+
needs:
|
|
141
|
+
- ci-config
|
|
142
|
+
- changelog-and-release
|
|
143
|
+
if: needs.ci-config.outputs.build_windows == '1'
|
|
144
|
+
runs-on: windows-latest
|
|
145
|
+
|
|
146
|
+
steps:
|
|
147
|
+
- name: Checkout
|
|
148
|
+
uses: actions/checkout@v4
|
|
149
|
+
|
|
150
|
+
- name: Install Make
|
|
151
|
+
run: choco install make
|
|
152
|
+
|
|
153
|
+
- name: Install uv
|
|
154
|
+
uses: astral-sh/setup-uv@v6
|
|
155
|
+
with:
|
|
156
|
+
version: "latest"
|
|
157
|
+
enable-cache: true
|
|
158
|
+
|
|
159
|
+
- name: Setup CI Environment
|
|
160
|
+
run: make ci-setup
|
|
161
|
+
|
|
162
|
+
- name: Build Release Assets
|
|
163
|
+
run: make ci-build-release-assets
|
|
164
|
+
|
|
165
|
+
- name: Upload Artifacts
|
|
166
|
+
uses: softprops/action-gh-release@v2
|
|
167
|
+
with:
|
|
168
|
+
files: ${{ needs.ci-config.outputs.release_artifacts }}
|
|
169
|
+
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
convert_logo.py – Convert a .svg logo to all standard icon/image formats.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
uv run python .mk-scripts/convert_logo.py <path/to/logo.svg>
|
|
7
|
+
|
|
8
|
+
Generates in the SAME directory as the source SVG:
|
|
9
|
+
• PNG – 16, 32, 48, 64, 128, 256, 512, 1024 px
|
|
10
|
+
• JPG – 64, 128, 256, 512, 1024 px (white background, 95 % quality)
|
|
11
|
+
• ICO – multi-size (16, 24, 32, 48, 64, 128, 256 px)
|
|
12
|
+
• ICNS – macOS icon (16, 32, 64, 128, 256, 512, 1024 px)
|
|
13
|
+
|
|
14
|
+
Renderer: skia-python – ships pre-compiled Skia wheels on PyPI,
|
|
15
|
+
zero system-library dependencies (no cairo, no ImageMagick).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import io
|
|
21
|
+
import struct
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
# ── Sizes ─────────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
PNG_SIZES = [16, 32, 48, 64, 128, 256, 512, 1024]
|
|
28
|
+
JPG_SIZES = [64, 128, 256, 512, 1024]
|
|
29
|
+
ICO_SIZES = [16, 24, 32, 48, 64, 128, 256]
|
|
30
|
+
ICNS_SIZES = [16, 32, 64, 128, 256, 512, 1024]
|
|
31
|
+
|
|
32
|
+
# Apple ICNS OSType codes per pixel size (1x variants – PNG-compressed entries)
|
|
33
|
+
ICNS_TYPES: dict[int, bytes] = {
|
|
34
|
+
16: b"icp4",
|
|
35
|
+
32: b"icp5",
|
|
36
|
+
64: b"icp6",
|
|
37
|
+
128: b"ic07",
|
|
38
|
+
256: b"ic08",
|
|
39
|
+
512: b"ic09",
|
|
40
|
+
1024: b"ic10",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ── SVG renderer (skia-python) ────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
def _render_svg(svg_path: Path, size: int) -> bytes:
|
|
46
|
+
"""
|
|
47
|
+
Render *svg_path* at *size* × *size* pixels using Skia.
|
|
48
|
+
Returns raw PNG bytes (RGBA).
|
|
49
|
+
"""
|
|
50
|
+
import skia # type: ignore[import-untyped]
|
|
51
|
+
|
|
52
|
+
svg_bytes = svg_path.read_bytes()
|
|
53
|
+
stream = skia.MemoryStream(svg_bytes)
|
|
54
|
+
svg_dom = skia.SVGDOM.MakeFromStream(stream)
|
|
55
|
+
if svg_dom is None:
|
|
56
|
+
raise RuntimeError(f"Skia could not parse SVG: {svg_path}")
|
|
57
|
+
|
|
58
|
+
surface = skia.Surface(size, size)
|
|
59
|
+
with surface as canvas:
|
|
60
|
+
canvas.clear(skia.ColorTRANSPARENT)
|
|
61
|
+
svg_dom.setContainerSize(skia.Size.Make(size, size))
|
|
62
|
+
svg_dom.render(canvas)
|
|
63
|
+
|
|
64
|
+
image = surface.makeImageSnapshot()
|
|
65
|
+
png_data = image.encodeToData()
|
|
66
|
+
return bytes(png_data)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ── Image helpers ─────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
def _open_rgba(png_bytes: bytes):
|
|
72
|
+
"""Open raw PNG bytes as a PIL RGBA Image."""
|
|
73
|
+
from PIL import Image # type: ignore[import-untyped]
|
|
74
|
+
return Image.open(io.BytesIO(png_bytes)).convert("RGBA")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _resize(base_img, size: int):
|
|
78
|
+
from PIL import Image # type: ignore[import-untyped]
|
|
79
|
+
return base_img.resize((size, size), Image.LANCZOS)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _to_png_bytes(img) -> bytes:
|
|
83
|
+
buf = io.BytesIO()
|
|
84
|
+
img.save(buf, format="PNG", optimize=True)
|
|
85
|
+
return buf.getvalue()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ── ICNS builder ──────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
def _build_icns(png_map: dict[int, bytes]) -> bytes:
|
|
91
|
+
"""
|
|
92
|
+
Hand-craft an ICNS binary from {size: png_bytes}.
|
|
93
|
+
|
|
94
|
+
ICNS layout:
|
|
95
|
+
magic 4 bytes b"icns"
|
|
96
|
+
length 4 bytes total file length (big-endian uint32)
|
|
97
|
+
entries …
|
|
98
|
+
type 4 bytes
|
|
99
|
+
length 4 bytes (type + length + data)
|
|
100
|
+
data N bytes PNG bytes (ic07 … ic10 / icp4 … icp6)
|
|
101
|
+
"""
|
|
102
|
+
body = b""
|
|
103
|
+
for size in sorted(png_map):
|
|
104
|
+
ostype = ICNS_TYPES.get(size)
|
|
105
|
+
if ostype is None:
|
|
106
|
+
continue
|
|
107
|
+
data = png_map[size]
|
|
108
|
+
body += ostype + struct.pack(">I", 8 + len(data)) + data
|
|
109
|
+
return b"icns" + struct.pack(">I", 8 + len(body)) + body
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ── Main conversion ───────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
def convert(svg_path: Path) -> None:
|
|
115
|
+
if not svg_path.exists():
|
|
116
|
+
print(f"ERROR: file not found: {svg_path}", file=sys.stderr)
|
|
117
|
+
sys.exit(1)
|
|
118
|
+
if svg_path.suffix.lower() != ".svg":
|
|
119
|
+
print(f"ERROR: expected a .svg file, got: {svg_path}", file=sys.stderr)
|
|
120
|
+
sys.exit(1)
|
|
121
|
+
|
|
122
|
+
out_dir = svg_path.parent
|
|
123
|
+
stem = svg_path.stem
|
|
124
|
+
|
|
125
|
+
print(f"Source : {svg_path}")
|
|
126
|
+
print(f"Output : {out_dir}/")
|
|
127
|
+
print()
|
|
128
|
+
|
|
129
|
+
# Render at the largest size once; downscale from that master image.
|
|
130
|
+
max_size = max(PNG_SIZES + ICNS_SIZES)
|
|
131
|
+
print(f"Rendering SVG at {max_size}×{max_size} px (skia-python) …")
|
|
132
|
+
base_img = _open_rgba(_render_svg(svg_path, max_size))
|
|
133
|
+
print()
|
|
134
|
+
|
|
135
|
+
from PIL import Image as _Image # type: ignore[import-untyped]
|
|
136
|
+
|
|
137
|
+
# ── PNG ──────────────────────────────────────────────────────────────────
|
|
138
|
+
print("PNG files:")
|
|
139
|
+
for size in PNG_SIZES:
|
|
140
|
+
img = _resize(base_img, size)
|
|
141
|
+
out_path = out_dir / f"{stem}-{size}x{size}.png"
|
|
142
|
+
img.save(out_path, format="PNG", optimize=True)
|
|
143
|
+
print(f" {out_path}")
|
|
144
|
+
print()
|
|
145
|
+
|
|
146
|
+
# ── JPG ──────────────────────────────────────────────────────────────────
|
|
147
|
+
print("JPG files:")
|
|
148
|
+
for size in JPG_SIZES:
|
|
149
|
+
rgba = _resize(base_img, size)
|
|
150
|
+
bg = _Image.new("RGB", (size, size), (255, 255, 255))
|
|
151
|
+
bg.paste(rgba, mask=rgba.split()[3]) # composite over white bg
|
|
152
|
+
out_path = out_dir / f"{stem}-{size}x{size}.jpg"
|
|
153
|
+
bg.save(out_path, format="JPEG", quality=95, optimize=True)
|
|
154
|
+
print(f" {out_path}")
|
|
155
|
+
print()
|
|
156
|
+
|
|
157
|
+
# ── ICO ───────────────────────────────────────────────────────────────────
|
|
158
|
+
print("ICO file:")
|
|
159
|
+
ico_images = [_resize(base_img, s) for s in ICO_SIZES]
|
|
160
|
+
ico_path = out_dir / f"{stem}.ico"
|
|
161
|
+
ico_images[0].save(
|
|
162
|
+
ico_path,
|
|
163
|
+
format="ICO",
|
|
164
|
+
sizes=[(s, s) for s in ICO_SIZES],
|
|
165
|
+
append_images=ico_images[1:],
|
|
166
|
+
)
|
|
167
|
+
print(f" {ico_path}")
|
|
168
|
+
print()
|
|
169
|
+
|
|
170
|
+
# ── ICNS ─────────────────────────────────────────────────────────────────
|
|
171
|
+
print("ICNS file:")
|
|
172
|
+
icns_png_map = {s: _to_png_bytes(_resize(base_img, s)) for s in ICNS_SIZES}
|
|
173
|
+
icns_path = out_dir / f"{stem}.icns"
|
|
174
|
+
icns_path.write_bytes(_build_icns(icns_png_map))
|
|
175
|
+
print(f" {icns_path}")
|
|
176
|
+
print()
|
|
177
|
+
|
|
178
|
+
print("Done.")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
if len(sys.argv) != 2:
|
|
183
|
+
print("Usage: python convert_logo.py <path/to/logo.svg>", file=sys.stderr)
|
|
184
|
+
sys.exit(1)
|
|
185
|
+
convert(Path(sys.argv[1]))
|
|
186
|
+
|
|
187
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import sys
|
|
4
|
+
import tomllib
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main() -> int:
|
|
8
|
+
if len(sys.argv) != 3:
|
|
9
|
+
print(
|
|
10
|
+
"Usage: python .mk-scripts/gen-project-py <pyproject.toml> <output.py>",
|
|
11
|
+
file=sys.stderr,
|
|
12
|
+
)
|
|
13
|
+
return 1
|
|
14
|
+
|
|
15
|
+
pyproject_path = Path(sys.argv[1])
|
|
16
|
+
py_file = Path(sys.argv[2])
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
if not pyproject_path.exists():
|
|
20
|
+
raise FileNotFoundError(f"File {pyproject_path} does not exist.")
|
|
21
|
+
|
|
22
|
+
with pyproject_path.open("rb") as f:
|
|
23
|
+
data = tomllib.load(f)
|
|
24
|
+
|
|
25
|
+
project = data.get("project", {})
|
|
26
|
+
raw_authors = project.get("authors", [])
|
|
27
|
+
authors: list[tuple[str | None, str | None]] = []
|
|
28
|
+
for entry in raw_authors:
|
|
29
|
+
name = entry.get("name")
|
|
30
|
+
email = entry.get("email")
|
|
31
|
+
if name or email:
|
|
32
|
+
authors.append((name, email))
|
|
33
|
+
|
|
34
|
+
info = {
|
|
35
|
+
"name": project.get("name"),
|
|
36
|
+
"version": project.get("version"),
|
|
37
|
+
"description": project.get("description"),
|
|
38
|
+
"requires_python": project.get("requires-python"),
|
|
39
|
+
"authors": authors,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
authors_repr = "[" + ", ".join(
|
|
43
|
+
f"({repr(name)}, {repr(email)})" for name, email in info["authors"]
|
|
44
|
+
) + "]"
|
|
45
|
+
|
|
46
|
+
lines = [
|
|
47
|
+
"# fmt: off",
|
|
48
|
+
"name = " + repr(info["name"]),
|
|
49
|
+
"version = " + repr(info["version"]),
|
|
50
|
+
"description = " + repr(info["description"]),
|
|
51
|
+
"requires_python = " + repr(info["requires_python"]),
|
|
52
|
+
"authors = " + authors_repr,
|
|
53
|
+
"# fmt: on",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
py_file.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
with py_file.open("w", encoding="utf-8") as f:
|
|
58
|
+
f.write("\n".join(lines) + "\n")
|
|
59
|
+
|
|
60
|
+
print(f"Generated: {py_file}")
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
65
|
+
return 1
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
raise SystemExit(main())
|
|
70
|
+
|