common.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. import { conversionUnit, countTextLength } from './util'
  2. /**
  3. * 公共工具方法
  4. */
  5. export class CommonUtilMethods {
  6. constructor(options) {
  7. const { unit, type, width, height, fontStyle } = options
  8. this.unit = unit
  9. this.type = type
  10. this.is2d = type === '2d'
  11. this.width = width
  12. this.height = height
  13. this.fontStyle = fontStyle
  14. this.Context = null
  15. this.canvas = null
  16. }
  17. setCanvasStyle() {
  18. const { width, height } = this
  19. // 绘制时的参数用于当unit为rpx时可以保存之前的参数并且获取转换单位之后的参数值
  20. this.canvasWidth = this.getConvertedValue(width)
  21. this.canvasHeight = this.getConvertedValue(height)
  22. }
  23. /**
  24. * 根据unit单位获取转化过后的数值(rpx下绘制的单位需要转换成px)
  25. * @param { Number } value 数值
  26. * @returns
  27. */
  28. getConvertedValue(value = 0) {
  29. if (value === 0) return 0
  30. if (this.unit === 'px') {
  31. return value
  32. }
  33. return value === 375.0001 ? 375.0001 : uni.upx2px(value)
  34. }
  35. /**
  36. * px单位转换成rpx单位
  37. * @param { Number } value
  38. */
  39. pxToRpx(value = 0) {
  40. return value / (uni.upx2px(100) / 100)
  41. }
  42. /***
  43. * rpx单位转换为px
  44. * @param { String | Number } value 单位大小
  45. */
  46. rpxToPx(value = 0) {
  47. let type = false
  48. if (value.includes('px') && !value.includes('r') && !value.includes('u')) {
  49. value = Number(value.split('px')[0])
  50. type = true
  51. }else if (value.includes('rpx')) {
  52. value = Number(value.split('rpx')[0])
  53. } else if (value.includes('upx')) {
  54. value = Number(value.split('upx')[0])
  55. }
  56. return type ? value : uni.upx2px(value)
  57. }
  58. /**
  59. * 转换单位
  60. * @param { Object | Array } params 参数
  61. * @returns
  62. */
  63. conversionUnit(params) {
  64. return conversionUnit(params, this.getConvertedValue, this)
  65. }
  66. /**
  67. * 获取公共的绘制参数
  68. * @param { Object } params 参数
  69. */
  70. getGlobalDataDrawParams(params = {}) {
  71. const { width } = this
  72. const { x = 0, y = 0, w = width, h = 0, r = 0, color = 'black', alpha = 1, isFill = true,
  73. lineWidth = 1, windowAlign = 'none', rotate = {}, drawImage = false,
  74. borderColor = '#000000', borderWidth = 0, borderType = 'default', offsetRight = 0,
  75. shadow, gradient = {},
  76. } = params
  77. const drawParams = { x, y, w, h, r }
  78. // 公共的参数
  79. return {
  80. ...drawParams,
  81. color,
  82. lineWidth,
  83. borderWidth,
  84. borderColor,
  85. borderType,
  86. alpha,
  87. isFill,
  88. // 窗口对齐的方式 默认: none 可选 居中: center 右边: right
  89. windowAlign,
  90. // 旋转
  91. rotate,
  92. // 阴影
  93. shadow: this.getShadow(shadow),
  94. // 渐变
  95. gradient,
  96. // 距离右边的差值
  97. offsetRight,
  98. drawImage
  99. }
  100. }
  101. /**
  102. * 获取阴影参数
  103. * @param { Boolean } shadow.show 是否使用阴影
  104. * @param { Number } shadow.x 阴影相对于形状在水平方向的偏移
  105. * @param { Number } shadow.y 阴影相对于形状在竖直方向的偏移
  106. * @param { Number } shadow.blur 阴影的模糊级别,数值越大越模糊
  107. * @param { String } shadow.color 阴影的颜色
  108. * @returns
  109. */
  110. getShadow(shadow = {}) {
  111. const { show = false, x = 0, y = 0, blur = 0, color = '#000000' } = shadow
  112. return { show, x, y, blur, color }
  113. }
  114. /**
  115. * 获取渐变参数
  116. * @param { String } type 渐变类型 linear: 线性 circle: 圆形
  117. * @param { Boolean } relative 相对定位
  118. * @param { Object } position 位置
  119. * @param { Object } colors 颜色
  120. * @param { Object } box 盒子的位置
  121. * @returns
  122. */
  123. getGradient(type = 'linear', relative = true, position = {}, colors = [], box = {}) {
  124. const { x0 = 0, y0 = 0, x1 = 0, y1 = 0, x = 0, y = 0, r = 0 } = position
  125. if (type == 'linear') {
  126. position.x0 = relative ? x0 + box.x : x0
  127. position.y0 = relative ? y0 + box.y : y0
  128. position.x1 = relative ? x1 + box.x : x1
  129. position.y1 = relative ? y1 + box.y : y1
  130. } else {
  131. position.x = relative ? x + box.x : x
  132. position.y = relative ? y + box.y : y
  133. position.r = r
  134. }
  135. return type == 'linear' ? this.getLinearGradient(position, colors) : this.getCircularGradient(position, colors)
  136. }
  137. /**
  138. * 获取线性渐变颜色
  139. * @param { Object } position 位置对象
  140. * @param { Array } colors 颜色坐标
  141. */
  142. getLinearGradient(position = {}, colors = []) {
  143. const { Context } = this
  144. const { x0, y0, x1, y1 } = position
  145. const grd = Context.createLinearGradient(x0, y0, x1, y1)
  146. for(let i of colors) {
  147. grd.addColorStop(i.stop, i.color)
  148. }
  149. return grd
  150. }
  151. /**
  152. * 获取圆形渐变
  153. * @param { Object } position 位置对象
  154. * @param { Array } colors 颜色坐标
  155. */
  156. getCircularGradient(position = {}, colors = []) {
  157. const { Context } = this
  158. const { x, y, r } = position
  159. const grd = Context.createCircularGradient(x, y, r)
  160. for(let i of colors) {
  161. grd.addColorStop(i.stop, i.color)
  162. }
  163. return grd
  164. }
  165. }
  166. /**
  167. * 公共绘制方法
  168. */
  169. export class CommonDrawMethods {
  170. constructor(Context, type, commonUtilMethods) {
  171. this.Context = Context
  172. this.type = type
  173. this.is2d = type === '2d'
  174. this.commonUtilMethods = commonUtilMethods
  175. this.drawParams = {}
  176. }
  177. /**
  178. * 设置透明度
  179. * @param { number } alpha 透明度
  180. */
  181. setAlpha(alpha = 1) {
  182. const { Context, is2d } = this
  183. if (is2d) {
  184. Context.globalAlpha = alpha
  185. } else {
  186. Context.setGlobalAlpha(alpha)
  187. }
  188. }
  189. /**
  190. * 设置填充颜色
  191. * @param { String } style 填充颜色
  192. */
  193. setFillStyle(style) {
  194. const { Context, is2d } = this
  195. if (is2d) {
  196. Context.fillStyle = style
  197. } else {
  198. Context.setFillStyle(style)
  199. }
  200. }
  201. /**
  202. * 设置宽度大小
  203. * @param { number } width 宽度
  204. */
  205. setLineWidth(width) {
  206. const { Context, is2d } = this
  207. if (is2d) {
  208. Context.lineWidth = width
  209. } else {
  210. Context.setLineWidth(width)
  211. }
  212. }
  213. /**
  214. * 设置描边颜色
  215. * @param { String } style 颜色
  216. */
  217. setStrokeStyle(style) {
  218. const { Context, is2d } = this
  219. if (is2d) {
  220. Context.strokeStyle = style
  221. } else {
  222. Context.setStrokeStyle(style)
  223. }
  224. }
  225. /**
  226. * 设置阴影
  227. * @param { Object } params
  228. */
  229. setShadow(x, y, blur, color) {
  230. const { Context, is2d } = this
  231. Context.shadowOffsetX = x
  232. Context.shadowOffsetY = y
  233. Context.shadowColor = color
  234. Context.shadowBlur = blur
  235. }
  236. /**
  237. * 设置旋转角度
  238. * @param { String } x x轴
  239. * @param { String } y y轴
  240. * @param { String } w 宽度
  241. * @param { String } h 高度
  242. * @param { Object } rotate 旋转内容
  243. * @param { String } rotate.deg 旋转角度
  244. * @param { String } rotate.type 类型 topLeft: 中心点在上左 topMiddle 中心点在上中 topRight 中心点在上右
  245. * middleLeft: 中心点在中左 bottomMiddle 中心点在正中间 middleRight 中心点在中右
  246. * bottomLeft: 中心点在下左 bottomMiddle 中心点在下中 middleRight 中心点在下右
  247. */
  248. setRotate(x, y, w, h, rotate) {
  249. const { Context } = this
  250. const { deg = 0, type = 'middle' } = rotate
  251. let rx = x
  252. let ry = y
  253. switch (type) {
  254. case 'topLeft':
  255. Context.translate(rx, ry)
  256. Context.rotate(deg * Math.PI / 180)
  257. Context.translate(-rx, -ry)
  258. break
  259. case 'topMiddle':
  260. rx = x + (w / 2)
  261. Context.translate(rx, ry)
  262. Context.rotate(deg * Math.PI / 180)
  263. Context.translate(-rx, -ry)
  264. break
  265. case 'topRight':
  266. rx = x + w
  267. Context.translate(rx, ry)
  268. Context.rotate(deg * Math.PI / 180)
  269. Context.translate(-rx, -ry)
  270. break
  271. case 'bottomLeft':
  272. ry = y + h
  273. Context.translate(rx, ry)
  274. Context.rotate(deg * Math.PI / 180)
  275. Context.translate(-rx, -ry)
  276. break
  277. case 'bottomMiddle':
  278. rx = x + (w / 2)
  279. ry = y + h
  280. Context.translate(rx, ry)
  281. Context.rotate(deg * Math.PI / 180)
  282. Context.translate(-rx, -ry)
  283. break
  284. case 'bottomRight':
  285. rx = x + w
  286. ry = y + h
  287. Context.translate(rx, ry)
  288. Context.rotate(deg * Math.PI / 180)
  289. Context.translate(-rx, -ry)
  290. break
  291. case 'middleLeft':
  292. ry = y + (h / 2)
  293. Context.translate(rx, ry)
  294. Context.rotate(deg * Math.PI / 180)
  295. Context.translate(-rx, -ry)
  296. break
  297. case 'middleRight':
  298. rx = x + w
  299. ry = y + (h / 2)
  300. Context.translate(rx, ry)
  301. Context.rotate(deg * Math.PI / 180)
  302. Context.translate(-rx, -ry)
  303. break
  304. case 'middle':
  305. rx = x + (w / 2)
  306. ry = y + (h / 2)
  307. Context.translate(rx, ry)
  308. Context.rotate(deg * Math.PI / 180)
  309. Context.translate(-rx, -ry)
  310. break
  311. }
  312. }
  313. /**
  314. * 设置三角形旋转角度
  315. * @param { String } x x轴
  316. * @param { String } y y轴
  317. * @param { String } w 宽度
  318. * @param { String } h 高度
  319. * @param { Object } rotate 旋转内容
  320. * @param { String } rotate.deg 旋转角度
  321. * @param { String } rotate.type 类型 top: 上 left: 左 right: 右 middle: 中心
  322. * @param { String } triangType 三角形类型(不支持自定义的三角形 ) right: 直角三角形 isosceles: 等腰三角形
  323. */
  324. setTriangleRotate(x, y, w, h, rotate, triangType) {
  325. const { Context } = this
  326. const { deg = 0, type = 'middle' } = rotate
  327. let rx = x
  328. let ry = y
  329. switch (type) {
  330. case 'top':
  331. if (triangType == 'right') {
  332. rx = x
  333. ry = y
  334. } else {
  335. rx = x + w / 2
  336. ry = y
  337. }
  338. Context.translate(rx, ry)
  339. Context.rotate(deg * Math.PI / 180)
  340. Context.translate(-rx, -ry)
  341. break
  342. case 'left':
  343. rx = x
  344. ry = y + h
  345. Context.translate(rx, ry)
  346. Context.rotate(deg * Math.PI / 180)
  347. Context.translate(-rx, -ry)
  348. break
  349. case 'right':
  350. rx = x + w
  351. ry = y + h
  352. Context.translate(rx, ry)
  353. Context.rotate(deg * Math.PI / 180)
  354. Context.translate(-rx, -ry)
  355. break
  356. case 'middle':
  357. rx = x + w / 2
  358. ry = y + h / 2
  359. Context.translate(rx, ry)
  360. Context.rotate(deg * Math.PI / 180)
  361. Context.translate(-rx, -ry)
  362. break
  363. }
  364. }
  365. /**
  366. * 计算内容需要显示在画布中间的x轴的位置
  367. * @param { Number | String } boxWidth 容器的宽度
  368. * @param { Number | String } contentWidth 内容宽度
  369. * @param { String } type 类型 center: 水平 right: 右边
  370. * @param { Number } offsetRight 距离右边的差值
  371. * @returns
  372. */
  373. computedCenterX(boxWidth, contentWidth, type = 'center', offsetRight = 0) {
  374. if (type === 'center') {
  375. return ((boxWidth - contentWidth) / 2) - offsetRight
  376. }
  377. if (type === 'right') {
  378. return boxWidth - contentWidth - offsetRight
  379. }
  380. return 0
  381. }
  382. /**
  383. * 计算出文字一共有多少列,渲染位置之类
  384. * @param { Number } params.x x轴
  385. * @param { Number } params.y y轴
  386. * @param { Number } params.w 文字宽度
  387. * @param { String } params.text 文字内容
  388. * @param { Number } params.textIndent 首行缩进
  389. * @param { Number } params.lastWidth 最后一行的宽度
  390. * @param { Object } params.font 字体
  391. * @param { Object } params.line 行高
  392. * @param { String } params.textAlign 文字对齐方式
  393. * @param { String } params.windowAlign 窗口对齐方式
  394. * @param { String } params.defaultColor 字体默认颜色
  395. * @param { String } params.offsetRight 距离右侧的距离
  396. * @param { Array } params.highlightText 高亮文字
  397. * @returns
  398. */
  399. computedFontTextLineHeight(params) {
  400. const { x, y, w, text, textIndent, lastWidth, font, line, textAlign, windowAlign, defaultColor, offsetRight, highlightText } = params
  401. const { Context, commonUtilMethods, drawParams } = this
  402. const { canvasWidth, getConvertedValue, is2d } = commonUtilMethods
  403. const { fontSize } = font
  404. // console.log(font)
  405. Context.font = font.style
  406. // console.log()
  407. // #ifdef MP-TOUTIAO
  408. // 不知道为啥现在字节跳动一定用这个方法来设置文字大小了. 之前还不用...
  409. if (!is2d) {
  410. Context.setFontSize(fontSize)
  411. }
  412. // #endif
  413. // console.log('fontSize', fontSize)
  414. let { lineNum, lineHeight, lineStyle } = line
  415. // 文字的总长度
  416. // let textLength = countTextLength(Context, text, fontSize)
  417. // if (w == 0 || w == 375.0001) {
  418. // textLength > canvasWidth ? w = canvasWidth : w = textLength + fontSize
  419. // }
  420. const formatTexts = drawParams.drawText.formatTextData(text, defaultColor, font, highlightText)
  421. let currentTextLength = 0
  422. let rowIndex = 0
  423. let indentLength = 0
  424. let currentFontSize = fontSize
  425. let currentFontStyle = font.style
  426. // console.log(font)
  427. const rowData = formatTexts.map(({ text, color, font: customFont, siblingsCenter, siblingsNum }, index) => {
  428. const row = []
  429. const allData = []
  430. let text2 = ''
  431. let length = 0
  432. // 计算时的fontSize
  433. // if (currentFontSize !== )
  434. const computedFontSize = customFont.fontSize || fontSize
  435. if (computedFontSize !== currentFontSize) {
  436. currentFontSize = computedFontSize
  437. currentFontStyle = customFont.style
  438. Context.font = currentFontStyle
  439. }
  440. const setData = (i) => {
  441. const current = row.length == 0 ? 0 : row[row.length - 1].index
  442. const newAllData = Object.assign([], allData).splice(current, i)
  443. // console.log(lastWidth)
  444. const limitLastText = lastWidth != 0 && newAllData.length != 0 && newAllData.reverse().find(item => item.length <= lastWidth)
  445. row.push({
  446. length,
  447. text: text2,
  448. index: i - 1,
  449. lastText: text2.substring(0, text2.length - 1) + '...',
  450. limitText: limitLastText ? limitLastText.text + '...' : '',
  451. limitTextWidth: countTextLength(Context, limitLastText.text + '...', computedFontSize),
  452. color,
  453. rowIndex,
  454. dataX: currentTextLength,
  455. fontSize: computedFontSize,
  456. fontStyle: currentFontStyle,
  457. font: customFont,
  458. isCustomFont: currentFontSize !== fontSize,
  459. // 当自定义字体大小的时候,设置的排序
  460. siblingsCenter,
  461. siblingsNum
  462. })
  463. text2 = ''
  464. // indentLength = 0
  465. }
  466. for (let i in text) {
  467. text2 += text[i]
  468. // console.log(text2)
  469. length = countTextLength(Context, text2, computedFontSize)
  470. if (textIndent !== 0 && rowIndex === 0 && indentLength === 0) {
  471. currentTextLength += textIndent
  472. ++indentLength
  473. }
  474. if (currentTextLength + length > w - computedFontSize) {
  475. if (i === text.length - 1) {
  476. length = 0
  477. }
  478. setData(i)
  479. currentTextLength = 0
  480. rowIndex++
  481. length = 0
  482. continue
  483. }
  484. if (length >= w - computedFontSize || i == text.length - 1) {
  485. setData(i)
  486. // currentTextLength = 0
  487. }
  488. allData.push({
  489. index: i,
  490. text: text2,
  491. length
  492. })
  493. }
  494. currentTextLength += length
  495. return row
  496. }).flat()
  497. // console.log(rowData)
  498. // 行高
  499. lineHeight = lineHeight == 1 ? fontSize + 2 : lineHeight
  500. // 获取一共有多少列
  501. let lineSize = rowData.length
  502. // if (lineNum != - 1 && lineNum > lineSize) {
  503. // lineNum = lineSize
  504. // }
  505. // console.log(rowData.map(v => v.rowIndex))
  506. const findRowPosition = rowData.findIndex(item => item.rowIndex == lineNum)
  507. let size = lineNum != -1 && findRowPosition != -1 ? findRowPosition : lineSize
  508. const lastRowDataSum = rowData.filter(item => (item.rowIndex + 1) == lineNum).length
  509. let lastRowCount = 0
  510. if (size > lineSize) {
  511. size = lineSize
  512. }
  513. let textArr = []
  514. for (let i = 0; i < size; i++) {
  515. let { text, length: textLength, lastText, limitText, limitTextWidth, color, rowIndex, dataX, fontSize: customFontSize, font, isCustomFont } = rowData[i]
  516. let obj = {}
  517. let wx = x + (dataX ?? 0)
  518. let tx = x + (dataX ?? 0)
  519. if (rowIndex === 0 && dataX === 0 && textIndent !== 0 && textAlign == 'none' && windowAlign == 'none') {
  520. textLength += textIndent
  521. wx += textIndent
  522. tx = wx
  523. }
  524. if (text && lineNum != -1 && rowIndex == lineNum - 1) {
  525. if (++lastRowCount === lastRowDataSum) {
  526. if (lastWidth > 0) {
  527. text = limitText
  528. textLength = limitTextWidth
  529. } else {
  530. text = textLength + dataX + customFontSize >= w ? lastText : text
  531. }
  532. }
  533. }
  534. if (textAlign !== 'none' && textLength <= w) {
  535. let textW = w
  536. if (textLength == w) {
  537. textW = canvasWidth
  538. }
  539. tx = this.computedCenterX(textW, textLength, textAlign)
  540. wx += tx
  541. }
  542. if (windowAlign !== 'none' && textAlign !== 'none') {
  543. wx = this.computedCenterX(canvasWidth, w, windowAlign, offsetRight)
  544. wx += tx
  545. tx = wx
  546. } else if (windowAlign !== 'none') {
  547. wx = this.computedCenterX(canvasWidth, w, windowAlign, offsetRight)
  548. tx = wx
  549. }
  550. let tw = 0
  551. let ty = y + (rowIndex * lineHeight)
  552. if (lineStyle !== 'none') {
  553. tx = tx + getConvertedValue(0)
  554. tw = textLength + getConvertedValue(0)
  555. if (lineStyle == 'underline') {
  556. ty += customFontSize + 1
  557. } else if (lineStyle == 'lineThrough') {
  558. ty += customFontSize / 2
  559. // #ifdef MP-WEIXIN
  560. if (uni.getSystemInfoSync().platform === 'android' && this.type !== '2d') {
  561. ty += customFontSize / 6
  562. }
  563. // #endif
  564. }
  565. }
  566. obj.w = textLength
  567. obj.text = text
  568. obj.x = wx
  569. if (lineHeight == 0) {
  570. obj.y = y + (customFontSize * rowIndex)
  571. ty = ty + (customFontSize * rowIndex)
  572. } else {
  573. obj.y = y + (lineHeight * rowIndex)
  574. }
  575. if (isCustomFont) {
  576. const { siblingsCenter, siblingsNum } = rowData[i]
  577. obj.y -= siblingsNum
  578. // console.log(siblingsCenter)
  579. if (siblingsCenter === 'center') {
  580. obj.y -= (customFontSize - fontSize) / 2
  581. // console.log(obj.y)
  582. } else if (siblingsCenter === 'bottom') {
  583. obj.y -= customFontSize - fontSize
  584. }
  585. }
  586. obj.fontColor = color
  587. obj.lineColor = color
  588. obj.tw = tw
  589. obj.ty = ty
  590. obj.tx = tx
  591. obj.ey = obj.y + customFontSize
  592. obj.fontSize = customFontSize
  593. obj.font = font
  594. text && textArr.push(obj)
  595. }
  596. return textArr
  597. }
  598. }
  599. /**
  600. * 睡眠线程
  601. * @param { Number } time 睡眠时间
  602. */
  603. export function sleep(time = 300) {
  604. return new Promise(resolve => {
  605. setTimeout(() => {
  606. resolve(true)
  607. }, time)
  608. })
  609. }