diff --git a/README.md b/README.md index 16432ee..7b4c679 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PicGen Console -一个面向 OpenAI 兼容图像生成 / 编辑接口的本地工作台,当前版本 **0.1.62**。它把 +一个面向 OpenAI 兼容图像生成 / 编辑接口的本地工作台,当前版本 **0.1.63**。它把 `/v1/images/generations`、`/v1/images/edits` 与 `/v1/responses`(含 `image_generation` 工具) 包装成统一可观测的代理,前端是一套零依赖的 Web 控制台。 @@ -14,7 +14,9 @@ ![PicGen Console 主程序界面](demo1.png) -## 0.1.62 主要特性 +## 0.1.63 主要特性 + +- **行程图按需嵌入标题字体**:仅打包本次标题和日期实际使用的字形,保留原字体效果,同时显著降低 SVG 体积和客户浏览器解码压力。 - **简洁模式可直接接收分享**:主页新增“收到分享”区块,支持刷新并直接打开同事分享的成品;新用户无需先切换到专业模式。 @@ -123,10 +125,10 @@ PICGEN_LOG_FORMAT=json \ ### Docker ```bash -docker build -t minorli/picgen:0.1.62 . +docker build -t minorli/picgen:0.1.63 . docker run --rm -p 8000:8000 \ -v picgen-data:/app/data \ - minorli/picgen:0.1.62 + minorli/picgen:0.1.63 ``` 或: @@ -141,10 +143,10 @@ docker compose up -d ./scripts/docker-build-push.sh ``` -默认会构建并推送 `minorli/picgen:0.1.62`。也可以覆盖: +默认会构建并推送 `minorli/picgen:0.1.63`。也可以覆盖: ```bash -IMAGE=minorli/picgen VERSION=0.1.62 PLATFORM=linux/amd64 ./scripts/docker-build-push.sh +IMAGE=minorli/picgen VERSION=0.1.63 PLATFORM=linux/amd64 ./scripts/docker-build-push.sh ``` 镜像不会包含 `.env`、本地用户库或历史图片。容器内置 `HEALTHCHECK` 探测 `/api/health`,以非 root @@ -257,7 +259,7 @@ Bug 反馈和找回密码申请会先写入本地认证库,再优先发送到 ## 图像通道 -PicGen 0.1.62 把四类图像操作统一提交给 `/api/image-jobs`,实际通道由服务端决定: +PicGen 0.1.63 把四类图像操作统一提交给 `/api/image-jobs`,实际通道由服务端决定: | 用户操作 | 默认接口 | 默认模型 | | --- | --- | --- | diff --git a/docker-compose.yml b/docker-compose.yml index 55989e7..0428773 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: picgen: - image: minorli/picgen:0.1.62 + image: minorli/picgen:0.1.63 build: context: . ports: diff --git a/pyproject.toml b/pyproject.toml index 756aa19..07b29e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "picgen" -version = "0.1.62" +version = "0.1.63" description = "Enterprise-grade local web console for OpenAI-compatible image generation and editing APIs." readme = "README.md" requires-python = ">=3.12" @@ -23,6 +23,7 @@ dependencies = [ "pydantic-settings>=2.4.0", "anyio>=4.4.0", "pillow>=11.0.0", + "fonttools>=4.0.0", ] [project.optional-dependencies] diff --git a/scripts/docker-build-push.sh b/scripts/docker-build-push.sh index c9a86cd..6ab3b0d 100755 --- a/scripts/docker-build-push.sh +++ b/scripts/docker-build-push.sh @@ -2,7 +2,7 @@ set -euo pipefail IMAGE="${IMAGE:-minorli/picgen}" -VERSION="${VERSION:-0.1.62}" +VERSION="${VERSION:-0.1.63}" PLATFORM="${PLATFORM:-linux/amd64}" docker buildx build \ diff --git a/src/picgen/itinerary_map.py b/src/picgen/itinerary_map.py index 77fb3e2..2972987 100644 --- a/src/picgen/itinerary_map.py +++ b/src/picgen/itinerary_map.py @@ -3,6 +3,7 @@ import asyncio import base64 import html +import logging import math import os import re @@ -14,11 +15,14 @@ from collections.abc import Awaitable, Callable from datetime import datetime from functools import lru_cache +from io import BytesIO from pathlib import Path from typing import Any from urllib import parse import httpx +from fontTools import subset as font_subset +from fontTools.ttLib import TTFont from .config import Settings from .restricted_destinations import stop_has_restricted_destination @@ -32,6 +36,8 @@ LOGO_HREF = "6renyou.png" TITLE_FONT_FAMILY = "PicGenRouteTitle" TITLE_FONT_RELATIVE_PATH = Path("fonts/zcool-xiaowei/ZCOOLXiaoWei-Regular.ttf") +logger = logging.getLogger(__name__) +logging.getLogger("fontTools.subset").setLevel(logging.WARNING) INSTRUCTION_STOP_PREFIXES = ( "地点与地理校验", "地理校验", @@ -81,14 +87,29 @@ def _candidate_static_dirs() -> list[Path]: return deduped -@lru_cache(maxsize=1) -def _embedded_title_font_face_css_cached() -> str: +def _subset_title_font_bytes(font_path: Path, glyphs: str) -> bytes: + font = TTFont(font_path, recalcTimestamp=False) + try: + subsetter = font_subset.Subsetter(options=font_subset.Options()) + subsetter.populate(text=glyphs) + subsetter.subset(font) + output = BytesIO() + font.save(output, reorderTables=False) + return output.getvalue() + finally: + font.close() + + +@lru_cache(maxsize=64) +def _embedded_title_font_face_css_cached(glyphs: str) -> str: for static_dir in _candidate_static_dirs(): - font_path = static_dir / TITLE_FONT_RELATIVE_PATH try: - font_bytes = font_path.read_bytes() + font_bytes = _subset_title_font_bytes(static_dir / TITLE_FONT_RELATIVE_PATH, glyphs) except OSError: continue + except Exception as exc: + logger.warning("itinerary_title_font_subset_failed", extra={"error_type": type(exc).__name__}) + return "" encoded = base64.b64encode(font_bytes).decode("ascii") return ( "/* ZCOOL XiaoWei, SIL Open Font License 1.1 */" @@ -99,11 +120,14 @@ def _embedded_title_font_face_css_cached() -> str: return "" -def _embedded_title_font_face_css() -> str: +def _embedded_title_font_face_css(text: str) -> str: # Only cache SUCCESS: a transient read failure (missing volume, bad cwd at # first render) must not permanently strip the title font from every # poster this process renders afterwards. - css = _embedded_title_font_face_css_cached() + glyphs = "".join(sorted(set(text))) + if not glyphs: + return "" + css = _embedded_title_font_face_css_cached(glyphs) if not css: _embedded_title_font_face_css_cached.cache_clear() return css @@ -1743,8 +1767,10 @@ def render_itinerary_map_svg( f'' for segment in route_segments ] - title = _svg_text(plan.get("title") or "定制旅行路线图") - subtitle = _svg_text(plan.get("subtitle") or "") + title_text = str(plan.get("title") or "定制旅行路线图") + subtitle_text = str(plan.get("subtitle") or "") + title = _svg_text(title_text) + subtitle = _svg_text(subtitle_text) safe_background_url = ( background_image_url if background_image_url.startswith( @@ -1836,7 +1862,7 @@ def render_itinerary_map_svg( f'' ) - title_font_face_css = _embedded_title_font_face_css() if safe_background_url else "" + title_font_face_css = _embedded_title_font_face_css(f"{title_text}{subtitle_text}") if safe_background_url else "" return "\n".join( [ svg_open[:-1] + f' data-density="{"dense" if dense_layout else "normal"}">', diff --git a/static/app.js b/static/app.js index f4eff6a..c157e2f 100644 --- a/static/app.js +++ b/static/app.js @@ -2,14 +2,14 @@ import { calculateLogoPlacementScore, calculateOfficialLogoPixelMatch, chooseLogoPlacement, -} from "./logo-placement.mjs?v=0.1.62" +} from "./logo-placement.mjs?v=0.1.63" import { DEFAULT_RESPONSES_MODEL, RESPONSES_MODEL_STORAGE_VERSION, RESPONSES_REASONING_STORAGE_VERSION, migrateStoredResponsesReasoningSettings, migrateStoredResponsesSettings, -} from "./responses-settings.mjs?v=0.1.62" +} from "./responses-settings.mjs?v=0.1.63" const RESPONSES_REASONING_EFFORTS = new Set(["low", "medium", "high", "xhigh", "max", "ultra"]) const DEFAULT_RESPONSES_REASONING_EFFORT = "xhigh" diff --git a/static/index.html b/static/index.html index 4a0eef7..c7cbaf7 100644 --- a/static/index.html +++ b/static/index.html @@ -5,7 +5,7 @@ PicGen Console - +
@@ -1061,7 +1061,7 @@

