update at 2026-02-08 22:31:25
This commit is contained in:
@@ -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})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user