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