新文章 网摘 文章 随笔 日记

HTML5 Canvas实现自动换行和竖排文本

文章转自:https://dandelioncloud.cn/article/details/1529086574952464386/

Canvas的渣渣API不支持自动换行和竖排,找了一下没有满意的解决方案,只好自己写一个了。

方法1:根据文字尺寸计算在哪换行

  1. // 画个框用来调试
  2. const DEBUG = true
  3. // 画文本,支持多行、自动换行、竖排文字
  4. function drawText (ctx, text, x, y, width, height, hasStroke = false, isVertical = false) {
  5. if (DEBUG) {
  6. let [oldLineWidth, oldStrokeStyle] = [ctx.lineWidth, ctx.strokeStyle];
  7. [ctx.lineWidth, ctx.strokeStyle] = [1, 'red']
  8. ctx.strokeRect(x, y, width, height);
  9. [ctx.lineWidth, ctx.strokeStyle] = [oldLineWidth, oldStrokeStyle]
  10. }
  11. if (!isVertical) {
  12. drawTextHorizontal(ctx, text, x, y, width, height, hasStroke)
  13. } else {
  14. drawTextVertical(ctx, text, x, y, width, height, hasStroke)
  15. }
  16. }
  17. // 画横排文本,垂直居中,可以垂直溢出
  18. function drawTextHorizontal (ctx, text, x, y, width, height, hasStroke = false) {
  19. let oldBaseLine = ctx.textBaseline
  20. ctx.textBaseline = 'hanging'
  21. let lineHeight = parseInt(ctx.font) // ctx.font必须以'XXpx'开头
  22. // 计算每一行
  23. let lines = []
  24. let curLine = ''
  25. for (let char of text) {
  26. let nextLine = curLine + char
  27. if (char === '\n' || ctx.measureText(nextLine).width > width) {
  28. lines.push(curLine)
  29. curLine = char === '\n' ? '' : char
  30. } else {
  31. curLine = nextLine
  32. }
  33. }
  34. lines.push(curLine)
  35. // 逐行画文本
  36. let lineY = y + (height - lineHeight * lines.length) / 2
  37. for (let line of lines) {
  38. let lineX
  39. if (ctx.textAlign === 'center') {
  40. lineX = x + width / 2
  41. } else if (ctx.textAlign === 'right') {
  42. lineX = x + width
  43. } else {
  44. lineX = x
  45. }
  46. if (hasStroke) {
  47. ctx.strokeText(line, lineX, lineY, width)
  48. }
  49. ctx.fillText(line, lineX, lineY, width)
  50. lineY += lineHeight
  51. }
  52. ctx.textBaseline = oldBaseLine
  53. }
  54. // 画竖排文本,从右到左,水平居中,可以水平溢出
  55. function drawTextVertical (ctx, text, x, y, width, height, hasStroke = false) {
  56. let [oldAlign, oldBaseLine] = [ctx.textAlign, ctx.textBaseline];
  57. [ctx.textAlign, ctx.textBaseline] = ['center', 'middle']
  58. let lineWidth = parseInt(ctx.font) // ctx.font必须以'XXpx'开头
  59. // 计算每个字符的尺寸信息
  60. let charInfo = []
  61. for (let char of text) {
  62. let cInfo = {
  63. char: char,
  64. needsRotation: needsRotation(char) // 中日韩文字不用旋转
  65. }
  66. if (cInfo.needsRotation) {
  67. [cInfo.width, cInfo.height] = [lineWidth, ctx.measureText(char).width]
  68. } else {
  69. [cInfo.width, cInfo.height] = [ctx.measureText(char).width, lineWidth]
  70. }
  71. charInfo.push(cInfo)
  72. }
  73. // 计算每一列
  74. let lineInfo = []
  75. let curLine = []
  76. let curLineHeight = 0
  77. for (let info of charInfo) {
  78. if (info.char === '\n' || curLineHeight + info.height > height) {
  79. lineInfo.push({
  80. charInfo: curLine,
  81. height: curLineHeight
  82. })
  83. curLine = info.char === '\n' ? [] : [info]
  84. curLineHeight = info.height
  85. } else {
  86. curLine.push(info)
  87. curLineHeight += info.height
  88. }
  89. }
  90. lineInfo.push({
  91. charInfo: curLine,
  92. height: curLineHeight
  93. })
  94. // 逐字画文本
  95. let lineX = x + (width + lineWidth * lineInfo.length) / 2 - lineWidth / 2 // 列中心的坐标
  96. for (let lInfo of lineInfo) {
  97. let charY // 字符顶端的坐标
  98. if (oldAlign === 'center') {
  99. charY = y + (height - lInfo.height) / 2
  100. } else if (oldAlign === 'right') { // 这里右对齐视为底端对齐,左对齐视为顶端对齐
  101. charY = y + height - lInfo.height
  102. } else {
  103. charY = y
  104. }
  105. // 画一列文本
  106. for (let cInfo of lInfo.charInfo) {
  107. ctx.translate(lineX, charY + cInfo.height / 2)
  108. if (cInfo.needsRotation) {
  109. ctx.rotate(90 * Math.PI / 180)
  110. }
  111. // 画一个字符
  112. if (hasStroke) {
  113. ctx.strokeText(cInfo.char, 0, 0)
  114. }
  115. ctx.fillText(cInfo.char, 0, 0)
  116. ctx.setTransform(1, 0, 0, 1, 0, 0)
  117. charY += cInfo.height
  118. }
  119. lineX -= lineWidth
  120. }
  121. [ctx.textAlign, ctx.textBaseline] = [oldAlign, oldBaseLine]
  122. }
  123. // 需要旋转的Unicode码范围,基本上是CJK文字
  124. const NO_ROTATION_RANGE = [
  125. [0x2E80, 0x2FEF],
  126. [0x3040, 0x9FFF],
  127. [0xAC00, 0xD7FF],
  128. [0xF900, 0xFAFF],
  129. [0x1D300, 0x1D35F],
  130. [0x20000, 0x2FA1F]
  131. ]
  132. function needsRotation (char) {
  133. let codePoint = char.codePointAt(0)
  134. for (let [lowerBound, upperBound] of NO_ROTATION_RANGE) {
  135. if (lowerBound <= codePoint && codePoint <= upperBound) {
  136. return false
  137. }
  138. }
  139. return true
  140. }

