From 6bc0a2b85f565562636bb40f30bf5dda10cf9b3d Mon Sep 17 00:00:00 2001 From: zzc <1761997216@qq.com> Date: Sun, 1 Feb 2026 16:49:23 +0800 Subject: [PATCH] fix: font --- pages/make/index.vue | 135 +++++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 64 deletions(-) diff --git a/pages/make/index.vue b/pages/make/index.vue index 09ef58f..e9d23c7 100644 --- a/pages/make/index.vue +++ b/pages/make/index.vue @@ -863,52 +863,67 @@ const saveByCanvas = async (save = true) => { }); }; - try { - // 1️⃣ 画背景 - // ⭐ 先加载背景图 - const [bgImg, avatarImg, titleImg] = await Promise.all([ - loadCanvasImage(currentTemplate?.value?.imageUrl), - loadCanvasImage(userAvatar.value), - currentTitle.value ? loadCanvasImage(currentTitle.value.imageUrl) : Promise.resolve(null), - ]); + // 辅助函数:rpx 转 px (基于预览容器宽度 506rpx 对应 Canvas 540px) + const r2p = (rpx) => (rpx * 540) / 506; - ctx.drawImage(bgImg, 0, 0, W, H); + try { + // 1️⃣ 画背景 + // ⭐ 先加载背景图 + const [bgImg, avatarImg, titleImg] = await Promise.all([ + loadCanvasImage(currentTemplate?.value?.imageUrl), + loadCanvasImage(userAvatar.value), + currentTitle.value ? loadCanvasImage(currentTitle.value.imageUrl) : Promise.resolve(null), + ]); - // 2️⃣ 半透明遮罩(和你 UI 一致) - ctx.fillStyle = "rgba(0,0,0,0.08)"; - ctx.fillRect(0, 0, W, H); + ctx.drawImage(bgImg, 0, 0, W, H); - // 3️⃣ 标题图片 - if (titleImg) { - const titleW = titleImg.width * titleScale.value; - const titleH = titleImg.height * titleScale.value; - const titleX = (W - titleW) / 2 + titleOffsetX.value; - const titleY = 40 + titleOffsetY.value; - ctx.drawImage(titleImg, titleX, titleY, titleW, titleH); - } + // 2️⃣ 半透明遮罩 + ctx.fillStyle = "rgba(0,0,0,0.08)"; + ctx.fillRect(0, 0, W, H); - // 4️⃣ 祝福语气泡 - drawBubbleText(ctx, { - text: targetName.value + "\n " + blessingText.value.content, - x: 70, - y: 260 + bubbleOffsetY.value, - maxWidth: bubbleMaxWidth.value, // 使用动态宽度 - canvasWidth: W, // 传入画布宽度以实现自动居中 - fontSize: fontSize.value, - lineHeight: fontSize.value * 1.5, - backgroundColor: "rgba(255,255,255,0.85)", - textColor: selectedColor.value, - fontFamily: selectedFont.value.family, - }); + // 3️⃣ 标题图片 + if (titleImg) { + const previewBaseWidth = 400; // rpx + const drawWidth = r2p(previewBaseWidth) * titleScale.value; + const drawHeight = (titleImg.height / titleImg.width) * drawWidth; + // 预览中是 translate(offsetX, offsetY),top: 40rpx + const titleX = (W - drawWidth) / 2 + r2p(titleOffsetX.value); + const titleY = r2p(40 + titleOffsetY.value); + ctx.drawImage(titleImg, titleX, titleY, drawWidth, drawHeight); + } - drawUserBubble(ctx, { - x: 160 + userOffsetX.value, - y: H - 136 + userOffsetY.value, - avatarImg: avatarImg, // 传入 Image 对象 - username: signatureName.value, - desc: "送上祝福", - textColor: signatureColor.value, - }); + // 4️⃣ 祝福语气泡 + // 预览中 .bubble 有 padding: 40rpx,且 .card-overlay 有 padding: 30rpx + // 意味着文字距离容器边缘至少有 70rpx + drawBubbleText(ctx, { + text: targetName.value + "\n " + blessingText.value.content, + x: 0, + y: r2p(230 + bubbleOffsetY.value), + maxWidth: r2p(bubbleMaxWidth.value), // 预览中 bubble-text 的宽度 + canvasWidth: W, + fontSize: r2p(fontSize.value), + lineHeight: r2p(fontSize.value * 1.6), // 预览中是 1.6 + padding: r2p(40 + 30), // 内部 padding 40 + 容器 padding 30 + backgroundColor: "transparent", + textColor: selectedColor.value, + fontFamily: selectedFont.value.family, + }); + + // 5️⃣ 用户信息 + // 预览中 user 是 absolute, left: 160 + offsetX, bottom: 40 - offsetY + drawUserBubble(ctx, { + x: r2p(160 + userOffsetX.value), + bottom: r2p(40 - userOffsetY.value), + canvasHeight: H, + avatarImg: avatarImg, + username: signatureName.value, + desc: "送上祝福", + textColor: signatureColor.value, + avatarSize: r2p(64), + padding: r2p(15), + fontSizeName: r2p(24), + fontSizeDesc: r2p(20), + }); // 6️⃣ 输出 uni.canvasToTempFilePath({ @@ -1012,40 +1027,26 @@ function drawBubbleText(ctx, options) { }); // 如果提供了 canvasWidth,则自动计算居中的 x 坐标 + // 预览中文字是左对齐,但整个气泡容器是水平居中的 let drawX = x; if (canvasWidth) { - const totalWidth = maxLineWidth + padding * 2; - drawX = (canvasWidth - totalWidth) / 2; + drawX = (canvasWidth - maxLineWidth) / 2; } - // 2️⃣ 计算气泡尺寸 - // const bubbleWidth = maxWidth - - // const bubbleHeight = lines.length * lineHeight + padding * 2 - - // 3️⃣ 绘制气泡(圆角矩形) - // drawRoundRect( - // ctx, - // drawX, - // y, - // bubbleWidth, - // bubbleHeight, - // radius, - // backgroundColor - // ) - // 4️⃣ 绘制文字 ctx.fillStyle = textColor; lines.forEach((line, index) => { - ctx.fillText(line, drawX + padding, y + padding + index * lineHeight); + ctx.fillText(line, drawX, y + padding + index * lineHeight); }); } function drawUserBubble(ctx, options) { const { x = 40, // 气泡起点 x - y = 860, // 气泡起点 y + y, // 气泡起点 y (如果传了 bottom 则优先计算) + bottom, // 距离底部的距离 + canvasHeight, avatarImg, // CanvasImage 对象 username = "zzc", desc = "送上祝福", @@ -1072,11 +1073,17 @@ function drawUserBubble(ctx, options) { Math.max(avatarSize, fontSizeName + fontSizeDesc + 4) + padding * 2; const bubbleWidth = avatarSize + padding + textWidth + padding * 2; + // 计算 y + let drawY = y; + if (typeof bottom !== "undefined" && canvasHeight) { + drawY = canvasHeight - bottom - bubbleHeight; + } + // 1️⃣ 绘制气泡(左右半圆) drawRoundRect( ctx, x, - y, + drawY, bubbleWidth, bubbleHeight, bubbleHeight / 2, @@ -1085,7 +1092,7 @@ function drawUserBubble(ctx, options) { // 2️⃣ 绘制头像 const avatarX = x + padding; - const avatarY = y + (bubbleHeight - avatarSize) / 2; + const avatarY = drawY + (bubbleHeight - avatarSize) / 2; ctx.save(); ctx.beginPath(); @@ -1104,7 +1111,7 @@ function drawUserBubble(ctx, options) { // 3️⃣ 绘制文字 const textX = avatarX + avatarSize + padding; - const textY = y + padding; + const textY = drawY + padding; ctx.fillStyle = textColor; ctx.font = `${fontSizeName}px 'PingFang SC'`; ctx.fillText(username, textX, textY);