From f078dd32614b653b7b7a556361c8ad08369c4e41 Mon Sep 17 00:00:00 2001 From: douboer Date: Sun, 8 Feb 2026 22:31:25 +0800 Subject: [PATCH] update at 2026-02-08 22:31:25 --- apiserver/README.md | 21 +- apiserver/font2svg-api.service.example | 14 ++ apiserver/png_renderer.py | 55 +++++ apiserver/server.py | 135 +++++++----- apiserver/svg_to_png.js | 61 ++++++ fonts.conf | 49 +++-- miniprogram/README.md | 7 +- miniprogram/UPDATE_LOG.md | 1 + .../icons/{icons_idx _34.svg => check.svg} | 0 miniprogram/assets/icons/checkbox-no.svg | 3 + miniprogram/assets/icons/checkbox.png | Bin 2758 -> 0 bytes miniprogram/assets/icons/choose-color.png | Bin 7684 -> 0 bytes miniprogram/assets/icons/content.svg | 4 + miniprogram/assets/icons/expand.png | Bin 1456 -> 0 bytes miniprogram/assets/icons/export-png-s.svg | 4 + miniprogram/assets/icons/export-png.png | Bin 4203 -> 0 bytes miniprogram/assets/icons/export-svg-s.svg | 4 + miniprogram/assets/icons/export-svg.png | Bin 4529 -> 0 bytes miniprogram/assets/icons/export.png | Bin 1549 -> 0 bytes .../icons/{icons_idx _19.svg => favorite.svg} | 0 miniprogram/assets/icons/font-icon.png | Bin 4189 -> 0 bytes .../assets/icons/font-size-decrease.png | Bin 3657 -> 0 bytes .../assets/icons/font-size-increase.png | Bin 1805 -> 0 bytes miniprogram/assets/icons/icons_idx _12.svg | 3 - miniprogram/assets/icons/icons_idx _18.svg | 4 - miniprogram/assets/icons/icons_idx _29.svg | 3 - miniprogram/assets/icons/icons_idx _32.svg | 4 - miniprogram/assets/icons/icons_idx _33.svg | 3 - miniprogram/assets/icons/icons_idx _35.svg | 3 - miniprogram/assets/icons/icons_idx _36.svg | 3 - miniprogram/assets/icons/icons_idx _37.svg | 3 - miniprogram/assets/icons/icons_idx _38.svg | 3 - miniprogram/assets/icons/selectall.png | Bin 6177 -> 0 bytes miniprogram/assets/icons/unselectall.png | Bin 7144 -> 0 bytes miniprogram/pages/index/index.js | 201 +++++++++++++----- miniprogram/pages/index/index.wxml | 36 ++-- miniprogram/pages/index/index.wxss | 82 ++++--- miniprogram/utils/mp/file-export.js | 30 +++ miniprogram/utils/mp/render-api.js | 64 ++++++ 39 files changed, 587 insertions(+), 213 deletions(-) create mode 100644 apiserver/font2svg-api.service.example create mode 100644 apiserver/png_renderer.py create mode 100644 apiserver/svg_to_png.js rename miniprogram/assets/icons/{icons_idx _34.svg => check.svg} (100%) create mode 100644 miniprogram/assets/icons/checkbox-no.svg delete mode 100644 miniprogram/assets/icons/checkbox.png delete mode 100644 miniprogram/assets/icons/choose-color.png create mode 100644 miniprogram/assets/icons/content.svg delete mode 100644 miniprogram/assets/icons/expand.png create mode 100644 miniprogram/assets/icons/export-png-s.svg delete mode 100644 miniprogram/assets/icons/export-png.png create mode 100644 miniprogram/assets/icons/export-svg-s.svg delete mode 100644 miniprogram/assets/icons/export-svg.png delete mode 100644 miniprogram/assets/icons/export.png rename miniprogram/assets/icons/{icons_idx _19.svg => favorite.svg} (100%) delete mode 100644 miniprogram/assets/icons/font-icon.png delete mode 100644 miniprogram/assets/icons/font-size-decrease.png delete mode 100644 miniprogram/assets/icons/font-size-increase.png delete mode 100644 miniprogram/assets/icons/icons_idx _12.svg delete mode 100644 miniprogram/assets/icons/icons_idx _18.svg delete mode 100644 miniprogram/assets/icons/icons_idx _29.svg delete mode 100644 miniprogram/assets/icons/icons_idx _32.svg delete mode 100644 miniprogram/assets/icons/icons_idx _33.svg delete mode 100644 miniprogram/assets/icons/icons_idx _35.svg delete mode 100644 miniprogram/assets/icons/icons_idx _36.svg delete mode 100644 miniprogram/assets/icons/icons_idx _37.svg delete mode 100644 miniprogram/assets/icons/icons_idx _38.svg delete mode 100644 miniprogram/assets/icons/selectall.png delete mode 100644 miniprogram/assets/icons/unselectall.png diff --git a/apiserver/README.md b/apiserver/README.md index 0e78536..19862f2 100644 --- a/apiserver/README.md +++ b/apiserver/README.md @@ -2,7 +2,7 @@ `apiserver/` 提供微信小程序用的远端渲染接口: - 小程序只上传参数(字体 ID、文字、字号、颜色等) -- 服务端读取 `fonts.json` + `fonts/`,生成 SVG 后返回 +- 服务端读取 `fonts.json` + `fonts/`,生成 SVG/PNG 后返回 ## 1. 启动 @@ -43,6 +43,11 @@ python3 apiserver/server.py \ } ``` +### POST `/api/render-png` + +请求体与 `/api/render-svg` 相同,成功时直接返回 `image/png` 二进制。 +小程序应使用 `wx.request({ responseType: 'arraybuffer' })` 接收。 + 成功响应: ```json @@ -82,8 +87,20 @@ location /api/ { sudo nginx -t && sudo systemctl reload nginx ``` -## 4. 约束 +## 4. systemd(可选) + +仓库内提供示例:`apiserver/font2svg-api.service.example`,可复制到: + +```bash +sudo cp apiserver/font2svg-api.service.example /etc/systemd/system/font2svg-api.service +sudo systemctl daemon-reload +sudo systemctl enable --now font2svg-api +sudo systemctl status font2svg-api +``` + +## 5. 约束 - 字体解析完全基于 `fonts.json`,`fontId` 必须存在。 - 服务端启用 CORS,允许小程序访问。 - 不依赖 Flask/FastAPI,使用 Python 标准库 HTTP 服务。 +- `/api/render-png` 依赖 `node + sharp`(使用 `apiserver/svg_to_png.js` 转换)。 diff --git a/apiserver/font2svg-api.service.example b/apiserver/font2svg-api.service.example new file mode 100644 index 0000000..2881903 --- /dev/null +++ b/apiserver/font2svg-api.service.example @@ -0,0 +1,14 @@ +[Unit] +Description=Font2SVG API Server +After=network.target + +[Service] +Type=simple +User=gavin +WorkingDirectory=/home/gavin/font2svg +ExecStart=/home/gavin/font2svg/.venv/bin/python /home/gavin/font2svg/apiserver/server.py --host 127.0.0.1 --port 9300 --static-root /home/gavin/font2svg +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target diff --git a/apiserver/png_renderer.py b/apiserver/png_renderer.py new file mode 100644 index 0000000..8cf763b --- /dev/null +++ b/apiserver/png_renderer.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""服务端 PNG 渲染:通过 sharp 将 SVG 转为 PNG。""" + +import os +import subprocess + + +def _script_path(): + return os.path.join(os.path.dirname(__file__), "svg_to_png.js") + + +def render_png_from_svg(svg_text, width, height, *, timeout_seconds=20): + if not svg_text or not str(svg_text).strip(): + raise ValueError("SVG 内容为空") + + script = _script_path() + if not os.path.isfile(script): + raise FileNotFoundError(f"未找到 SVG 转 PNG 脚本: {script}") + + safe_width = max(1, min(4096, int(round(float(width or 0) or 0)))) + safe_height = max(1, min(4096, int(round(float(height or 0) or 0)))) + + cmd = [ + "node", + script, + "--width", + str(safe_width), + "--height", + str(safe_height), + ] + + try: + completed = subprocess.run( + cmd, + input=str(svg_text).encode("utf-8"), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=timeout_seconds, + check=False, + ) + except FileNotFoundError as error: + raise RuntimeError("未找到 node,请先安装 Node.js") from error + + if completed.returncode != 0: + stderr = completed.stderr.decode("utf-8", errors="replace").strip() + if "Cannot find module 'sharp'" in stderr: + raise RuntimeError("缺少 sharp 依赖,请在项目根目录执行: npm install") + raise RuntimeError(stderr or f"PNG 渲染失败,退出码: {completed.returncode}") + + png_bytes = completed.stdout + if not png_bytes: + raise RuntimeError("PNG 渲染失败,返回空内容") + + return png_bytes diff --git a/apiserver/server.py b/apiserver/server.py index 703b477..966e6a7 100644 --- a/apiserver/server.py +++ b/apiserver/server.py @@ -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}) diff --git a/apiserver/svg_to_png.js b/apiserver/svg_to_png.js new file mode 100644 index 0000000..d8b00b8 --- /dev/null +++ b/apiserver/svg_to_png.js @@ -0,0 +1,61 @@ +#!/usr/bin/env node + +const sharp = require('sharp') + +function parseArgs(argv) { + const parsed = {} + for (let i = 2; i < argv.length; i += 1) { + const key = argv[i] + const value = argv[i + 1] + if (key === '--width' && value) { + parsed.width = Number(value) + i += 1 + continue + } + if (key === '--height' && value) { + parsed.height = Number(value) + i += 1 + } + } + return parsed +} + +async function main() { + const args = parseArgs(process.argv) + const chunks = [] + + process.stdin.on('data', (chunk) => { + chunks.push(chunk) + }) + + process.stdin.on('end', async () => { + try { + const svgBuffer = Buffer.concat(chunks) + if (!svgBuffer.length) { + throw new Error('empty svg input') + } + + const width = Number.isFinite(args.width) && args.width > 0 ? Math.round(args.width) : null + const height = Number.isFinite(args.height) && args.height > 0 ? Math.round(args.height) : null + + let pipeline = sharp(svgBuffer, { density: 300 }) + if (width || height) { + pipeline = pipeline.resize({ + width: width || undefined, + height: height || undefined, + fit: 'contain', + background: { r: 255, g: 255, b: 255, alpha: 1 }, + withoutEnlargement: false, + }) + } + + const pngBuffer = await pipeline.png({ compressionLevel: 9 }).toBuffer() + process.stdout.write(pngBuffer) + } catch (error) { + process.stderr.write(`svg_to_png_error: ${error && error.message ? error.message : String(error)}\n`) + process.exit(1) + } + }) +} + +main() diff --git a/fonts.conf b/fonts.conf index f7579c5..da1d983 100644 --- a/fonts.conf +++ b/fonts.conf @@ -1,5 +1,5 @@ # Font2SVG - Nginx 配置(fonts.biboer.cn) -# 用途:为微信小程序提供 fonts.json 与字体文件静态托管 +# 用途:为微信小程序提供静态字体资源 + 远端 SVG 渲染 API server { listen 80; @@ -30,8 +30,8 @@ server { # 小程序跨域访问 add_header Access-Control-Allow-Origin "*" always; - add_header Access-Control-Allow-Methods "GET,HEAD,OPTIONS" always; - add_header Access-Control-Allow-Headers "Origin,Range,Accept,Content-Type" always; + add_header Access-Control-Allow-Methods "GET,HEAD,POST,OPTIONS" always; + add_header Access-Control-Allow-Headers "Origin,Range,Accept,Content-Type,Authorization" always; add_header Access-Control-Expose-Headers "Content-Length,Content-Range" always; # MIME @@ -44,8 +44,12 @@ server { application/vnd.ms-fontobject eot; } - # SVG 渲染 API(独立 Python 服务) - location /api/ { + # SVG 渲染 API(独立 Python 服务,systemd 监听 127.0.0.1:9300) + location ^~ /api/ { + # 预检请求:直接返回 204(CORS 头由 server 级 add_header 提供) + if ($request_method = OPTIONS) { + return 204; + } proxy_pass http://127.0.0.1:9300; proxy_http_version 1.1; proxy_set_header Host $host; @@ -57,13 +61,23 @@ server { proxy_read_timeout 60s; } + # 健康检查(可选) + location = /healthz { + proxy_pass http://127.0.0.1:9300/healthz; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + # fonts.json:短缓存,便于更新 location = /fonts.json { expires 1h; - add_header Cache-Control "public, must-revalidate"; + add_header Cache-Control "public, must-revalidate" always; add_header Access-Control-Allow-Origin "*" always; - add_header Access-Control-Allow-Methods "GET,HEAD,OPTIONS" always; - add_header Access-Control-Allow-Headers "Origin,Range,Accept,Content-Type" always; + add_header Access-Control-Allow-Methods "GET,HEAD,POST,OPTIONS" always; + add_header Access-Control-Allow-Headers "Origin,Range,Accept,Content-Type,Authorization" always; add_header Access-Control-Expose-Headers "Content-Length,Content-Range" always; try_files $uri =404; } @@ -71,27 +85,16 @@ server { # 字体文件:长缓存 location ~* \.(ttf|otf|woff|woff2|eot)$ { expires 30d; - add_header Cache-Control "public, immutable"; + add_header Cache-Control "public, immutable" always; add_header Access-Control-Allow-Origin "*" always; - add_header Access-Control-Allow-Methods "GET,HEAD,OPTIONS" always; - add_header Access-Control-Allow-Headers "Origin,Range,Accept,Content-Type" always; + add_header Access-Control-Allow-Methods "GET,HEAD,POST,OPTIONS" always; + add_header Access-Control-Allow-Headers "Origin,Range,Accept,Content-Type,Authorization" always; add_header Access-Control-Expose-Headers "Content-Length,Content-Range" always; try_files $uri =404; } - # 默认仅允许静态文件 + # 默认仅提供静态文件 location / { - # 处理预检请求(if 在 location 内合法) - if ($request_method = OPTIONS) { - add_header Access-Control-Allow-Origin "*"; - add_header Access-Control-Allow-Methods "GET,HEAD,OPTIONS"; - add_header Access-Control-Allow-Headers "Origin,Range,Accept,Content-Type"; - add_header Access-Control-Max-Age 1728000; - add_header Content-Length 0; - add_header Content-Type "text/plain; charset=utf-8"; - return 204; - } - try_files $uri =404; } diff --git a/miniprogram/README.md b/miniprogram/README.md index a633f31..c146d3b 100644 --- a/miniprogram/README.md +++ b/miniprogram/README.md @@ -8,7 +8,7 @@ - 远端 API 生成 SVG(服务端读取字体并渲染) - SVG 预览 - 导出 SVG 并调用 `wx.shareFileMessage` 分享 -- SVG 渲染到 Canvas 并导出 PNG,保存到系统相册 +- 远端 API 生成 PNG,保存到系统相册 ## 目录说明 @@ -34,6 +34,11 @@ miniprogram/ 5. Nginx 配置 `/api/` 反向代理到渲染服务。 6. 编译运行。 +## 导出说明 + +- `SVG`:受微信限制,`shareFileMessage` 需由单次点击直接触发,建议逐个字体导出。 +- `PNG`:由服务端 `POST /api/render-png` 直接返回二进制,小程序仅负责保存到相册。 + ## 字体清单格式(由服务端解析) `assets/fonts.json` 每项字段: diff --git a/miniprogram/UPDATE_LOG.md b/miniprogram/UPDATE_LOG.md index 31ae53c..c068bfd 100644 --- a/miniprogram/UPDATE_LOG.md +++ b/miniprogram/UPDATE_LOG.md @@ -9,6 +9,7 @@ - 新增 `apiserver/` 目录,服务端读取 `fonts.json` + `fonts/` 并渲染 SVG。 - 小程序新增 `miniprogram/utils/mp/render-api.js`,调用 `https://fonts.biboer.cn/api/render-svg`。 +- 小程序 PNG 导出改为调用 `https://fonts.biboer.cn/api/render-png`,不再依赖真机 Canvas 加载 SVG。 - `miniprogram/pages/index/index.js` 移除本地 `loadFontBuffer + worker.generateSvg` 依赖,改为远端渲染。 - `miniprogram/app.js` 新增全局配置: - `svgRenderApiUrl` diff --git a/miniprogram/assets/icons/icons_idx _34.svg b/miniprogram/assets/icons/check.svg similarity index 100% rename from miniprogram/assets/icons/icons_idx _34.svg rename to miniprogram/assets/icons/check.svg diff --git a/miniprogram/assets/icons/checkbox-no.svg b/miniprogram/assets/icons/checkbox-no.svg new file mode 100644 index 0000000..3827bda --- /dev/null +++ b/miniprogram/assets/icons/checkbox-no.svg @@ -0,0 +1,3 @@ + + + diff --git a/miniprogram/assets/icons/checkbox.png b/miniprogram/assets/icons/checkbox.png deleted file mode 100644 index a203dba47d0bf2d50c0e069e7b4563aea0b78ec6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2758 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 + + + diff --git a/miniprogram/assets/icons/expand.png b/miniprogram/assets/icons/expand.png deleted file mode 100644 index 29f40e0c65ae8d751dc874a72653f909c439309b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1456 zcmZ{k`9Bj30LEuyX71hz8xDF>fUl&3aBg$J1g}iD}@A;v~gC@K7p4mQ-@OAAIgM;OobHu(x3fR42 z)wUNR%me0D?Mj!T)8aCiBu>w9X3ijtuS-M7fe&^my`U@5g+58Tr*J~`Gj|LmfIkFW z%XgRU{RL<;=po^%X3X?qgaZInoiXs-f7gH$WX5i`ovxcH2n{YZyVJ1UAaJ)CqXQ0d z5`4?Gz2fh%s&RjvMZt^ivM-DmNE&2(x6 zx6_U7rszFkK)326`9($X+TcesCq7)mW)QLdAppD<=csEvUg+{k$nIW`i5Js=>agV* zOWa#u$9?0!B5s+NB@m+9Y1X5$W;G`O=5?BzZ!C**9g? zB#3&NX!e@ov6~BZ9f7G41ujjQeYhQG(|!_rlVoM1KsX?$3>wbUSxp9V-4swk>IRdz zfXiI!O@IfQpy`eq#kE?2?;tt_{tC;oiPhzMnR6rCXbq63QtDd0^R^!cN%@_J zj}yrcIyN<0j3{Th%JA|@(|+sOJ9U$taj6}ab4ZABP05NPUsm4H0^qDDs0ixqQw4aN z0BaE_MveLSp(A}`EyJg*WJ{g?P9XV%Yq^X`MC8P$b{`Jy)c%P1BX}Gdnz)Esp-U=Exsa zD@=jZQb(H;&c{r+>W2qKk7Ny~whqnfi?VoE{L(a=I()XP3RjIMyI|jQ?_bD5#p4>z zM9*OmF=K?)YXk4z&~E9EEsQrkJ7Sa&nH01AKw80A{@`_4#3yQ;gh^MXg5e%@lxM)r_!xH}o D7x1Re diff --git a/miniprogram/assets/icons/export-png-s.svg b/miniprogram/assets/icons/export-png-s.svg new file mode 100644 index 0000000..195d038 --- /dev/null +++ b/miniprogram/assets/icons/export-png-s.svg @@ -0,0 +1,4 @@ + + + + diff --git a/miniprogram/assets/icons/export-png.png b/miniprogram/assets/icons/export-png.png deleted file mode 100644 index 8a68af4a275426d83bb0edb76861c999f1b7b792..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4203 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 + + + diff --git a/miniprogram/assets/icons/export-svg.png b/miniprogram/assets/icons/export-svg.png deleted file mode 100644 index 6343e04a6d2a7db0f775ac134373b349f381e6b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4529 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 P00000NkvXXu0mjf&k%z- diff --git a/miniprogram/assets/icons/export.png b/miniprogram/assets/icons/export.png deleted file mode 100644 index 1d6d5c13d56bd67c3583f555dd064c596f7caac0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1549 zcma)6dpOez82*jPC1Vw>oTZ`Kq%)MFHjI#4o6TjYwbEsa5SmL3n{G#);WNGV*^sGF9CQj0ewIzaV{*WEG* z?Y5<53#;z>@83TqbHk-O&p8Z9dXjVHJvqlJFfk`yEvkPx`QZ_5Y@tp5QZVn}vx$$z zZ*|8T5FH1|k<4&;@cq=juA-fFdFz?ESrd2OzdfQm0PC}E@N^MaKNISlvl&b884ufI zm`d3&#R9~dng!ntIokIIgKsf8X1IiVKsaMDW?-epWpxBA;s=tf*%P+6v2WgIev1H)d7Lz(XpJ6Gv{-TC;(`+WE!0{?ZTmQPj?}!vPbOW@ zI$L8hCG0_nf(bW9<^_>cZL>O@`->BY<>^uH%9#42reIIYb{L^;-CT0c<4J{rn>Z3{ zBg~r6F{eyUi`{vJ5MVICXAC(&SZFzF%?y*D!O85D`K45ls%nk&Agg2e{Ymy+FJ;w! z)(LYHizG)@ji)>S?>68PS+|8G*TGKPV-yPOD8e0@ldw67WKP{HL95#`In4~c)Q;GJ zrTYh9nKZnPfs|o9legT#6(dHVDY3A<_n#T=3jp#|-J58F5l%#J7oSw5%vv+zv_NTUU<;jJ(B7Z6VPBw2 zpWK6Ty>MeetWDg55|hEy&f}(Flp#m#mf+5lgcNhnt@XX)2y~&q_c=d>?h4eJLy0T{+NOwPVs`#q%ttRMUMV3#` zxQF=NY=hbtVC}c6@UFYl3vKc96?z4qt5au>PffoV@rFbEE!v)orZ)S}ZP$Oo^;3_W zftO!ZWAs3C7bYWas%+v`vQ6n2((>F^T?gO=4(R)zNCuBTiZiEUYCfuB*w|eP#e8|6 zg(1RTrQb+T)e!--0LYQT{86;tx;44oVF*dl?EFg9QJ>Q%E^^WQK*(Kt-&=4E890nU zzLM|QfPQpdJzagr`HQNcpLhcSxx`RE@v=dbpu}(6NO6C{7Y8?jkW<@UT>v=j%?V+> z$704KNC~!#L!ZD_urt@XC#!bCR8ZmCIgF5D8IHAZzBE%Jx{`Lidx4(SgTBl?j4%Z7OB8+$$FC0F3$XJvX)+@@k0jW)^QyNT4^SL+_16PM z!qY6-P~Z%-oIi%7=zZ3DlJ{CE5Vs1G4k|N#h36>X-gI;O2~X5;`7E`VSw#z25); diff --git a/miniprogram/assets/icons/icons_idx _19.svg b/miniprogram/assets/icons/favorite.svg similarity index 100% rename from miniprogram/assets/icons/icons_idx _19.svg rename to miniprogram/assets/icons/favorite.svg diff --git a/miniprogram/assets/icons/font-icon.png b/miniprogram/assets/icons/font-icon.png deleted file mode 100644 index 50652efe0bc7c56536efc3b04d58cae8e47de6c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4189 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 diff --git a/miniprogram/assets/icons/font-size-decrease.png b/miniprogram/assets/icons/font-size-decrease.png deleted file mode 100644 index 427f6ab33d81c19ab0701f8447f0fcaacf6e9dae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3657 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 YQtinieeNqKnGgUhu2{n>jXfUy5AWdo8~^|S diff --git a/miniprogram/assets/icons/font-size-increase.png b/miniprogram/assets/icons/font-size-increase.png deleted file mode 100644 index 6b056418f84fbe0eda98009afe985aca472693c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1805 zcmYjSdo-Kb7XLy#qm6h(A6gO3q6iIDQ&iG4T2Uol_Sxrm_SwI+_da{AolEgMwncx3 zJ^%n)$ljztwPtRLuC{vb@r)i)E4>u&@KgYBu-X*Jg}eB40MPCxlMV-8xcx`@368m! z3G=MVD`0Xx8?Vbk+p-`F??_4f**i>I74B6VD<; z?J&A5)IS%Lnv$Zd_Mpwib2r?B&51x?fBz^{+=*Cd4dA1eb>38%A(kw9bL~u=2}V3ql8TLi_+IqEY3_k6QjhkSm(%<-@v-6QRaB_A z#_&8UBijyA7>VXn@&Ph4yeUz!9cZ~!2_;mwnOYHyqKZ8lgKu@_elrMK^N zOxCO?q!9Dy$R-p+&wZR3ar;ZU%5=OLsL6$FUIpM!cYfC4O|6hA-QY3JPuUd^9;Zc^}AVh&z&Ku`B6cEbn9XC6^m&ePpA*Jj4(yP@@S0veLeq4fCtA z=G)rehMnB%xL-cuQgTPE0r4uhqp67FM`N7o9+HTwcu)0vMV@InU}2#=#D4Nj!4R-E zUwm}uQh+xb``+P=*A?K zE?Tv%W1*=3{R1w0X=%wAc>AZs;NIw>BWR+qyY&_;5}@S4v+RC7Iv7c_o9j6 zw(HTEna3W9mfkpsxn%TeWo1Z7`bRpcqaqIry^ndQAsH2p$V>W~)r;*(MIPonady&gloHbIJIvAj31Ti&!Qxg&I*|xF$vx0_5Z> z7V@M--}=qB)L7NJ2Jf`}IW9K1SP|5U`%=Y=E6g;Z!k+w~T4zyViXP=yPEJn0uqpzmI3XLqzQhGh+C?QNpm`XWh@XY6G|Q{2G&6S4HHo)TF-+WLAs`zznVSs>WP zbZVotE-IH?QK_(vFnk7|3RUODZ)YgC49*hELHy!<@JW9D*sTE{HEB>A*V0C42v`4+ zAy{K9&ynmE84S!4)m>3l`fCJEJ1)8Mo+8`3JHjZclR8o}jD00kdkJkJ`xNbLbBX_h zc1EpJn_+bnto@C0%-I&9y^DrE{up`wH$}|Pf)Krb?-k|d-%%_azgmS2HYsrbE&?wi zoDmu_WoHuJA9nT=7(s)Y*s~INQef`2yRoqLdW&A=`J4Mnx!7KHKVbAg8&h>~pM?}# zb;;|KDz)k>k}6~B^j$_$VXrTqe17Tc*r2#EWDcpp>o&A@q!?7JZycx8ztFeFsC1ecM?`r^RmC@q*CdI+J*)SKp_57>~(7EFW-Z7OEe#c tOmP}N@{@91pV{Xk`thF=Lw&9pUpJf4y5Jkw8MQgz0J4`KNkEKZ{S8HdM?wGq diff --git a/miniprogram/assets/icons/icons_idx _12.svg b/miniprogram/assets/icons/icons_idx _12.svg deleted file mode 100644 index 1eb83c1..0000000 --- a/miniprogram/assets/icons/icons_idx _12.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/miniprogram/assets/icons/icons_idx _18.svg b/miniprogram/assets/icons/icons_idx _18.svg deleted file mode 100644 index a9d2261..0000000 --- a/miniprogram/assets/icons/icons_idx _18.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/miniprogram/assets/icons/icons_idx _29.svg b/miniprogram/assets/icons/icons_idx _29.svg deleted file mode 100644 index 1eb83c1..0000000 --- a/miniprogram/assets/icons/icons_idx _29.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/miniprogram/assets/icons/icons_idx _32.svg b/miniprogram/assets/icons/icons_idx _32.svg deleted file mode 100644 index a24279a..0000000 --- a/miniprogram/assets/icons/icons_idx _32.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/miniprogram/assets/icons/icons_idx _33.svg b/miniprogram/assets/icons/icons_idx _33.svg deleted file mode 100644 index 0d38a96..0000000 --- a/miniprogram/assets/icons/icons_idx _33.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/miniprogram/assets/icons/icons_idx _35.svg b/miniprogram/assets/icons/icons_idx _35.svg deleted file mode 100644 index b0168a5..0000000 --- a/miniprogram/assets/icons/icons_idx _35.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/miniprogram/assets/icons/icons_idx _36.svg b/miniprogram/assets/icons/icons_idx _36.svg deleted file mode 100644 index b760809..0000000 --- a/miniprogram/assets/icons/icons_idx _36.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/miniprogram/assets/icons/icons_idx _37.svg b/miniprogram/assets/icons/icons_idx _37.svg deleted file mode 100644 index b760809..0000000 --- a/miniprogram/assets/icons/icons_idx _37.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/miniprogram/assets/icons/icons_idx _38.svg b/miniprogram/assets/icons/icons_idx _38.svg deleted file mode 100644 index 700c186..0000000 --- a/miniprogram/assets/icons/icons_idx _38.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/miniprogram/assets/icons/selectall.png b/miniprogram/assets/icons/selectall.png deleted file mode 100644 index 433ee7aef751ebd2526c805af48c9f942ef04a3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6177 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 diff --git a/miniprogram/assets/icons/unselectall.png b/miniprogram/assets/icons/unselectall.png deleted file mode 100644 index 9f0291e636a3f6a5a3a0a602b8a36bb479242d7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7144 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 e1uIw$w*DW;i0@x(gHd|`0000:"/\\|?*\x00-\x1F]/g, '_').slice(0, 60) || 'font' + const filePath = `${wx.env.USER_DATA_PATH}/${safeName}_${Date.now()}.png` + const fs = wx.getFileSystemManager() + fs.writeFileSync(filePath, pngBuffer) + return filePath +} + Page({ data: { inputText: '星程字体转换', @@ -324,10 +355,19 @@ Page({ selectedFonts[index].svg = result.svg selectedFonts[index].width = result.width selectedFonts[index].height = result.height + selectedFonts[index].previewError = '' this.setData({ selectedFonts }) } } catch (error) { console.error('生成预览失败', error) + const selectedFonts = this.data.selectedFonts + const index = selectedFonts.findIndex(f => f.id === fontId) + if (index >= 0) { + selectedFonts[index].previewSrc = '' + selectedFonts[index].svg = '' + selectedFonts[index].previewError = (error && error.message) ? error.message : '预览生成失败' + this.setData({ selectedFonts }) + } } }, @@ -471,21 +511,14 @@ Page({ return } - wx.showLoading({ title: '导出 SVG 中', mask: true }) + wx.showModal({ + title: '微信限制说明', + content: '微信要求 shareFileMessage 必须由单次点击直接触发,暂不支持批量自动分享 SVG,请逐个字体导出。', + showCancel: false, + confirmText: '知道了', + }) + return - try { - for (const font of selectedFonts) { - const filePath = await saveSvgToUserPath(font.svg, font.name, this.data.inputText) - const filename = buildFilename(font.name, this.data.inputText, 'svg') - await shareLocalFile(filePath, filename) - } - wx.showToast({ title: 'SVG 导出完成', icon: 'success' }) - } catch (error) { - const message = error && error.errMsg ? error.errMsg : error.message - wx.showToast({ title: message || '导出 SVG 失败', icon: 'none', duration: 2400 }) - } finally { - wx.hideLoading() - } }, async exportAllPng() { @@ -500,21 +533,24 @@ Page({ try { for (const font of selectedFonts) { - const width = Math.max(64, Math.round(font.width || 1024)) - const height = Math.max(64, Math.round(font.height || 1024)) - - const pngPath = await exportSvgToPngByCanvas(this, { - svgString: font.svg, - width, - height, + const pngBuffer = await renderPngByApi({ + fontId: font.id, + text: this.data.inputText, + fontSize: Number(this.data.fontSize), + fillColor: normalizeHexColor(this.data.textColor), + letterSpacing: Number(this.data.letterSpacingInput || 0), + maxCharsPerLine: 45, }) + const pngPath = writePngBufferToTempFile(pngBuffer, font.name) - await savePngToAlbum(pngPath) + const saveResult = await savePngToAlbum(pngPath) + if (!saveResult.success) { + throw saveResult.error || new Error('保存 PNG 失败') + } } wx.showToast({ title: 'PNG 已保存到相册', icon: 'success' }) } catch (error) { - const message = error && error.errMsg ? error.errMsg : error.message - wx.showToast({ title: message || '导出 PNG 失败', icon: 'none', duration: 2400 }) + showExportError('导出 PNG 失败', error, '请稍后重试') } finally { wx.hideLoading() } @@ -531,17 +567,11 @@ Page({ // 如果只有一个字体,直接导出 if (selectedFonts.length === 1) { const font = selectedFonts[0] - wx.showLoading({ title: '导出 SVG 中', mask: true }) try { - const filePath = await saveSvgToUserPath(font.svg, font.name, this.data.inputText) - const filename = buildFilename(font.name, this.data.inputText, 'svg') - await shareLocalFile(filePath, filename) + await shareSvgFromUserTap(font.svg, font.name, this.data.inputText) wx.showToast({ title: 'SVG 已分享', icon: 'success' }) } catch (error) { - const message = error && error.errMsg ? error.errMsg : error.message - wx.showToast({ title: message || '导出 SVG 失败', icon: 'none', duration: 2400 }) - } finally { - wx.hideLoading() + showExportError('导出 SVG 失败', error, '请稍后重试') } } else { this.exportAllSvg() @@ -562,24 +592,24 @@ Page({ wx.showLoading({ title: '导出 PNG 中', mask: true }) try { - const width = Math.max(64, Math.round(font.width || 1024)) - const height = Math.max(64, Math.round(font.height || 1024)) - - const pngPath = await exportSvgToPngByCanvas(this, { - svgString: font.svg, - width, - height, + const pngBuffer = await renderPngByApi({ + fontId: font.id, + text: this.data.inputText, + fontSize: Number(this.data.fontSize), + fillColor: normalizeHexColor(this.data.textColor), + letterSpacing: Number(this.data.letterSpacingInput || 0), + maxCharsPerLine: 45, }) + const pngPath = writePngBufferToTempFile(pngBuffer, font.name) const saveResult = await savePngToAlbum(pngPath) if (saveResult.success) { wx.showToast({ title: 'PNG 已保存到相册', icon: 'success' }) } else { - wx.showToast({ title: '保存失败,请重试', icon: 'none' }) + showExportError('导出 PNG 失败', saveResult.error, '保存失败,请检查相册权限') } } catch (error) { - const message = error && error.errMsg ? error.errMsg : error.message - wx.showToast({ title: message || '导出 PNG 失败', icon: 'none', duration: 2400 }) + showExportError('导出 PNG 失败', error, '请稍后重试') } finally { wx.hideLoading() } @@ -588,6 +618,59 @@ Page({ } }, + // 单个字体导出 + async onExportSingleSvg(e) { + const fontId = e.currentTarget.dataset.fontId + const font = this.data.selectedFonts.find(f => f.id === fontId) + + if (!font || !font.svg) { + wx.showToast({ title: '请先生成预览', icon: 'none' }) + return + } + + try { + await shareSvgFromUserTap(font.svg, font.name, this.data.inputText) + wx.showToast({ title: 'SVG 已分享', icon: 'success' }) + } catch (error) { + showExportError('导出 SVG 失败', error, '请稍后重试') + } + }, + + async onExportSinglePng(e) { + const fontId = e.currentTarget.dataset.fontId + const font = this.data.selectedFonts.find(f => f.id === fontId) + + if (!font || !font.svg) { + wx.showToast({ title: '请先生成预览', icon: 'none' }) + return + } + + wx.showLoading({ title: '导出 PNG 中', mask: true }) + + try { + const pngBuffer = await renderPngByApi({ + fontId: font.id, + text: this.data.inputText, + fontSize: Number(this.data.fontSize), + fillColor: normalizeHexColor(this.data.textColor), + letterSpacing: Number(this.data.letterSpacingInput || 0), + maxCharsPerLine: 45, + }) + const pngPath = writePngBufferToTempFile(pngBuffer, font.name) + + const saveResult = await savePngToAlbum(pngPath) + if (saveResult.success) { + wx.showToast({ title: 'PNG 已保存到相册', icon: 'success' }) + } else { + showExportError('导出 PNG 失败', saveResult.error, '保存失败,请检查相册权限') + } + } catch (error) { + showExportError('导出 PNG 失败', error, '请稍后重试') + } finally { + wx.hideLoading() + } + }, + // 搜索功能 onToggleSearch() { this.setData({ showSearch: !this.data.showSearch }) diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml index d777289..f22b64b 100644 --- a/miniprogram/pages/index/index.wxml +++ b/miniprogram/pages/index/index.wxml @@ -38,8 +38,9 @@ - + + - - - - - - - - - - - @@ -71,9 +61,12 @@ {{item.name}} - - - + + + + + + @@ -84,6 +77,7 @@ mode="widthFix" src="{{item.previewSrc}}" /> + {{item.previewError}} 生成中... @@ -106,7 +100,7 @@ bindinput="onSearchInput" /> - + @@ -115,7 +109,8 @@ {{item.category}} @@ -131,7 +126,7 @@ - + @@ -156,7 +151,8 @@ {{item.category}} @@ -172,7 +168,7 @@ - + diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss index ae079a2..2a824eb 100644 --- a/miniprogram/pages/index/index.wxss +++ b/miniprogram/pages/index/index.wxss @@ -71,12 +71,20 @@ margin-top: 16rpx; } +.content-icon { + width: 44rpx; + height: 72rpx; + flex-shrink: 0; +} + .text-input-container { flex: 1; height: 100%; background: #F7F8FA; border-radius: 12rpx; padding: 0 6rpx; + display: flex; + align-items: center; } .text-input { @@ -86,30 +94,6 @@ color: #4E5969; } -.export-buttons { - display: flex; - align-items: center; - gap: 12rpx; - height: 100%; - background: #fff; - border: 1rpx solid #E5E6EB; - border-radius: 10rpx; - padding: 5rpx 10rpx; -} - -.export-btn { - width: 62rpx; - height: 62rpx; - display: flex; - align-items: center; - justify-content: center; -} - -.export-icon { - width: 100%; - height: 100%; -} - /* 效果预览区域 */ .preview-section { flex: 1; @@ -162,6 +146,35 @@ color: #86909C; } +.export-btns-inline { + display: flex; + align-items: center; + gap: 12rpx; +} + +.export-btn-sm { + display: flex; + align-items: center; + justify-content: center; + border-radius: 8rpx; + padding: 4rpx 6rpx; + width: 50rpx; + height: 22rpx; +} + +.export-btn-sm.export-svg-btn { + background: #8552A1; +} + +.export-btn-sm.export-png-btn { + background: #2420A8; +} + +.export-icon-sm { + width: 100%; + height: 100%; +} + .preview-checkbox { width: 14rpx; height: 14rpx; @@ -206,6 +219,13 @@ color: #86909C; } +.preview-error { + font-size: 22rpx; + color: #dc2626; + text-align: center; + padding: 0 12rpx; +} + .preview-empty { text-align: center; padding: 80rpx 0; @@ -240,6 +260,13 @@ align-items: center; gap: 8rpx; padding: 4rpx 0; + height: 56rpx; +} + +.selection-header .section-title { + padding: 0; + height: auto; + line-height: 56rpx; } .search-container { @@ -250,7 +277,7 @@ background: #F7F8FA; border-radius: 8rpx; padding: 4rpx 12rpx; - height: 56rpx; + height: 40rpx; } .search-icon { @@ -264,11 +291,12 @@ flex: 1; font-size: 22rpx; color: #4E5969; + height: 100%; } .search-toggle { - width: 56rpx; - height: 56rpx; + width: 40rpx; + height: 40rpx; display: flex; align-items: center; justify-content: center; diff --git a/miniprogram/utils/mp/file-export.js b/miniprogram/utils/mp/file-export.js index 46ba55f..2d91f61 100644 --- a/miniprogram/utils/mp/file-export.js +++ b/miniprogram/utils/mp/file-export.js @@ -25,6 +25,17 @@ async function saveSvgToUserPath(svgText, fontName, text) { return writeTextToUserPath(svgText, 'svg', filename) } +function saveSvgToUserPathSync(svgText, fontName, text) { + const filename = buildFilename(fontName, text, 'svg') + const filePath = `${wx.env.USER_DATA_PATH}/${filename}` + const fs = wx.getFileSystemManager() + fs.writeFileSync(filePath, svgText, 'utf8') + return { + filePath, + fileName: filename, + } +} + async function shareLocalFile(filePath, fileName) { if (typeof wx.shareFileMessage !== 'function') { throw new Error('当前微信版本不支持文件分享') @@ -40,10 +51,29 @@ async function shareLocalFile(filePath, fileName) { }) } +function shareSvgFromUserTap(svgText, fontName, text) { + if (typeof wx.shareFileMessage !== 'function') { + throw new Error('当前微信版本不支持文件分享') + } + + const { filePath, fileName } = saveSvgToUserPathSync(svgText, fontName, text) + + return new Promise((resolve, reject) => { + wx.shareFileMessage({ + filePath, + fileName, + success: resolve, + fail: reject, + }) + }) +} + module.exports = { sanitizeFilename, buildFilename, writeTextToUserPath, saveSvgToUserPath, + saveSvgToUserPathSync, shareLocalFile, + shareSvgFromUserTap, } diff --git a/miniprogram/utils/mp/render-api.js b/miniprogram/utils/mp/render-api.js index 12ebb5a..f3cc0c0 100644 --- a/miniprogram/utils/mp/render-api.js +++ b/miniprogram/utils/mp/render-api.js @@ -27,6 +27,24 @@ function normalizeResult(data) { } } +function decodeArrayBuffer(buffer) { + try { + if (!buffer) return '' + if (typeof buffer === 'string') return buffer + if (typeof TextDecoder === 'function') { + return new TextDecoder('utf-8').decode(buffer) + } + const bytes = new Uint8Array(buffer) + let text = '' + for (let i = 0; i < bytes.length; i += 1) { + text += String.fromCharCode(bytes[i]) + } + return decodeURIComponent(escape(text)) + } catch (error) { + return '' + } +} + async function renderSvgByApi(payload) { const app = getApp() const timeout = app && app.globalData && app.globalData.apiTimeoutMs @@ -62,6 +80,52 @@ async function renderSvgByApi(payload) { return normalizeResult(body.data) } +async function renderPngByApi(payload) { + const app = getApp() + const timeout = app && app.globalData && app.globalData.apiTimeoutMs + ? Number(app.globalData.apiTimeoutMs) + : 30000 + const baseApiUrl = buildApiUrl() + const apiUrl = /\/api\/render-svg$/.test(baseApiUrl) + ? baseApiUrl.replace(/\/api\/render-svg$/, '/api/render-png') + : `${baseApiUrl.replace(/\/$/, '')}/render-png` + + const response = await request({ + url: apiUrl, + method: 'POST', + timeout, + responseType: 'arraybuffer', + header: { + 'content-type': 'application/json', + accept: 'image/png', + }, + data: { + fontId: payload.fontId, + text: payload.text, + fontSize: payload.fontSize, + fillColor: payload.fillColor, + letterSpacing: payload.letterSpacing, + maxCharsPerLine: payload.maxCharsPerLine, + }, + }) + + if (!response || response.statusCode !== 200) { + let message = `PNG 渲染服务请求失败,状态码: ${response && response.statusCode}` + const maybeText = decodeArrayBuffer(response && response.data) + if (maybeText && maybeText.includes('error')) { + message = maybeText + } + throw new Error(message) + } + + if (!(response.data instanceof ArrayBuffer)) { + throw new Error('PNG 渲染服务返回格式无效') + } + + return response.data +} + module.exports = { renderSvgByApi, + renderPngByApi, }