forked from natuka/web.puabadge.com
586 lines
21 KiB
TypeScript
586 lines
21 KiB
TypeScript
// textHelper.ts
|
||
/**
|
||
* 文字定位助手模块
|
||
* @module TextPosition
|
||
*/
|
||
|
||
/**
|
||
* 创建文字定位助手实例
|
||
* @param {Object} options - 配置选项
|
||
* @param {Function} options.setData - 设置数据的方法(用于更新响应式数据)
|
||
* @returns {Object} 包含renderText方法的对象
|
||
*/
|
||
export function createTextPosition({ setData }) {
|
||
if (!setData) {
|
||
throw new Error('setData is required');
|
||
}
|
||
|
||
// 默认样式配置
|
||
const defaultStyle = {
|
||
fontSize: 16, // 字体大小(px)
|
||
fontFamily: 'Arial', // 字体
|
||
color: '#000000', // 文字颜色
|
||
textAlign: 'left', // 文本对齐方式: left, center, right
|
||
fontWeight: 'normal', // 字体粗细: normal, bold
|
||
lineHeight: 1.5, // 行高
|
||
opacity: 1, // 透明度
|
||
backgroundColor: 'transparent', // 背景颜色
|
||
padding: 0, // 内边距(px)
|
||
borderRadius: 0, // 边框圆角(px)
|
||
};
|
||
|
||
/**
|
||
* 渲染文字到指定位置
|
||
* @param {Object} options - 文字配置
|
||
* @param {string} options.text - 要显示的文字
|
||
* @param {number} options.x - 文字左上角X坐标(基于原始容器尺寸)
|
||
* @param {number} options.y - 文字左上角Y坐标(基于原始容器尺寸)
|
||
* @param {number} [options.width] - 文字容器宽度(基于原始容器尺寸)
|
||
* @param {number} [options.height] - 文字容器高度(基于原始容器尺寸)
|
||
* @param {Object} [options.style] - 文字样式配置
|
||
* @param {string} [options.bindKey] - 绑定到页面数据的键名
|
||
*/
|
||
|
||
function roundText({ shapeImage, x, y, width, height, radius, style = {}, bindKey = 'roundTextConfig', text = '', isFront = true, cachedImageInfo = null, onImageInfoCached = null }) {
|
||
// 合并默认样式和用户自定义样式
|
||
const mergedStyle = { ...defaultStyle, ...style };
|
||
|
||
// 获取设备信息,计算缩放比例
|
||
const screenWidth = 320;
|
||
|
||
// 计算宽度和高度的缩放比例
|
||
const scale = 0.6;
|
||
|
||
// 计算字符位置的函数
|
||
const calculateTextPosition = (imageInfo) => {
|
||
// 计算图片宽高比
|
||
const imageRatio = imageInfo.width / imageInfo.height;
|
||
const containerRatio = 1; // 容器是正方形,宽高比为1
|
||
|
||
let ratio;
|
||
|
||
// 根据图片和容器的比例关系决定如何缩放
|
||
if (imageRatio > containerRatio) {
|
||
// 图片更宽,以宽度为基准缩放
|
||
ratio = screenWidth / imageInfo.width;
|
||
} else {
|
||
// 图片更高或等比,以高度为基准缩放
|
||
ratio = screenWidth / imageInfo.height;
|
||
}
|
||
|
||
// 计算缩放后的参数
|
||
const scaledX = x * ratio * scale;
|
||
const scaledY = y * ratio * scale;
|
||
const scaledWidth = width * ratio * scale;
|
||
const scaledHeight = height * ratio * scale;
|
||
const scaledRadius = radius * ratio * scale;
|
||
const scaledFontSize = mergedStyle.fontSize * ratio * scale;
|
||
|
||
// 计算圆心位置(基于容器的中心点)
|
||
const centerX = scaledX + scaledWidth / 2;
|
||
const centerY = scaledY + scaledHeight / 2;
|
||
|
||
// 计算每个字符的位置信息
|
||
// 根据正面/背面使用不同的计算方法,互不影响
|
||
const charList = isFront
|
||
? calculateFrontRoundTextChars({
|
||
text: text,
|
||
centerX: scaledWidth / 2, // 相对于容器的中心
|
||
centerY: scaledHeight / 2,
|
||
radius: scaledRadius,
|
||
fontSize: scaledFontSize,
|
||
fontWeight: mergedStyle.fontWeight || 'normal',
|
||
color: mergedStyle.color || '#000000'
|
||
})
|
||
: calculateBackRoundTextChars({
|
||
text: text,
|
||
centerX: scaledWidth / 2, // 相对于容器的中心
|
||
centerY: scaledHeight / 2,
|
||
radius: scaledRadius,
|
||
fontSize: scaledFontSize,
|
||
fontWeight: mergedStyle.fontWeight || 'normal',
|
||
color: mergedStyle.color || '#000000'
|
||
});
|
||
|
||
// 构建圆形文字配置对象
|
||
const roundTextConfig = {
|
||
x: scaledX,
|
||
y: scaledY,
|
||
width: scaledWidth,
|
||
height: scaledHeight,
|
||
centerX: centerX,
|
||
centerY: centerY,
|
||
radius: scaledRadius,
|
||
text: text,
|
||
charList: charList, // 字符位置列表
|
||
style: {
|
||
...mergedStyle,
|
||
fontSize: scaledFontSize,
|
||
}
|
||
};
|
||
|
||
console.log('roundTextConfig===>', roundTextConfig);
|
||
|
||
// 更新页面数据
|
||
setData({
|
||
[bindKey]: roundTextConfig
|
||
});
|
||
|
||
// 返回配置对象,以便后续使用
|
||
return roundTextConfig;
|
||
};
|
||
|
||
// 如果已有缓存的图片信息,直接使用
|
||
if (cachedImageInfo && cachedImageInfo.src === shapeImage) {
|
||
calculateTextPosition(cachedImageInfo);
|
||
return;
|
||
}
|
||
|
||
// 否则获取图片信息
|
||
const img = new Image();
|
||
img.onload = () => {
|
||
const res = {
|
||
width: img.width,
|
||
height: img.height,
|
||
path: shapeImage,
|
||
src: shapeImage
|
||
};
|
||
|
||
// 如果提供了缓存回调,调用它
|
||
if (typeof onImageInfoCached === 'function') {
|
||
onImageInfoCached(res);
|
||
}
|
||
|
||
calculateTextPosition(res);
|
||
};
|
||
img.onerror = (err) => {
|
||
console.error('Failed to get image info:', err);
|
||
};
|
||
img.src = shapeImage;
|
||
}
|
||
|
||
/**
|
||
* 计算正面圆形文字中每个字符的位置和样式
|
||
* @param {Object} options - 配置选项
|
||
* @param {string} options.text - 文字内容
|
||
* @param {number} options.centerX - 圆心X坐标(相对于容器)
|
||
* @param {number} options.centerY - 圆心Y坐标(相对于容器)
|
||
* @param {number} options.radius - 半径
|
||
* @param {number} options.fontSize - 字体大小
|
||
* @param {string} options.fontWeight - 字体粗细
|
||
* @param {string} options.color - 文字颜色
|
||
* @returns {Array} 字符位置信息数组
|
||
*/
|
||
function calculateFrontRoundTextChars({ text, centerX, centerY, radius, fontSize, fontWeight, color }) {
|
||
if (!text) {
|
||
return [];
|
||
}
|
||
|
||
// 计算字符宽度
|
||
// 使用更精确的估算方法
|
||
const charWidths = [];
|
||
let totalWidth = 0;
|
||
|
||
for (let i = 0; i < text.length; i++) {
|
||
const char = text[i];
|
||
let charWidth;
|
||
|
||
// 判断字符类型,使用不同的宽度估算
|
||
if (/[\u4e00-\u9fa5]/.test(char)) {
|
||
// 中文字符:宽度约为 fontSize
|
||
charWidth = fontSize;
|
||
} else if (/[0-9]/.test(char)) {
|
||
// 数字:宽度约为 fontSize * 0.6
|
||
charWidth = fontSize * 0.6;
|
||
} else if (/[a-zA-Z]/.test(char)) {
|
||
// 英文字母:宽度约为 fontSize * 0.6(大写)或 0.5(小写)
|
||
charWidth = /[A-Z]/.test(char) ? fontSize * 0.6 : fontSize * 0.45;
|
||
} else if (/[\u3000-\u303f\uff00-\uffef]/.test(char)) {
|
||
// 全角符号:宽度约为 fontSize
|
||
charWidth = fontSize;
|
||
} else {
|
||
// 其他字符(半角符号等):宽度约为 fontSize * 0.4
|
||
charWidth = fontSize * 0.4;
|
||
}
|
||
|
||
// 如果字体是粗体,稍微增加宽度
|
||
if (fontWeight === 'bold' || fontWeight === '700') {
|
||
charWidth *= 1.1;
|
||
}
|
||
|
||
charWidths.push(charWidth);
|
||
totalWidth += charWidth;
|
||
}
|
||
|
||
// 为文字之间添加2px间距(除了最后一个字符)
|
||
const spacing = 0;
|
||
const totalSpacing = (text.length > 1) ? (text.length - 1) * spacing : 0;
|
||
const totalWidthWithSpacing = totalWidth + totalSpacing;
|
||
|
||
// 计算文字总弧长和每个字符的角度(正面使用原始半径)
|
||
const circumference = 2 * Math.PI * radius;
|
||
const arcLength = totalWidthWithSpacing;
|
||
const totalAngle = (arcLength / circumference) * 2 * Math.PI;
|
||
|
||
// 计算起始角度(使文字居中于底部)
|
||
// 从底部中心开始,顺时针排列,起始角度 = 底部中心 + 总角度的一半
|
||
const startAngle = Math.PI / 2 + totalAngle / 2;
|
||
|
||
// 创建并定位每个字符(逆时针排列,所以角度递减)
|
||
let currentAngle = startAngle;
|
||
const charList = [];
|
||
|
||
for (let i = 0; i < text.length; i++) {
|
||
const char = text[i];
|
||
const charWidth = charWidths[i];
|
||
|
||
// 计算字符角度增量(包括字符宽度和间距)
|
||
// 最后一个字符不需要添加后面的间距
|
||
const spacingAfter = (i < text.length - 1) ? spacing : 0;
|
||
const charAngleIncrement = ((charWidth + spacingAfter) / circumference) * 2 * Math.PI;
|
||
|
||
// 计算字符角度位置(逆时针排列,所以角度递减)
|
||
// 字符中心位置 = 当前角度 - 字符宽度的一半对应的角度
|
||
const charWidthAngle = (charWidth / circumference) * 2 * Math.PI;
|
||
const charAngle = currentAngle - charWidthAngle / 2;
|
||
|
||
// 计算字符位置(正面使用原始半径)
|
||
const x = centerX + radius * Math.cos(charAngle);
|
||
const y = centerY + radius * Math.sin(charAngle) - fontSize / 3;
|
||
|
||
// 计算旋转角度(正面:文字底部向外,顶部朝向圆心)
|
||
const rotateAngle = charAngle + Math.PI * 1.5;
|
||
// 转换为度数(用于CSS transform)
|
||
const rotateDeg = (rotateAngle * 180) / Math.PI;
|
||
|
||
charList.push({
|
||
char: char,
|
||
x: x,
|
||
y: y,
|
||
rotate: rotateAngle,
|
||
rotateDeg: rotateDeg, // 度数版本
|
||
fontSize: fontSize,
|
||
fontWeight: fontWeight,
|
||
color: color
|
||
});
|
||
|
||
// 更新当前角度(逆时针排列,所以角度递减)
|
||
currentAngle -= charAngleIncrement;
|
||
}
|
||
|
||
return charList;
|
||
}
|
||
|
||
/**
|
||
* 计算背面圆形文字中每个字符的位置和样式
|
||
* @param {Object} options - 配置选项
|
||
* @param {string} options.text - 文字内容
|
||
* @param {number} options.centerX - 圆心X坐标(相对于容器)
|
||
* @param {number} options.centerY - 圆心Y坐标(相对于容器)
|
||
* @param {number} options.radius - 半径
|
||
* @param {number} options.fontSize - 字体大小
|
||
* @param {string} options.fontWeight - 字体粗细
|
||
* @param {string} options.color - 文字颜色
|
||
* @returns {Array} 字符位置信息数组
|
||
*/
|
||
function calculateBackRoundTextChars({ text, centerX, centerY, radius, fontSize, fontWeight, color }) {
|
||
if (!text) {
|
||
return [];
|
||
}
|
||
|
||
// 计算字符宽度
|
||
// 使用更精确的估算方法
|
||
const charWidths = [];
|
||
let totalWidth = 0;
|
||
|
||
for (let i = 0; i < text.length; i++) {
|
||
const char = text[i];
|
||
let charWidth;
|
||
|
||
// 判断字符类型,使用不同的宽度估算
|
||
if (/[\u4e00-\u9fa5]/.test(char)) {
|
||
// 中文字符:宽度约为 fontSize
|
||
charWidth = fontSize;
|
||
} else if (/[0-9]/.test(char)) {
|
||
// 数字:宽度约为 fontSize * 0.6
|
||
charWidth = fontSize * 0.6;
|
||
} else if (/[a-zA-Z]/.test(char)) {
|
||
// 英文字母:宽度约为 fontSize * 0.6(大写)或 0.5(小写)
|
||
charWidth = /[A-Z]/.test(char) ? fontSize * 0.65 : fontSize * 0.5;
|
||
} else if (/[\u3000-\u303f\uff00-\uffef]/.test(char)) {
|
||
// 全角符号:宽度约为 fontSize
|
||
charWidth = fontSize;
|
||
} else {
|
||
// 其他字符(半角符号等):宽度约为 fontSize * 0.4
|
||
charWidth = fontSize * 0.4;
|
||
}
|
||
|
||
// 如果字体是粗体,稍微增加宽度
|
||
if (fontWeight === 'bold' || fontWeight === '700') {
|
||
charWidth *= 1.1;
|
||
}
|
||
|
||
charWidths.push(charWidth);
|
||
totalWidth += charWidth;
|
||
}
|
||
|
||
// 为文字之间添加2px间距(除了最后一个字符)
|
||
const spacing = 1;
|
||
const totalSpacing = (text.length > 1) ? (text.length - 1) * spacing : 0;
|
||
const totalWidthWithSpacing = totalWidth + totalSpacing;
|
||
|
||
// 计算文字总弧长和每个字符的角度
|
||
// 背面:文字到圆心的距离 = 半径 + 文字高度的一半
|
||
const adjustedRadius = radius + fontSize / 2;
|
||
const circumference = 2 * Math.PI * adjustedRadius;
|
||
const arcLength = totalWidthWithSpacing;
|
||
const totalAngle = (arcLength / circumference) * 2 * Math.PI;
|
||
|
||
// 计算起始角度(使文字居中于底部)
|
||
// 从底部中心开始,顺时针排列,起始角度 = 底部中心 - 总角度的一半
|
||
const startAngle = Math.PI / 2 - totalAngle / 2;
|
||
|
||
// 创建并定位每个字符(顺时针排列,所以角度递增)
|
||
let currentAngle = startAngle;
|
||
const charList = [];
|
||
|
||
for (let i = 0; i < text.length; i++) {
|
||
const char = text[i];
|
||
const charWidth = charWidths[i];
|
||
|
||
// 计算字符角度增量(包括字符宽度和间距)
|
||
// 最后一个字符不需要添加后面的间距
|
||
const spacingAfter = (i < text.length - 1) ? spacing : 0;
|
||
const charAngleIncrement = ((charWidth + spacingAfter) / circumference) * 2 * Math.PI;
|
||
|
||
// 计算字符角度位置(顺时针排列,所以角度递增)
|
||
// 字符中心位置 = 当前角度 + 字符宽度的一半对应的角度
|
||
const charWidthAngle = (charWidth / circumference) * 2 * Math.PI;
|
||
const charAngle = currentAngle + charWidthAngle / 2;
|
||
|
||
// 计算字符位置(背面:文字到圆心的距离 = 半径 + 文字高度的一半)
|
||
const x = centerX + adjustedRadius * Math.cos(charAngle);
|
||
const y = centerY + adjustedRadius * Math.sin(charAngle);
|
||
|
||
// 计算旋转角度(背面:文字底部朝向圆心,顶部向外)
|
||
const rotateAngle = charAngle + Math.PI * 0.5;
|
||
// 转换为度数(用于CSS transform)
|
||
const rotateDeg = (rotateAngle * 180) / Math.PI;
|
||
|
||
charList.push({
|
||
char: char,
|
||
x: x,
|
||
y: y,
|
||
rotate: rotateAngle,
|
||
rotateDeg: rotateDeg, // 度数版本
|
||
fontSize: fontSize,
|
||
fontWeight: fontWeight,
|
||
color: color
|
||
});
|
||
|
||
// 更新当前角度(顺时针排列,所以角度递增)
|
||
currentAngle += charAngleIncrement;
|
||
}
|
||
|
||
return charList;
|
||
}
|
||
|
||
/**
|
||
* 初始化圆形文字 Canvas
|
||
* @param {string} canvasId - Canvas 的 ID 或选择器
|
||
* @param {Object} config - 配置对象,包含 width 和 height
|
||
* @returns {Promise} 返回包含 canvas 和 ctx 的对象
|
||
*/
|
||
function initRoundTextCanvas(canvasId, config) {
|
||
return new Promise((resolve) => {
|
||
const tryInit = () => {
|
||
// 如果是选择器字符串,去掉 # 或 . 前缀
|
||
const selector = canvasId.startsWith('#') ? canvasId.slice(1) :
|
||
canvasId.startsWith('.') ? canvasId.slice(1) : canvasId;
|
||
|
||
// 尝试通过 ID 查找
|
||
let canvasElement: HTMLCanvasElement | null = document.getElementById(selector) as HTMLCanvasElement | null;
|
||
|
||
// 如果找不到,尝试通过类名查找(取第一个)
|
||
if (!canvasElement && canvasId.startsWith('.')) {
|
||
const elements = document.getElementsByClassName(selector);
|
||
if (elements.length > 0) {
|
||
canvasElement = elements[0] as HTMLCanvasElement;
|
||
}
|
||
}
|
||
|
||
// 如果还是找不到,尝试直接使用 canvasId 作为选择器
|
||
if (!canvasElement) {
|
||
canvasElement = document.querySelector(canvasId) as HTMLCanvasElement | null;
|
||
}
|
||
|
||
if (canvasElement && canvasElement instanceof HTMLCanvasElement) {
|
||
const canvas = canvasElement;
|
||
const ctx = canvas.getContext('2d');
|
||
if (!ctx) {
|
||
throw new Error('无法获取 Canvas 2D 上下文');
|
||
}
|
||
const dpr = window.devicePixelRatio || 1;
|
||
|
||
// 使用配置中的尺寸,如果没有则使用 Canvas 的实际尺寸或默认值
|
||
const width = (config && config.width) || canvas.offsetWidth || 700;
|
||
const height = (config && config.height) || canvas.offsetHeight || 700;
|
||
|
||
canvas.width = width * dpr;
|
||
canvas.height = height * dpr;
|
||
ctx.scale(dpr, dpr);
|
||
|
||
resolve({ canvas, ctx });
|
||
} else {
|
||
// 如果 Canvas 还未渲染,延迟重试
|
||
setTimeout(() => {
|
||
tryInit();
|
||
}, 100);
|
||
}
|
||
};
|
||
tryInit();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 在 Canvas 上绘制圆形文字
|
||
* @param {Object} options - 绘制选项
|
||
* @param {Object} options.ctx - Canvas 2D 上下文
|
||
* @param {Object} options.config - 圆形文字配置对象
|
||
* @param {Function} options.setData - 设置数据的方法
|
||
* @param {HTMLCanvasElement} options.canvas - Canvas 元素
|
||
* @param {string} options.imageKey - 图片数据键名,用于存储生成的图片路径
|
||
* @returns {Promise} 返回生成的图片路径(Data URL)
|
||
*/
|
||
function drawRoundTextOnCanvas({ ctx, config, setData, canvas, imageKey = 'roundTextImage' }) {
|
||
return new Promise((resolve, reject) => {
|
||
if (!ctx || !config) {
|
||
console.log('drawRoundTextOnCanvas: ctx 或 config 不存在', { ctx: !!ctx, config: !!config });
|
||
if (setData) {
|
||
setData({
|
||
[imageKey]: ''
|
||
});
|
||
}
|
||
resolve('');
|
||
return;
|
||
}
|
||
|
||
const text = config.text;
|
||
if (!text) {
|
||
console.log('drawRoundTextOnCanvas: 文字为空');
|
||
// 如果没有文字,清空图片
|
||
if (setData) {
|
||
setData({
|
||
[imageKey]: ''
|
||
});
|
||
}
|
||
resolve('');
|
||
return;
|
||
}
|
||
|
||
console.log('drawRoundTextOnCanvas: 开始绘制文字', { text, config });
|
||
|
||
// 使用配置中的宽高
|
||
const canvasWidth = config.width || 700;
|
||
const canvasHeight = config.height || 700;
|
||
// 圆心位置:根据需求,圆心应该在容器的中心
|
||
// 由于容器就是 Canvas,所以圆心就是 Canvas 的中心
|
||
const centerX = canvasWidth / 2;
|
||
const centerY = canvasHeight / 2;
|
||
const radius = config.radius;
|
||
const fontSize = config.style.fontSize;
|
||
const color = config.style.color || '#000000';
|
||
const fontWeight = config.style.fontWeight || 'normal';
|
||
|
||
// 清空画布(注意:Canvas 的实际尺寸已经乘以了 dpr,但绘制时使用的是逻辑尺寸)
|
||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||
|
||
// 设置字体样式
|
||
ctx.font = `${fontWeight} ${fontSize}px sans-serif`;
|
||
ctx.fillStyle = color;
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle'; // 使用 middle 基线,文字中心对齐,更适合圆形排列
|
||
|
||
// 计算每个字符的角度
|
||
const circumference = 2 * Math.PI * radius;
|
||
const charWidths = [];
|
||
let totalWidth = 0;
|
||
|
||
// 测量每个字符的宽度
|
||
for (let i = 0; i < text.length; i++) {
|
||
const char = text[i];
|
||
const metrics = ctx.measureText(char);
|
||
const width = metrics.width;
|
||
charWidths.push(width);
|
||
totalWidth += width;
|
||
}
|
||
|
||
// 计算文字总弧长和每个字符的角度
|
||
const arcLength = totalWidth;
|
||
const totalAngle = (arcLength / circumference) * 2 * Math.PI;
|
||
|
||
// 计算起始角度(使文字居中于底部)
|
||
// 从底部中心开始,顺时针排列,起始角度 = 底部中心 + 总角度的一半
|
||
const startAngle = Math.PI / 2 + totalAngle / 2;
|
||
|
||
// 创建并定位每个字符(逆时针排列,所以角度递减)
|
||
let currentAngle = startAngle;
|
||
|
||
for (let i = 0; i < text.length; i++) {
|
||
const char = text[i];
|
||
const charWidth = charWidths[i];
|
||
|
||
// 计算字符角度增量
|
||
const charAngleIncrement = (charWidth / circumference) * 2 * Math.PI;
|
||
|
||
// 计算字符角度位置(逆时针排列,所以角度递减)
|
||
const charAngle = currentAngle - charAngleIncrement / 2;
|
||
|
||
// 计算字符位置
|
||
const x = centerX + radius * Math.cos(charAngle);
|
||
const y = centerY + radius * Math.sin(charAngle);
|
||
|
||
// 保存上下文
|
||
ctx.save();
|
||
|
||
// 移动到字符位置
|
||
ctx.translate(x, y);
|
||
|
||
// 旋转文字,使其沿着圆形路径排列
|
||
// 文字底部向外,顶部朝向圆心,所以旋转角度为字符角度 + π * 1.5
|
||
ctx.rotate(charAngle + Math.PI * 1.5);
|
||
|
||
// 绘制文字(使用 0, 0 作为中心点,因为已经 translate 到字符位置)
|
||
ctx.fillText(char, 0, 0);
|
||
|
||
// 恢复上下文
|
||
ctx.restore();
|
||
|
||
// 更新当前角度(逆时针排列,所以角度递减)
|
||
currentAngle -= charAngleIncrement;
|
||
}
|
||
|
||
// 将 Canvas 转换为图片(Data URL)
|
||
try {
|
||
const dataURL = canvas.toDataURL('image/png');
|
||
if (setData) {
|
||
setData({
|
||
[imageKey]: dataURL
|
||
});
|
||
}
|
||
resolve(dataURL);
|
||
} catch (err) {
|
||
console.error('Canvas 转图片失败:', err);
|
||
reject(err);
|
||
}
|
||
});
|
||
}
|
||
|
||
return {
|
||
roundText,
|
||
initRoundTextCanvas,
|
||
drawRoundTextOnCanvas,
|
||
calculateFrontRoundTextChars,
|
||
calculateBackRoundTextChars
|
||
};
|
||
} |