From 0181066b346d4df221710b9eeba28111296881b5 Mon Sep 17 00:00:00 2001 From: zzc <1761997216@qq.com> Date: Thu, 22 Jan 2026 11:04:47 +0800 Subject: [PATCH] fix: card content font --- pages/make/index.vue | 191 +++++++++++++++++++++++++++---------------- 1 file changed, 119 insertions(+), 72 deletions(-) diff --git a/pages/make/index.vue b/pages/make/index.vue index 6cd83cd..968668e 100644 --- a/pages/make/index.vue +++ b/pages/make/index.vue @@ -228,7 +228,8 @@ @@ -275,12 +276,17 @@ const fontList = [ { name: "手写", family: "ShouXie", - url: "https://file.lihailezzc.com/resource/font/shouxie.ttf", // 示例地址 + url: "https://file.lihailezzc.com/ZhiMangXing-Regular.ttf", // 示例地址 }, { name: "可爱", family: "KeAi", - url: "https://file.lihailezzc.com/resource/font/keai.ttf", // 示例地址 + url: "https://file.lihailezzc.com/ZCOOLKuaiLe-Regular.ttf", // 示例地址 + }, + { + name: "草书", + family: "LiuJianMaoCao", + url: "https://file.lihailezzc.com/LiuJianMaoCao-Regular.ttf", // 示例地址 }, ]; const selectedFont = ref(fontList[0]); @@ -289,8 +295,10 @@ const changeFont = (font) => { if (font.url) { uni.showLoading({ title: "加载字体中" }); uni.loadFontFace({ + global: true, family: font.family, source: `url("${font.url}")`, + scopes: ["webview", "native"], success: () => { selectedFont.value = font; uni.hideLoading(); @@ -496,75 +504,113 @@ const showMore = () => { }; const saveByCanvas = async (save = true) => { - const ctx = uni.createCanvasContext("cardCanvas"); + return new Promise((resolve, reject) => { + const query = uni.createSelectorQuery(); + query + .select("#cardCanvas") + .fields({ node: true, size: true }) + .exec(async (res) => { + if (!res[0] || !res[0].node) { + reject("Canvas not found"); + return; + } - // 画布尺寸(rpx 转 px) - const W = 540; - const H = 960; + const canvas = res[0].node; + const ctx = canvas.getContext("2d"); - // 1️⃣ 画背景 - // ⭐ 先加载背景图 - const [bgPath, avatarPath] = await Promise.all([ - loadImage(currentTemplate?.value?.imageUrl), - loadImage(userAvatar.value), - ]); + // 初始化画布尺寸 + const dpr = uni.getSystemInfoSync().pixelRatio; + // 保持 540x960 的逻辑尺寸,为了清晰度可以考虑 * dpr, + // 但为了保持和原来一致的输出尺寸,这里先固定物理尺寸 + // 如果要高清,可以 set width = 540 * dpr,然后 scale(dpr, dpr) + // 这里为了简单兼容原逻辑,我们让物理尺寸等于逻辑尺寸 + canvas.width = 540; + canvas.height = 960; - ctx.drawImage(bgPath, 0, 0, W, H); + // 画布尺寸(rpx 转 px) + const W = 540; + const H = 960; - // 2️⃣ 半透明遮罩(和你 UI 一致) - ctx.setFillStyle("rgba(0,0,0,0.08)"); - ctx.fillRect(0, 0, W, H); + // 辅助函数:加载图片为 Image 对象 + const loadCanvasImage = (url) => { + return new Promise((resolve, reject) => { + const img = canvas.createImage(); + img.onload = () => resolve(img); + img.onerror = (e) => reject(e); + img.src = url; + }); + }; - // 3️⃣ 标题 - ctx.setFillStyle("#ffffff"); - ctx.setFontSize(42); - ctx.setTextAlign("center"); - ctx.fillText("新春快乐", W / 2, 120); + try { + // 1️⃣ 画背景 + // ⭐ 先加载背景图 + const [bgImg, avatarImg] = await Promise.all([ + loadCanvasImage(currentTemplate?.value?.imageUrl), + loadCanvasImage(userAvatar.value), + ]); - ctx.setFontSize(22); - ctx.setGlobalAlpha(0.9); - ctx.fillText("2026 YEAR OF THE HORSE", W / 2, 165); - ctx.setGlobalAlpha(1); + ctx.drawImage(bgImg, 0, 0, W, H); - // 4️⃣ 祝福语气泡 - drawBubbleText(ctx, { - text: targetName.value + "\n " + blessingText.value, - x: 70, - y: 260 + bubbleOffsetY.value, - maxWidth: 400, - fontSize: 32, - lineHeight: 46, - backgroundColor: "rgba(255,255,255,0.85)", - textColor: selectedColor.value, - fontFamily: selectedFont.value.family, - }); + // 2️⃣ 半透明遮罩(和你 UI 一致) + ctx.fillStyle = "rgba(0,0,0,0.08)"; + ctx.fillRect(0, 0, W, H); - drawUserBubble(ctx, { - x: 40 + userOffsetX.value, - y: H - 120, - avatarPath: avatarPath, - username: signatureName.value, - desc: "送上祝福", - }); + // 3️⃣ 标题 + ctx.fillStyle = "#ffffff"; + ctx.font = "42px sans-serif"; // 默认字体 + ctx.textAlign = "center"; + ctx.textBaseline = "alphabetic"; // Canvas 2D 默认是 alphabetic + ctx.fillText("新春快乐", W / 2, 120); - // 6️⃣ 输出 - const tempPath = await new Promise((resolve, reject) => { - ctx.draw(false, () => { - uni.canvasToTempFilePath({ - canvasId: "cardCanvas", - success: (res) => { - if (save) saveImage(res.tempFilePath); - resolve(res.tempFilePath); - }, - fail: (err) => reject(err), + ctx.font = "22px sans-serif"; + ctx.globalAlpha = 0.9; + ctx.fillText("2026 YEAR OF THE HORSE", W / 2, 165); + ctx.globalAlpha = 1; + + // 4️⃣ 祝福语气泡 + drawBubbleText(ctx, { + text: targetName.value + "\n " + blessingText.value, + x: 70, + y: 260 + bubbleOffsetY.value, + maxWidth: 400, + fontSize: 32, + lineHeight: 46, + backgroundColor: "rgba(255,255,255,0.85)", + textColor: selectedColor.value, + fontFamily: selectedFont.value.family, + }); + + drawUserBubble(ctx, { + x: 160 + userOffsetX.value, + y: H - 120, + avatarImg: avatarImg, // 传入 Image 对象 + username: signatureName.value, + desc: "送上祝福", + }); + + // 6️⃣ 输出 + uni.canvasToTempFilePath({ + canvas: canvas, // Canvas 2D 必须传 canvas 实例 + width: W, + height: H, + destWidth: W, + destHeight: H, + success: (res) => { + if (save) saveImage(res.tempFilePath); + resolve(res.tempFilePath); + }, + fail: (err) => reject(err), + }); + } catch (error) { + console.error("Canvas draw error:", error); + reject(error); + } }); - }); }); - - return tempPath; }; const loadImage = (url) => { + // 此函数保留给其他可能用到的地方,但 saveByCanvas 内部使用 loadCanvasImage return new Promise((resolve, reject) => { uni.getImageInfo({ src: url, @@ -607,12 +653,11 @@ function drawBubbleText(ctx, options) { fontSize = 32, fontFamily = "PingFang SC", } = options; - + console.log(111111, fontFamily); if (!text) return; - ctx.setFontSize(fontSize); - ctx.setFillStyle(textColor); - ctx.font = `${fontSize}px ${fontFamily}`; + ctx.fillStyle = textColor; + ctx.font = `${fontSize}px '${fontFamily}'`; ctx.textAlign = "left"; ctx.textBaseline = "top"; @@ -654,7 +699,7 @@ function drawBubbleText(ctx, options) { // ) // 4️⃣ 绘制文字 - ctx.setFillStyle(textColor); + ctx.fillStyle = textColor; lines.forEach((line, index) => { ctx.fillText(line, x + padding, y + padding + index * lineHeight); @@ -665,7 +710,7 @@ function drawUserBubble(ctx, options) { const { x = 40, // 气泡起点 x y = 860, // 气泡起点 y - avatarPath, // 头像本地路径 + avatarImg, // CanvasImage 对象 username = "zzc", desc = "送上祝福", avatarSize = 64, // 头像直径 @@ -678,11 +723,11 @@ function drawUserBubble(ctx, options) { // 设置字体 ctx.textBaseline = "top"; - ctx.font = `${fontSizeName}px PingFang SC`; + ctx.font = `${fontSizeName}px 'PingFang SC'`; // 测量文字宽度 const nameWidth = ctx.measureText(username).width; - ctx.font = `${fontSizeDesc}px PingFang SC`; + ctx.font = `${fontSizeDesc}px 'PingFang SC'`; const descWidth = ctx.measureText(desc).width; // 计算气泡宽度和高度 @@ -716,19 +761,21 @@ function drawUserBubble(ctx, options) { Math.PI * 2, ); ctx.clip(); - ctx.drawImage(avatarPath, avatarX, avatarY, avatarSize, avatarSize); + if (avatarImg) { + ctx.drawImage(avatarImg, avatarX, avatarY, avatarSize, avatarSize); + } ctx.restore(); // 3️⃣ 绘制文字 const textX = avatarX + avatarSize + padding; const textY = y + padding; - ctx.setFillStyle(textColor); - ctx.font = `${fontSizeName}px PingFang SC`; + ctx.fillStyle = textColor; + ctx.font = `${fontSizeName}px 'PingFang SC'`; ctx.fillText(username, textX, textY); - ctx.font = `${fontSizeDesc}px PingFang SC`; - ctx.setGlobalAlpha(0.6); + ctx.font = `${fontSizeDesc}px 'PingFang SC'`; + ctx.globalAlpha = 0.6; ctx.fillText(desc, textX, textY + fontSizeName + 4); - ctx.setGlobalAlpha(1); + ctx.globalAlpha = 1; } function drawRoundRect(ctx, x, y, w, h, r, color) {