效果

横排

竖排

方法2:利用SVG图片的foreignObject嵌入CSS排版

这个方法不知道为什么竖排文本每列只有一个字,所以我就不用了,CSS真是玄学…

  1. async function drawText (ctx, text, x, y, width, height, hasStroke = false, isVertical = false) {
  2. let div = document.createElement('div')
  3. div.innerText = text
  4. let parentSize = DEBUG ? 'calc(100% - 2px)' : '100%'
  5. div.style = `
  6. word-wrap: break-word;
  7. word-break: break-all;
  8. font: ${ctx.font};
  9. text-align: ${isVertical ? 'left' : ctx.textAlign};
  10. ${isVertical ? 'height' : 'width'}: ${parentSize};
  11. position: relative;
  12. ${isVertical ? `right: 50%; transform: translateX(50%);` : `top: 50%; transform: translateY(-50%);`}
  13. ${isVertical ? 'writing-mode: vertical-rl;' : ''}
  14. ${isVertical ? 'float: right;' : ''}
  15. color: ${ctx.fillStyle};
  16. ${hasStroke ? `text-shadow: 0 1px ${ctx.strokeStyle}, 1px 0 ${ctx.strokeStyle}, -1px 0 ${ctx.strokeStyle}, 0 -1px ${ctx.strokeStyle};` : ''}
  17. ${DEBUG ? 'border: 1px solid red;' : ''}
  18. `
  19. let bodyStyle = `
  20. margin: 0;
  21. overflow: hidden;
  22. width: ${parentSize};
  23. height: ${parentSize};
  24. ${DEBUG ? 'border: 1px solid blue;' : ''}
  25. `
  26. let svg = `
  27. <svg xmlns="http://www.w3.org/2000/svg">
  28. <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}` }) }

效果

这个我设置成了溢出部分隐藏

横排

竖排

posted @ 2023-01-07 08:12  岭南春  阅读(989)  评论(0)    收藏  举报