管理员高级设置

@@ -1371,6 +1371,6 @@

图片预览

- + diff --git a/tests/test_api.py b/tests/test_api.py index e0bdab0..cc554d5 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -12,6 +12,7 @@ import anyio import pytest from fastapi.testclient import TestClient +from fontTools.ttLib import TTFont from PIL import Image from starlette.types import Scope @@ -1780,6 +1781,54 @@ def test_itinerary_map_svg_uses_soft_route_style_and_country_labels(): assert "font-family:'PicGenRouteTitle'" in svg +def test_itinerary_map_svg_embeds_a_bounded_title_font_subset(): + plan = build_itinerary_map_plan( + title="北疆<秋日&环线", + subtitle="9/5 - 9/8", + stops=[ + {"date": "D1", "name": "乌鲁木齐", "lat": 43.8256, "lng": 87.6168}, + {"date": "D2", "name": "喀纳斯", "lat": 48.7087, "lng": 87.0174}, + ], + ) + + svg = render_itinerary_map_svg(plan, background_image_url=f"data:image/png;base64,{TINY_PNG_B64}") + encoded_font = svg.split("data:font/ttf;base64,", 1)[1].split(")", 1)[0] + + font_bytes = base64.b64decode(encoded_font) + subset_font = TTFont(BytesIO(font_bytes)) + embedded_characters = {chr(codepoint) for codepoint in subset_font.getBestCmap()} + + assert set("北疆<秋日&环线9/5 - 8") <= embedded_characters + assert len(font_bytes) < 100_000 + assert len(svg.encode("utf-8")) < 200_000 + + +def test_itinerary_map_svg_falls_back_when_title_font_subsetting_fails(monkeypatch, caplog): + import picgen.itinerary_map as itinerary_map + + plan = build_itinerary_map_plan( + title="北疆秋日环线", + subtitle="9/5 - 9/8", + stops=[ + {"date": "D1", "name": "乌鲁木齐", "lat": 43.8256, "lng": 87.6168}, + {"date": "D2", "name": "喀纳斯", "lat": 48.7087, "lng": 87.0174}, + ], + ) + + def fail_subset(*_args): + raise ValueError("broken font") + + itinerary_map._embedded_title_font_face_css_cached.cache_clear() + monkeypatch.setattr(itinerary_map, "_subset_title_font_bytes", fail_subset) + + with caplog.at_level("WARNING"): + svg = render_itinerary_map_svg(plan, background_image_url=f"data:image/png;base64,{TINY_PNG_B64}") + + assert "@font-face{font-family:'PicGenRouteTitle'" not in svg + assert "北疆秋日环线" in svg + assert "itinerary_title_font_subset_failed" in caplog.text + + def test_itinerary_country_labels_stay_inside_portrait_canvas(): plan = build_itinerary_map_plan( title="欧洲文化路线", diff --git a/tests/test_static_assets.py b/tests/test_static_assets.py index 6bc9afe..88dae35 100644 --- a/tests/test_static_assets.py +++ b/tests/test_static_assets.py @@ -13,7 +13,7 @@ def test_legacy_responses_model_storage_is_migrated_once() -> None: settings_js = (ROOT_DIR / "static" / "responses-settings.mjs").read_text(encoding="utf-8") assert 'const DEPRECATED_RESPONSES_MODELS = new Set(["gpt-5.4"])' in app_js - assert 'from "./responses-settings.mjs?v=0.1.62"' in app_js + assert 'from "./responses-settings.mjs?v=0.1.63"' in app_js assert 'const LEGACY_DEFAULT_RESPONSES_MODEL = "gpt-5.5"' in settings_js assert "const RESPONSES_MODEL_STORAGE_VERSION = 4" in settings_js assert "function migrateStoredResponsesSettings" in settings_js @@ -38,7 +38,7 @@ def test_logo_overlay_uses_uploaded_asset_without_ai_guidance() -> None: assert 'const COMPANY_LOGO_URL = "6renyou.png"' in app_js assert "composeLogoOverlayForCandidates" in app_js assert "createOfficialLogoCanvas" in app_js - assert 'from "./logo-placement.mjs?v=0.1.62"' in app_js + assert 'from "./logo-placement.mjs?v=0.1.63"' in app_js assert "chooseLogoPlacement" in app_js assert "calculateLogoPlacementScore" in app_js assert "calculateOfficialLogoPixelMatch" in app_js diff --git a/uv.lock b/uv.lock index 956c2f0..2f6c562 100644 --- a/uv.lock +++ b/uv.lock @@ -83,6 +83,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/ff/2e4eca3ade2c22fe1dea7043b8ee9dabe47753349eb1b56a202de8af6349/fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f", size = 117683, upload-time = "2026-04-23T16:49:42.437Z" }, ] +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -299,11 +340,12 @@ wheels = [ [[package]] name = "picgen" -version = "0.1.62" +version = "0.1.63" source = { editable = "." } dependencies = [ { name = "anyio" }, { name = "fastapi" }, + { name = "fonttools" }, { name = "httpx" }, { name = "pillow" }, { name = "pydantic" }, @@ -330,6 +372,7 @@ dev = [ requires-dist = [ { name = "anyio", specifier = ">=4.4.0" }, { name = "fastapi", specifier = ">=0.115.0" }, + { name = "fonttools", specifier = ">=4.0.0" }, { name = "httpx", specifier = ">=0.27.0" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "prometheus-client", marker = "extra == 'metrics'", specifier = ">=0.20.0" },