当前位置: 首页 > article >正文

canvas自定义文本排列方法 + 自定义花字应用案例

一、canvas自定义文本排列方法

其中还添加了自定义文字描边、文字阴影、文字下划线等逻辑功能。

1、竖排

注意点:竖排里的英文和数字的摆放方式是倒立的,跟中文的正立摆放是有区别的。

1.1 自动换行

需要根据传入的文字区域的宽高自动识别文本的长度进行换行

const ARR = [] // 存储每行的宽高
const WORDS = {} // 存储每个字的宽高
let LINEARR = [] // 片段数组

// console.error('自动换行')

_maxWidth = item.width * devicePixelRatio
_maxHeight = item.height * devicePixelRatio

let _w = 0
let _h = 0
let _p = ''

// 计算所有字的宽度和高度
for (let i = 0; i < item.content.length; i++) {

	// 换行符
	if (item.content[i] === '\n') {
		ARR.push([lineHeight || _w, _h])
		LINEARR.push(_p)

		_w = 0
		_h = 0

		_p = ''
		continue
	}

	// 计算文字宽高
	const metrics = context.measureText(item.content[i]);

	// 计算文本宽度
	// const textWidth = metrics.actualBoundingBoxRight + metrics.actualBoundingBoxLeft;
	const textWidth = metrics.width

	// 计算文本高度
	// 所有字在这个字体下的高度
	const textHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent
	// 当前文本字符串在这个字体下用的实际高度
	// const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; 



	if (!WORDS[item.content[i]]) {
		WORDS[item.content[i]] = [textWidth, textHeight]
	}

	// 英文&数字
	if (!isCstr(item.content[i])) {
		// 超过编辑框高度
		if (_h + textWidth > item.height * devicePixelRatio) {
			ARR.push([lineHeight || _w, _h])
			LINEARR.push(_p)

			_w = textHeight
			_h = textWidth

			_p = item.content[i]
		} else {
			_w = Math.max(textHeight, _w)
			_h += textWidth

			_p += item.content[i]
		}
	} else {
		// 超过编辑框高度
		if (_h + textHeight > item.height * devicePixelRatio) {
			ARR.push([lineHeight || _w, _h])
			LINEARR.push(_p)

			_w = textWidth
			_h = textHeight

			_p = item.content[i]
		} else {
			_w = Math.max(textWidth, _w)
			_h += textHeight

			_p += item.content[i]
		}
	}


}

1.2 换行符换行

const ARR = [] // 存储每行的宽高
const WORDS = {} // 存储每个字的宽高
let LINEARR = [] // 片段数组


// 根据换行符分段
LINEARR = item.content.split('\n');

// 计算所有段落的宽度和高度
for (let i = 0; i < LINEARR.length; i++) {
	let _w = 0
	let _h = 0

	for (let j = 0; j < LINEARR[i].length; j++) {
		// 计算文字宽高
		const metrics = context.measureText(LINEARR[i][j]);

		// 计算文本宽度
		// const textWidth = metrics.actualBoundingBoxRight + metrics.actualBoundingBoxLeft;
		const textWidth = metrics.width

		// 计算文本高度
		// 所有字在这个字体下的高度
		const textHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent
		// 当前文本字符串在这个字体下用的实际高度
		// const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; 

		if (!WORDS[LINEARR[i][j]]) {
			WORDS[LINEARR[i][j]] = [textWidth, textHeight]
		}

		// 英文&数字
		if (!isCstr(LINEARR[i][j])) {
			_w = Math.max(textHeight, _w)
			_h += textWidth
		} else {
			_w = Math.max(textWidth, _w)
			_h += textHeight
		}

	}

	_maxWidth += (lineHeight || _w)
	_maxHeight = Math.max(_maxHeight, _h)

	ARR.push([lineHeight || _w, _h])
}

1.3 绘制逻辑

