update at 2026-02-08 22:31:25

This commit is contained in:
douboer
2026-02-08 22:31:25 +08:00
parent 0f5a7f0d85
commit f078dd3261
39 changed files with 587 additions and 213 deletions

View File

@@ -14,6 +14,10 @@ try:
from .renderer import MAX_CHARS_PER_LINE, render_svg_from_font_file
except ImportError:
from renderer import MAX_CHARS_PER_LINE, render_svg_from_font_file
try:
from .png_renderer import render_png_from_svg
except ImportError:
from png_renderer import render_png_from_svg
LOGGER = logging.getLogger("font2svg.api")
@@ -175,6 +179,66 @@ class RenderHandler(BaseHTTPRequestHandler):
self.end_headers()
self.wfile.write(body)
def _send_binary(self, status_code, body, content_type):
self.send_response(status_code)
self._set_cors_headers()
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _parse_render_payload(self):
try:
content_length = int(self.headers.get("Content-Length", "0") or "0")
except ValueError:
raise ValueError("请求长度无效") from None
if content_length <= 0 or content_length > 256 * 1024:
raise ValueError("请求体大小无效")
try:
raw_body = self.rfile.read(content_length)
payload = json.loads(raw_body.decode("utf-8"))
except json.JSONDecodeError:
raise ValueError("请求体不是有效 JSON") from None
if not isinstance(payload, dict):
raise ValueError("请求体格式错误")
font_id = str(payload.get("fontId") or "").strip()
text = str(payload.get("text") or "")
if not font_id:
raise ValueError("缺少 fontId")
if not text.strip():
raise ValueError("文本内容不能为空")
return {
"fontId": font_id,
"text": text,
"fontSize": _safe_number(payload.get("fontSize"), 120.0, 8.0, 1024.0, float),
"letterSpacing": _safe_number(payload.get("letterSpacing"), 0.0, -2.0, 5.0, float),
"maxCharsPerLine": _safe_number(
payload.get("maxCharsPerLine"),
MAX_CHARS_PER_LINE,
1,
300,
int,
),
"fillColor": _normalize_hex_color(payload.get("fillColor"), "#000000"),
}
def _render_svg_core(self, render_params):
font_info = self.catalog.get(render_params["fontId"])
result = render_svg_from_font_file(
font_info["path"],
render_params["text"],
font_size=render_params["fontSize"],
fill_color=render_params["fillColor"],
letter_spacing=render_params["letterSpacing"],
max_chars_per_line=render_params["maxCharsPerLine"],
)
return font_info, result
def do_OPTIONS(self): # noqa: N802
self.send_response(204)
self._set_cors_headers()
@@ -195,61 +259,13 @@ class RenderHandler(BaseHTTPRequestHandler):
def do_POST(self): # noqa: N802
parsed = urlparse(self.path)
if parsed.path != "/api/render-svg":
if parsed.path not in ("/api/render-svg", "/api/render-png"):
self._send_json(404, {"ok": False, "error": "Not Found"})
return
try:
content_length = int(self.headers.get("Content-Length", "0") or "0")
except ValueError:
self._send_json(400, {"ok": False, "error": "请求长度无效"})
return
if content_length <= 0 or content_length > 256 * 1024:
self._send_json(400, {"ok": False, "error": "请求体大小无效"})
return
try:
raw_body = self.rfile.read(content_length)
payload = json.loads(raw_body.decode("utf-8"))
except json.JSONDecodeError:
self._send_json(400, {"ok": False, "error": "请求体不是有效 JSON"})
return
if not isinstance(payload, dict):
self._send_json(400, {"ok": False, "error": "请求体格式错误"})
return
font_id = str(payload.get("fontId") or "").strip()
text = str(payload.get("text") or "")
if not font_id:
self._send_json(400, {"ok": False, "error": "缺少 fontId"})
return
if not text.strip():
self._send_json(400, {"ok": False, "error": "文本内容不能为空"})
return
font_size = _safe_number(payload.get("fontSize"), 120.0, 8.0, 1024.0, float)
letter_spacing = _safe_number(payload.get("letterSpacing"), 0.0, -2.0, 5.0, float)
max_chars_per_line = _safe_number(
payload.get("maxCharsPerLine"),
MAX_CHARS_PER_LINE,
1,
300,
int,
)
fill_color = _normalize_hex_color(payload.get("fillColor"), "#000000")
try:
font_info = self.catalog.get(font_id)
result = render_svg_from_font_file(
font_info["path"],
text,
font_size=font_size,
fill_color=fill_color,
letter_spacing=letter_spacing,
max_chars_per_line=max_chars_per_line,
)
render_params = self._parse_render_payload()
font_info, result = self._render_svg_core(render_params)
except KeyError as error:
self._send_json(404, {"ok": False, "error": str(error)})
return
@@ -264,12 +280,27 @@ class RenderHandler(BaseHTTPRequestHandler):
self._send_json(500, {"ok": False, "error": str(error)})
return
if parsed.path == "/api/render-png":
try:
png_bytes = render_png_from_svg(
result["svg"],
result["width"],
result["height"],
)
except Exception as error:
LOGGER.exception("PNG 渲染失败")
self._send_json(500, {"ok": False, "error": str(error)})
return
self._send_binary(200, png_bytes, "image/png")
return
response_data = {
"svg": result["svg"],
"width": result["width"],
"height": result["height"],
"fontName": result.get("fontName") or font_info.get("name") or "Unknown",
"fontId": font_id,
"fontId": render_params["fontId"],
}
self._send_json(200, {"ok": True, "data": response_data})