文本转图片 (text-to-image)

/**
* 文本转图片
* @param {Object} options - 配置项
* @param {string} text - 文本
* @param {number} [width=200] - 图片宽度
* @param {number} [height=200] - 图片高度
* @param {string} [backgroundColor='transparent'] - 背景色
* @param {string} [fontFamily='Arial'] - 字体
* @param {number} [fontSize=16] - 字体大小
* @param {string} [fontColor='black'] - 字体颜色
* @param {number} [lineHeight=1.2] - 行高
* @param {boolean} [horizontalCenter=false] - 水平居中
* @param {boolean} [verticalCenter=false] - 垂直居中
* @param {number} [padding=10] - 边距
* @returns {string} base64
*/
function textToImage(options) {
const {
text,
width = 200,
height = 200,
backgroundColor = 'transparent',
fontFamily = 'Arial',
fontSize = 16,
fontColor = 'black',
lineHeight = 1.2,
horizontalCenter = false,
verticalCenter = false,
padding = 10,
} = options;
const dpr = devicePixelRatio || 1;
// const canvas = new OffscreenCanvas(width, height)
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
ctx.scale(dpr, dpr);
// 设置背景色
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, width, height);
// 设置字体
ctx.font = `${fontSize}px ${fontFamily}`;
ctx.fillStyle = fontColor;
const words = text.split(' ');
let line = '';
const lines = [];
// 计算每行的最大宽度
const maxWidth = width - padding * 2;
for (const word of words) {
const testLine = line + word + ' ';
const metrics = ctx.measureText(testLine);
// 如果超出最大宽度,则换行
if (metrics.width > maxWidth) {
lines.push(line);
line = word + ' ';
} else {
line = testLine;
}
}
lines.push(line);
// 计算文本位置
const drawableWidth = width - padding * 2;
const drawableHeight = height - padding * 2;
// const totalTextHeight = lines.length * fontSize * lineHeight;
// 限制文本行数
const maxLines = Math.floor(drawableHeight / (fontSize * lineHeight));
const displayLines = lines.slice(0, maxLines);
let startX = horizontalCenter ? width / 2 : padding;
let startY = verticalCenter
? (height - displayLines.length * fontSize * lineHeight) / 2 + (fontSize * lineHeight) / 2
: padding;
// 设置文本对齐方式
ctx.textAlign = horizontalCenter ? 'center' : 'left';
ctx.textBaseline = verticalCenter ? 'middle' : 'top';
// 绘制文本
displayLines.forEach((line, index) => {
let truncatedLine = line.trim();
while (ctx.measureText(truncatedLine).width > drawableWidth && truncatedLine.length > 0) {
truncatedLine = truncatedLine.slice(0, -1);
}
ctx.fillText(truncatedLine, startX, startY + index * fontSize * lineHeight);
});
queueMicrotask(() => {
canvas.remove();
});
return canvas.toDataURL('image/png');
// const blob = await canvas.convertToBlob();
// return URL.createObjectURL(blob);
}
  • 使用
const base64 = textToImage({
text: 'Hello, World! This is a best d
width: 200,
height: 200,
backgroundColor: 'pink',
fontSize: 24,
lineHeight: 1.25,
horizontalCenter: false,
verticalCenter: false,
padding: 10,
});
let img = new Image();
img.onload = () => {
document.body.appendChild(img);
};
img.src = base64;

  • 方式二
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Text to Image</title>
<style>
img {
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
function textToImage({
text,
font = '14px Arial', // 默认字体
width = 200, // 默认画布宽度
height = 200, // 默认画布高度
backgroundColor = '#FFFFFF', // 默认背景色
textColor = '#000000', // 默认文字颜色
padding = 10, // 默认内边距
align = 'left', // 默认水平居中
valign = 'top', // 默认垂直居中
}) {
/** @type {HTMLCanvasElement} */
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const maxWidth = width - 2 * padding;
const fontSize = parseInt(font.match(/\d+/)[0] ?? 14); // 字体大小
const dpr = window.devicePixelRatio || 1; // 默认设备像素比
// 根据 DPR 设置实际画布大小
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr); // 缩放画布
// 设置背景色
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, width, height);
// 设置字体
ctx.font = font;
ctx.fillStyle = textColor;
ctx.textBaseline = 'top'; // 设置文字基线
// 计算最大宽度
let lineWidth = maxWidth || width - 2 * padding;
// 创建缓存 Map 存储非中文的单词宽度
const wordCache = new Map();
// 自动换行文本
const lines = wrapText(ctx, text, lineWidth, wordCache);
// 计算文本总高度
let textHeight = lines.length * fontSize + (lines.length - 1) * 5; // 字体高度 + 行间距
if (textHeight > height - 2 * padding) {
// 如果文本高度超出,截断文本
lines.length = Math.floor((height - 2 * padding) / (fontSize + 5));
textHeight = lines.length * fontSize + (lines.length - 1) * 5;
}
// 计算文字的起始位置,支持水平和垂直居中
const x = align === 'center' ? (width - lineWidth) / 2 : padding;
const y = valign === 'middle' ? (height - textHeight) / 2 : padding;
// 绘制文本
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i], x, y + i * (fontSize + 5));
}
}
function wrapText(ctx, text, maxWidth, wordCache) {
let lines = [];
let currentLine = '';
let words = text.split(' ');
// 遍历每个单词
for (let i = 0; i < words.length; i++) {
const word = words[i];
// 判断当前单词是否为中文
const isChinese = /[\u4e00-\u9fa5]/.test(word);
// 获取单词的宽度,优先从缓存获取
let wordWidth;
if (isChinese) {
// 中文字符逐个测量
wordWidth = ctx.measureText(word).width;
} else {
// 非中文字符从缓存获取
if (wordCache.has(word)) {
wordWidth = wordCache.get(word);
} else {
wordWidth = ctx.measureText(word).width;
wordCache.set(word, wordWidth); // 缓存非中文单词的宽度
}
}
// 检查当前行加上该单词后是否超出最大宽度
if (ctx.measureText(currentLine).width + wordWidth > maxWidth && currentLine !== '') {
lines.push(currentLine); // 当前行宽度超出,换行
currentLine = word; // 新的一行从当前单词开始
} else {
currentLine += (currentLine ? ' ' : '') + word; // 当前行继续添加该单词
}
}
// 最后一行文本
if (currentLine) {
lines.push(currentLine);
}
return lines;
}
// 示例调用
textToImage({
text: 'This is a long text example that will wrap when necessary. 中文的文本也可以处理。 This is a long text example that will wrap when necessary. 中文的文本也可以处理。 This is a long text example that will wrap when necessary. 中文的文本也可以处理。 This is a long text example that will wrap when necessary. 中文的文本也可以处理。',
font: '14px Arial',
width: 100,
height: 100,
backgroundColor: '#e0e0e0',
textColor: '#333',
padding: 10,
// align: 'left',
// valign: 'top',
});
</script>
</body>
</html>
posted @   _clai  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示