// 计算坐标并绘制文字
let x = (item.left + item.width) * devicePixelRatio
let y = item.top * devicePixelRatio
for (let i = 0; i < LINEARR.length; i++) {
	x -= ARR[i][0]
	y = item.top * devicePixelRatio

	// 判断对齐方式
	switch (item.align) {
		case 'top':
			break
		case 'middle':
			y += ((item.height * devicePixelRatio - ARR[i][1]) / 2)
			break
		case 'bottom':
			y += (item.height * devicePixelRatio - ARR[i][1])
			break
	}


	for (let j = 0; j < LINEARR[i].length; j++) {

		// 英文&数字
		if (!isCstr(LINEARR[i][j])) {
			context.save()

			if (j > 0) {
				// 英文&数字
				if (!isCstr(LINEARR[i][j - 1])) {
					y += WORDS[LINEARR[i][j - 1]][0]
				} else {
					y += WORDS[LINEARR[i][j - 1]][1]
				}
			}


			context.translate(x + ARR[i][0] / 2, y + WORDS[LINEARR[i][j]][0] / 2)

			context.rotate(Math.PI / 2)

			context.translate(-(x + ARR[i][0] / 2), -(y + WORDS[LINEARR[i][j]][0] / 2))

			// 自定义阴影
			if (item?.shadow) {
				context.save()
				context.filter = `blur(${item.shadow.blur})px`
				context.fillStyle = item.shadow.color
				context.strokeStyle = item.shadow.color
				const s = (item.fontSize / 120) * devicePixelRatio
				item.stroke && item.stroke.width > 0 && context.strokeText(LINEARR[i][j], x + (ARR[i][0] / 2 - WORDS[LINEARR[i][j]][0] / 2) + s * item.shadow.x, y + (WORDS[LINEARR[i][j]][0] / 2 - WORDS[LINEARR[i][j]][1] / 2) + s * item.shadow.y);
				context.fillText(LINEARR[i][j], x + (ARR[i][0] / 2 - WORDS[LINEARR[i][j]][0] / 2) + s * item.shadow.x, y + (WORDS[LINEARR[i][j]][0] / 2 - WORDS[LINEARR[i][j]][1] / 2) + s * item.shadow.y);
				context.restore()
			}

			// 主体文字
			item.stroke && item.stroke.width > 0 && context.strokeText(LINEARR[i][j], x + ARR[i][0] / 2 - WORDS[LINEARR[i][j]][0] / 2, y + WORDS[LINEARR[i][j]][0] / 2 - WORDS[LINEARR[i][j]][1] / 2);

			context.fillText(LINEARR[i][j], x + ARR[i][0] / 2 - WORDS[LINEARR[i][j]][0] / 2, y + WORDS[LINEARR[i][j]][0] / 2 - WORDS[LINEARR[i][j]][1] / 2);

			// context.beginPath()
			// context.strokeRect(x+ARR[i][0]/2-WORDS[LINEARR[i][j]][0]/2, y+WORDS[LINEARR[i][j]][0]/2-WORDS[LINEARR[i][j]][1]/2, WORDS[LINEARR[i][j]][0], WORDS[LINEARR[i][j]][1])
			// context.closePath()

			context.restore()

			// 下划线
			if (item?.underline) {
				context.save()
				context.strokeStyle = item.fontColor; // 描边颜色
				context.lineWidth = Math.max(item.fontSize * devicePixelRatio / 12, 1)
				context.beginPath()
				context.moveTo(x, y);
				context.lineTo(x, y + WORDS[LINEARR[i][j]][0]);
				context.closePath()
				context.stroke();
				context.restore();
			}
		} else {

			if (j > 0) {
				// 英文&数字
				if (!isCstr(LINEARR[i][j - 1])) {
					y += WORDS[LINEARR[i][j - 1]][0]
				} else {
					y += WORDS[LINEARR[i][j - 1]][1]
				}
			}

			// 自定义阴影
			if (item?.shadow) {
				context.save()
				context.filter = `blur(${item.shadow.blur})px`
				context.fillStyle = item.shadow.color
				context.strokeStyle = item.shadow.color
				const s = (item.fontSize / 120) * devicePixelRatio
				item.stroke && item.stroke.width > 0 && context.strokeText(LINEARR[i][j], x + (ARR[i][0] - WORDS[LINEARR[i][j]][0]) / 2 + s * item.shadow.x, y + s * item.shadow.y);
				context.fillText(LINEARR[i][j], x + (ARR[i][0] - WORDS[L

http://www.kler.cn/a/374391.html

相关文章:

  • Pandas DataFrame学习补充
  • 盘点Windows10系统之下好用的录屏工具!
  • PYNQ 框架 - VDMA驱动 - 帧缓存
  • 10进阶篇:运用第一性原理解答“是什么”类型题目
  • Mac下载 安装MIMIC-IV 3.0数据集
  • Synergy遇见的问题
  • 使用Python和OCR技术实现自动化办公:图片转文字
  • Vue3入门--[vue/compiler-sfc] Unexpected token, expected “,“ (18:0)
  • 安装Docker到指定目录
  • 学习stm32
  • 免费送源码:Java+ssm++MVC+HTML+CSS+MySQL springboot 社区医院信息管理系统的设计与实现 计算机毕业设计原创定制
  • 校园社团信息管理平台:Spring Boot技术实战指南
  • 自修室预约系统|基于java和小程序的自修室预约系统设计与实现(源码+数据库+文档)
  • CentOS 9 Stream 上安装 IntelliJ IDEA
  • 什么是线程局部变量(ThreadLocal)?
  • 金融领域中的敏感性分析和期权价值计算相关的操作
  • 动态规划 01背包(算法)
  • OV代码签名证书
  • Leetcode 移除元素
  • 流畅!HTMLCSS打造网格方块加载动画
  • 使用 Elastic、OpenLLMetry 和 OpenTelemetry 跟踪 LangChain 应用程序
  • 如何基于Apache SeaTunnel 读取Oracle的数据
  • Metasploit(MSF)使用
  • elasticsearch7.x在k8s中的部署
  • 【Visual Studio】解决 CC++ 控制台程序 printf 函数输出中文和换行符显示异常
  • logback 替换日志中的类名