HTML5 Canvas实现自动换行和竖排文本
文章转自:https://dandelioncloud.cn/article/details/1529086574952464386/
Canvas的渣渣API不支持自动换行和竖排,找了一下没有满意的解决方案,只好自己写一个了。
方法1:根据文字尺寸计算在哪换行
// 画个框用来调试const DEBUG = true// 画文本,支持多行、自动换行、竖排文字function drawText (ctx, text, x, y, width, height, hasStroke = false, isVertical = false) {if (DEBUG) {let [oldLineWidth, oldStrokeStyle] = [ctx.lineWidth, ctx.strokeStyle];[ctx.lineWidth, ctx.strokeStyle] = [1, 'red']ctx.strokeRect(x, y, width, height);[ctx.lineWidth, ctx.strokeStyle] = [oldLineWidth, oldStrokeStyle]}if (!isVertical) {drawTextHorizontal(ctx, text, x, y, width, height, hasStroke)} else {drawTextVertical(ctx, text, x, y, width, height, hasStroke)}}// 画横排文本,垂直居中,可以垂直溢出function drawTextHorizontal (ctx, text, x, y, width, height, hasStroke = false) {let oldBaseLine = ctx.textBaselinectx.textBaseline = 'hanging'let lineHeight = parseInt(ctx.font) // ctx.font必须以'XXpx'开头// 计算每一行let lines = []let curLine = ''for (let char of text) {let nextLine = curLine + charif (char === '\n' || ctx.measureText(nextLine).width > width) {lines.push(curLine)curLine = char === '\n' ? '' : char} else {curLine = nextLine}}lines.push(curLine)// 逐行画文本let lineY = y + (height - lineHeight * lines.length) / 2for (let line of lines) {let lineXif (ctx.textAlign === 'center') {lineX = x + width / 2} else if (ctx.textAlign === 'right') {lineX = x + width} else {lineX = x}if (hasStroke) {ctx.strokeText(line, lineX, lineY, width)}ctx.fillText(line, lineX, lineY, width)lineY += lineHeight}ctx.textBaseline = oldBaseLine}// 画竖排文本,从右到左,水平居中,可以水平溢出function drawTextVertical (ctx, text, x, y, width, height, hasStroke = false) {let [oldAlign, oldBaseLine] = [ctx.textAlign, ctx.textBaseline];[ctx.textAlign, ctx.textBaseline] = ['center', 'middle']let lineWidth = parseInt(ctx.font) // ctx.font必须以'XXpx'开头// 计算每个字符的尺寸信息let charInfo = []for (let char of text) {let cInfo = {char: char,needsRotation: needsRotation(char) // 中日韩文字不用旋转}if (cInfo.needsRotation) {[cInfo.width, cInfo.height] = [lineWidth, ctx.measureText(char).width]} else {[cInfo.width, cInfo.height] = [ctx.measureText(char).width, lineWidth]}charInfo.push(cInfo)}// 计算每一列let lineInfo = []let curLine = []let curLineHeight = 0for (let info of charInfo) {if (info.char === '\n' || curLineHeight + info.height > height) {lineInfo.push({charInfo: curLine,height: curLineHeight})curLine = info.char === '\n' ? [] : [info]curLineHeight = info.height} else {curLine.push(info)curLineHeight += info.height}}lineInfo.push({charInfo: curLine,height: curLineHeight})// 逐字画文本let lineX = x + (width + lineWidth * lineInfo.length) / 2 - lineWidth / 2 // 列中心的坐标for (let lInfo of lineInfo) {let charY // 字符顶端的坐标if (oldAlign === 'center') {charY = y + (height - lInfo.height) / 2} else if (oldAlign === 'right') { // 这里右对齐视为底端对齐,左对齐视为顶端对齐charY = y + height - lInfo.height} else {charY = y}// 画一列文本for (let cInfo of lInfo.charInfo) {ctx.translate(lineX, charY + cInfo.height / 2)if (cInfo.needsRotation) {ctx.rotate(90 * Math.PI / 180)}// 画一个字符if (hasStroke) {ctx.strokeText(cInfo.char, 0, 0)}ctx.fillText(cInfo.char, 0, 0)ctx.setTransform(1, 0, 0, 1, 0, 0)charY += cInfo.height}lineX -= lineWidth}[ctx.textAlign, ctx.textBaseline] = [oldAlign, oldBaseLine]}// 需要旋转的Unicode码范围,基本上是CJK文字const NO_ROTATION_RANGE = [[0x2E80, 0x2FEF],[0x3040, 0x9FFF],[0xAC00, 0xD7FF],[0xF900, 0xFAFF],[0x1D300, 0x1D35F],[0x20000, 0x2FA1F]]function needsRotation (char) {let codePoint = char.codePointAt(0)for (let [lowerBound, upperBound] of NO_ROTATION_RANGE) {if (lowerBound <= codePoint && codePoint <= upperBound) {return false}}return true}
效果


方法2:利用SVG图片的foreignObject嵌入CSS排版
这个方法不知道为什么竖排文本每列只有一个字,所以我就不用了,CSS真是玄学…
async function drawText (ctx, text, x, y, width, height, hasStroke = false, isVertical = false) {let div = document.createElement('div')div.innerText = textlet parentSize = DEBUG ? 'calc(100% - 2px)' : '100%'div.style = `word-wrap: break-word;word-break: break-all;font: ${ctx.font};text-align: ${isVertical ? 'left' : ctx.textAlign};${isVertical ? 'height' : 'width'}: ${parentSize};position: relative;${isVertical ? `right: 50%; transform: translateX(50%);` : `top: 50%; transform: translateY(-50%);`}${isVertical ? 'writing-mode: vertical-rl;' : ''}${isVertical ? 'float: right;' : ''}color: ${ctx.fillStyle};${hasStroke ? `text-shadow: 0 1px ${ctx.strokeStyle}, 1px 0 ${ctx.strokeStyle}, -1px 0 ${ctx.strokeStyle}, 0 -1px ${ctx.strokeStyle};` : ''}${DEBUG ? 'border: 1px solid red;' : ''}`let bodyStyle = `margin: 0;overflow: hidden;width: ${parentSize};height: ${parentSize};${DEBUG ? 'border: 1px solid blue;' : ''}`let svg = `<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="${width}px" height="${height}px"> <body xmlns="http://www.w3.org/1999/xhtml" style="${bodyStyle}"> ${div.outerHTML} </body> </foreignObject> </svg> ` if (DEBUG) { console.log(svg) } return new Promise((resolve, reject) => { let img = new window.Image() img.onload = () => { ctx.drawImage(img, x, y) resolve() } img.onerror = () => { reject(new Error(`Failed to load the image: ${img.src}`)) } img.src = `data:image/svg+xml;charset=utf-8,${svg}` }) }
效果
这个我设置成了溢出部分隐藏


浙公网安备 33010602011771号