text.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import { countTextLength } from '../utils/util'
  2. export default class DrawText {
  3. constructor(params) {
  4. const { Context, commonUtilMethods, commonDrawMethods } = params
  5. this.Context = Context
  6. this.commonUtilMethods = commonUtilMethods
  7. this.commonDrawMethods = commonDrawMethods
  8. }
  9. /**
  10. * 获取绘制参数
  11. * @param { Object } params 参数
  12. * @param { Boolean } conversion 是否需要转换单位
  13. */
  14. getDrawParams(params = {}, conversion = true) {
  15. const globalDataParams = this.commonUtilMethods.getGlobalDataDrawParams(params)
  16. // 绘制文字的参数
  17. const {
  18. text = '', textIndent = 0, lastWidth = 0, font = {},
  19. textAlign = 'none', baseline = 'top', line = {},
  20. highlightText = [], textRect = { show: false, isFill: true, lineWidth: 1 }
  21. } = params
  22. const textParams = {
  23. ...globalDataParams,
  24. text: String(text) || '',
  25. textIndent,
  26. lastWidth,
  27. font: conversion ? this.commonUtilMethods.conversionUnit(this.getFontStyle(font)) : this.getFontStyle(font, conversion),
  28. textAlign,
  29. baseline,
  30. line: this.getTextLine(line, conversion),
  31. highlightText,
  32. textRect,
  33. }
  34. return conversion ? this.commonUtilMethods.conversionUnit(textParams) : textParams
  35. }
  36. /**
  37. * 绘制文字
  38. * @param { Object } params 文字参数
  39. * @param { Boolean } conversion 是否需要转换单位
  40. */
  41. draw(params = {}, conversion = true) {
  42. const { Context, commonUtilMethods, commonDrawMethods } = this
  43. const { canvasWidth, is2d } = commonUtilMethods
  44. let {
  45. x, y, w, h, text, textIndent,
  46. lastWidth, font, color, alpha,
  47. isFill, line, windowAlign, textAlign, baseline, highlightText, textRect, offsetRight
  48. } = this.getDrawParams(params, conversion)
  49. Context.save()
  50. Context.beginPath()
  51. commonDrawMethods.setAlpha(alpha)
  52. Context.font = font.style
  53. if (!is2d) {
  54. // #ifdef MP-TOUTIAO
  55. // 不知道为啥现在字节跳动一定用这个方法来设置文字大小了. 之前还不用...
  56. Context.setFontSize(font.fontSize)
  57. // #endif
  58. Context.setTextBaseline(baseline)
  59. }else{
  60. Context.textBaseline = baseline
  61. }
  62. if (typeof text !== 'string') {
  63. text += ''
  64. }
  65. // console.log(this.formatTextData(text, color, highlightText))
  66. const textArr = params.textArr || commonDrawMethods.computedFontTextLineHeight({
  67. x, y, w, text, textIndent, lastWidth, font, line, textAlign, windowAlign, defaultColor: color, offsetRight, highlightText
  68. })
  69. // console.log(textArr)
  70. if (textRect.show) {
  71. // 水平的 垂直的
  72. const { vorizontal, vertical, ...textReactParmas } = commonUtilMethods.conversionUnit(textRect)
  73. // console.log({
  74. // vorizontal,
  75. // vertical,
  76. // })
  77. const firstText = textArr[0]
  78. // console.log(firstText)
  79. let tw = w === canvasWidth ? commonUtilMethods.conversionUnit(params).tw ?? firstText.w : w
  80. let tx = firstText.x - vorizontal
  81. let ty = firstText.y - vertical
  82. let ey = textArr[textArr.length - 1].y + font.fontSize
  83. let th = ey - firstText.y
  84. th += vertical * 2
  85. // #ifdef MP-WEIXIN || MP-TOUTIAO
  86. if (uni.getSystemInfoSync().platform === 'android') {
  87. ty += font.fontSize / 3
  88. th -= font.fontSize / 3
  89. }
  90. // #endif
  91. tw += (vorizontal * 2)
  92. commonDrawMethods.drawParams.drawRect.draw({
  93. x: tx,
  94. y: ty,
  95. w: tw,
  96. h: th,
  97. ...textReactParmas
  98. }, false)
  99. }
  100. Context.font = font.style
  101. let currentFontSize = 0
  102. if (font.style.includes(8)) {
  103. currentFontSize = 8
  104. } else if (font.style.includes(16)) {
  105. currentFontSize = 16
  106. } else {
  107. currentFontSize = font.fontSize
  108. }
  109. textArr.forEach(item => {
  110. let { text, x, y, tx, ty, tw, fontColor, fontSize, font: customFont } = item
  111. if (currentFontSize !== fontSize) {
  112. const { fontStyle, fontVariant, fontWeight, fontFamily } = font
  113. const { fontStyle: customFontStyle, fontVariant: customFontVariant, fontWeight: customFontWeight, fontFamily: customfontFamily } = customFont
  114. Context.font = `${customFontStyle || fontStyle} ${customFontVariant || fontVariant} ${customFontWeight || fontWeight} ${fontSize || currentFontSize}px ${customfontFamily || fontFamily}`
  115. currentFontSize = fontSize
  116. }
  117. if (isFill) {
  118. commonDrawMethods.setFillStyle(fontColor)
  119. Context.fillText(text, x, y)
  120. } else {
  121. commonDrawMethods.setStrokeStyle(fontColor)
  122. Context.strokeText(text, x, y)
  123. }
  124. if (line.lineStyle !== 'none') {
  125. if (isFill) {
  126. commonDrawMethods.setFillStyle(color)
  127. } else {
  128. commonDrawMethods.setStrokeStyle(color)
  129. }
  130. commonDrawMethods.drawParams.drawLine.draw({
  131. x: tx,
  132. y: ty,
  133. w: tw,
  134. color,
  135. lineType: line.lineType,
  136. lineWidth: line.lineWidth
  137. }, false)
  138. }
  139. })
  140. Context.restore()
  141. Context.font = this.getFontStyle().style
  142. commonDrawMethods.setAlpha(1)
  143. }
  144. /**
  145. * 获取字体样式
  146. * @param { Object } font 字体样式
  147. * @param { Boolean } conversion 是否装换单位
  148. * @returns
  149. */
  150. getFontStyle(fontStyle = {}, conversion = true) {
  151. const { size, family, style, variant, weight } = {...this.commonUtilMethods.fontStyle, ...fontStyle}
  152. return {
  153. fontSize: size,
  154. fontSizeBefore: size,
  155. fontFamily: family,
  156. fontStyle: style,
  157. fontVariant: variant,
  158. fontWeight: weight,
  159. style: `${style || 'normal'} ${variant || 'normal'} ${weight || 'normal'} ${conversion ? this.commonUtilMethods.getConvertedValue(size) : size}px ${family || 'sans-serif'}`
  160. }
  161. }
  162. /**
  163. * 获取文字line样式
  164. * @param { Object } line 行高
  165. * @param { Object } conversion 是否转换单位默认true
  166. */
  167. getTextLine(line = {}, conversion = true) {
  168. const { num = -1, height = 0, style = 'none', type = 'solid', width = 1 } = line
  169. return {
  170. lineNum: num,
  171. lineHeight: conversion ? this.commonUtilMethods.getConvertedValue(height) : height,
  172. lineStyle: style,
  173. lineType: type,
  174. lineWidth: width
  175. }
  176. }
  177. /**
  178. * 格式化文字
  179. * @param { String } formatText 文字内容
  180. * @param { String } color 文字默认颜色
  181. * @param { Object } defaultFont 文字
  182. * @param { Array } highlightText 高亮文字
  183. * @returns
  184. */
  185. formatTextData(formatText, color = 'black', defaultFont, highlightText = []) {
  186. const isBr = formatText.includes('\n')
  187. const isHightText = highlightText.length !== 0
  188. if (isBr) {
  189. const formatTexts = formatText.split('\n')
  190. if (isHightText) {
  191. return formatTexts.map(text => this.formatHighlightText(text, color, defaultFont, highlightText)).flat()
  192. }
  193. return formatTexts.map(text => ({ text, color, font: defaultFont }))
  194. }
  195. if (isHightText) {
  196. return this.formatHighlightText(formatText, color, defaultFont, highlightText)
  197. }
  198. return [{ text: formatText, color, font: defaultFont }]
  199. }
  200. /**
  201. * 格式化高亮文字
  202. * @param { String } formatText 文字
  203. * @param { String } defaultColor 默认颜色
  204. * @param { Object } defaultFont 默认字体
  205. * @param { Array } highlightText 高亮文字
  206. * @returns
  207. */
  208. formatHighlightText(formatText, defaultColor = 'black', defaultFont, highlightText){
  209. const row = []
  210. const texts = highlightText.map(item => {
  211. item.i = 0
  212. return item
  213. })
  214. let flag = true
  215. const rulesData = []
  216. while (flag) {
  217. texts.forEach((item) => {
  218. const { text, color = defaultColor, i, fontSize, siblingsCenter = 'bottom', siblingsNum = 0, ...customFont } = item
  219. const index = formatText.indexOf(text, i)
  220. if (formatText.includes(text) && index !== -1) {
  221. item.i = index + 1 + text.length
  222. rulesData.push(({
  223. index,
  224. length: text.length,
  225. color,
  226. fontSize: fontSize ?? defaultFont.fontSize,
  227. font: Object.keys(customFont).length !== 0 ? this.getFontStyle(customFont, false) : defaultFont,
  228. siblingsCenter,
  229. siblingsNum
  230. }))
  231. } else {
  232. flag = false
  233. }
  234. })
  235. }
  236. const rulesIndex = rulesData.map(item => item.index)
  237. let text = ''
  238. for (let i = 0; i < formatText.length; i++) {
  239. if (rulesIndex.includes(i)) {
  240. const { length, color, fontSize, font, siblingsCenter, siblingsNum } = rulesData.find(item => item.index === i)
  241. if (text) {
  242. row.push({
  243. text,
  244. color: defaultColor,
  245. fontSize: defaultFont.fontSize,
  246. font: defaultFont,
  247. })
  248. text = ''
  249. }
  250. row.push({
  251. text: formatText.slice(i, i + length),
  252. color,
  253. fontSize,
  254. font,
  255. siblingsCenter,
  256. siblingsNum
  257. })
  258. i += length - 1
  259. continue
  260. }
  261. text += formatText[i]
  262. if (i === formatText.length - 1) {
  263. row.push({
  264. text,
  265. color: defaultColor,
  266. fontSize: defaultFont.fontSize,
  267. font: defaultFont
  268. })
  269. }
  270. }
  271. return row
  272. }
  273. }