fix: card content font
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user