fix: card content font

This commit is contained in:
zzc
2026-01-22 11:04:47 +08:00
parent bdaf3a3be1
commit 0181066b34

View File

@@ -228,7 +228,8 @@
</view>
<canvas
canvas-id="cardCanvas"
type="2d"
id="cardCanvas"
class="hidden-canvas"
style="width: 540px; height: 960px"
/>
@@ -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) {