draw.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. import { toPx, isNumber, getImageInfo } from './utils'
  2. import { GD } from './gradient'
  3. import QR from './qrcode'
  4. export class Draw {
  5. constructor(context, canvas, use2dCanvas = false, isH5PathToBase64 = false, sleep) {
  6. this.ctx = context
  7. this.canvas = canvas || null
  8. this.use2dCanvas = use2dCanvas
  9. this.isH5PathToBase64 = isH5PathToBase64
  10. this.sleep = sleep
  11. }
  12. roundRect(x, y, w, h, r, fill = false, stroke = false, ) {
  13. if (r < 0) return
  14. const {ctx} = this
  15. ctx.beginPath()
  16. if(!r) {
  17. ctx.rect(x, y, w, h)
  18. } else if(typeof r === 'number' && [0,1,-1].includes(w - r * 2) && [0, 1, -1].includes(h - r * 2)) {
  19. ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 2)
  20. } else {
  21. let {
  22. borderTopLeftRadius: tl = r || 0,
  23. borderTopRightRadius: tr = r || 0,
  24. borderBottomRightRadius: br = r || 0,
  25. borderBottomLeftRadius: bl = r || 0
  26. } = r || {r,r,r,r}
  27. // 右下角
  28. ctx.arc(x + w - br, y + h - br, br, 0, Math.PI * 0.5)
  29. ctx.lineTo(x + bl, y + h)
  30. // 左下角
  31. ctx.arc(x + bl, y + h - bl, bl, Math.PI * 0.5, Math.PI)
  32. ctx.lineTo(x, y + tl)
  33. // 左上角
  34. ctx.arc(x + tl, y + tl, tl, Math.PI, Math.PI * 1.5)
  35. ctx.lineTo(x + w - tr, y)
  36. // 右上角
  37. ctx.arc(x + w - tr, y + tr, tr, Math.PI * 1.5, Math.PI * 2)
  38. ctx.lineTo(x + w, y + h - br)
  39. }
  40. ctx.closePath()
  41. if (stroke) ctx.stroke()
  42. if (fill) ctx.fill()
  43. }
  44. setTransform(box, {transform}) {
  45. const {ctx} = this
  46. let {
  47. scaleX = 1,
  48. scaleY = 1,
  49. translateX = 0,
  50. translateY = 0,
  51. rotate = 0,
  52. skewX = 0,
  53. skewY = 0
  54. } = transform || {}
  55. let {
  56. left: x,
  57. top: y,
  58. width: w,
  59. height: h
  60. } = box
  61. translateX = toPx(translateX, w) || 0
  62. translateY = toPx(translateY, h) || 0
  63. ctx.scale(scaleX, scaleY)
  64. ctx.translate(
  65. w * (scaleX > 0 ? 1 : -1) / 2 + (x + translateX) / scaleX,
  66. h * (scaleY > 0 ? 1 : -1) / 2 + (y + translateY) / scaleY)
  67. if(rotate) {
  68. ctx.rotate(rotate * Math.PI / 180)
  69. }
  70. if(skewX || skewY) {
  71. ctx.transform(1, Math.tan(skewY * Math.PI/180), Math.tan(skewX * Math.PI/180), 1 , 0, 0)
  72. }
  73. }
  74. setBackground(bg , w, h) {
  75. const {ctx} = this
  76. if (!bg) {
  77. // #ifndef MP-TOUTIAO || MP-BAIDU
  78. ctx.setFillStyle('transparent')
  79. // #endif
  80. // #ifdef MP-TOUTIAO || MP-BAIDU
  81. ctx.setFillStyle('rgba(0,0,0,0)')
  82. // #endif
  83. } else if(GD.isGradient(bg)) {
  84. GD.doGradient(bg, w, h, ctx);
  85. } else {
  86. ctx.setFillStyle(bg)
  87. }
  88. }
  89. setShadow({boxShadow: bs = []}) {
  90. const {ctx} = this
  91. if (bs.length) {
  92. const [x, y, b, c] = bs
  93. ctx.setShadow(x, y, b, c)
  94. }
  95. }
  96. setBorder(box, style) {
  97. const {ctx} = this
  98. let {
  99. left: x,
  100. top: y,
  101. width: w,
  102. height: h
  103. } = box
  104. const {border, borderBottom, borderTop, borderRight, borderLeft, borderRadius: r, opacity = 1} = style;
  105. const {
  106. borderWidth : bw = 0,
  107. borderStyle : bs,
  108. borderColor : bc,
  109. } = border || {}
  110. const {
  111. borderBottomWidth : bbw = bw,
  112. borderBottomStyle : bbs = bs,
  113. borderBottomColor : bbc= bc,
  114. } = borderBottom || {}
  115. const {
  116. borderTopWidth: btw = bw,
  117. borderTopStyle: bts = bs,
  118. borderTopColor: btc = bc,
  119. } = borderTop || {}
  120. const {
  121. borderRightWidth: brw = bw,
  122. borderRightStyle: brs = bs,
  123. borderRightColor: brc = bc,
  124. } = borderRight || {}
  125. const {
  126. borderLeftWidth: blw = bw,
  127. borderLeftStyle: bls = bs,
  128. borderLeftColor: blc = bc,
  129. } = borderLeft || {}
  130. let {
  131. borderTopLeftRadius: tl = r || 0,
  132. borderTopRightRadius: tr = r || 0,
  133. borderBottomRightRadius: br = r || 0,
  134. borderBottomLeftRadius: bl = r || 0
  135. } = r || {r,r,r,r}
  136. if(!borderBottom && !borderLeft && !borderTop && !borderRight && !border) return;
  137. const _borderType = (w, s, c) => {
  138. // #ifndef APP-NVUE
  139. if (s == 'dashed') {
  140. // #ifdef MP
  141. ctx.setLineDash([Math.ceil(w * 4 / 3), Math.ceil(w * 4 / 3)])
  142. // #endif
  143. // #ifndef MP
  144. ctx.setLineDash([Math.ceil(w * 6), Math.ceil(w * 6)])
  145. // #endif
  146. } else if (s == 'dotted') {
  147. ctx.setLineDash([w, w])
  148. }
  149. // #endif
  150. ctx.setStrokeStyle(c)
  151. }
  152. const _setBorder = (x1, y1, x2, y2, x3, y3, r1, r2, p1, p2, p3, bw, bs, bc) => {
  153. ctx.save()
  154. this.setOpacity(style)
  155. this.setTransform(box, style)
  156. ctx.setLineWidth(bw)
  157. _borderType(bw, bs, bc)
  158. ctx.beginPath()
  159. ctx.arc(x1, y1, r1, Math.PI * p1, Math.PI * p2)
  160. ctx.lineTo(x2, y2)
  161. ctx.arc(x3, y3, r2, Math.PI * p2, Math.PI * p3)
  162. ctx.stroke()
  163. ctx.restore()
  164. }
  165. if(border) {
  166. ctx.save()
  167. this.setOpacity(style)
  168. this.setTransform(box, style)
  169. ctx.setLineWidth(bw)
  170. _borderType(bw, bs, bc)
  171. this.roundRect(-w/2, -h/2, w, h, r, false, bc ? true : false)
  172. ctx.restore()
  173. }
  174. x = -w/2
  175. y = -h/2
  176. if(borderBottom) {
  177. _setBorder(x + w - br, y + h - br, x + bl, y + h, x + bl, y + h - bl, br, bl, 0.25, 0.5, 0.75, bbw, bbs, bbc)
  178. }
  179. if(borderLeft) {
  180. // 左下角
  181. _setBorder(x + bl, y + h - bl, x, y + tl, x + tl, y + tl, bl, tl, 0.75, 1, 1.25, blw, bls, blc)
  182. }
  183. if(borderTop) {
  184. // 左上角
  185. _setBorder(x + tl, y + tl, x + w - tr, y, x + w - tr, y + tr, tl, tr, 1.25, 1.5, 1.75, btw, bts, btc)
  186. }
  187. if(borderRight) {
  188. // 右上角
  189. _setBorder(x + w - tr, y + tr, x + w, y + h - br, x + w - br, y + h - br, tr, br, 1.75, 2, 0.25, btw, bts, btc)
  190. }
  191. }
  192. setOpacity({opacity = 1}) {
  193. this.ctx.setGlobalAlpha(opacity)
  194. }
  195. drawView(box, style) {
  196. const {ctx} = this
  197. const {
  198. left: x,
  199. top: y,
  200. width: w,
  201. height: h
  202. } = box
  203. let {
  204. borderRadius = 0,
  205. border,
  206. borderTop,
  207. borderBottom,
  208. borderLeft,
  209. borderRight,
  210. color = '#000000',
  211. backgroundColor: bg,
  212. rotate,
  213. shadow
  214. } = style || {}
  215. ctx.save()
  216. this.setOpacity(style)
  217. this.setTransform(box, style)
  218. this.setShadow(style)
  219. this.setBackground(bg, w, h)
  220. this.roundRect(-w/2, -h/2, w, h, borderRadius, true, false)
  221. ctx.restore()
  222. this.setBorder(box, style)
  223. }
  224. async drawImage(img, box = {}, style = {}, custom = true) {
  225. await new Promise(async (resolve, reject) => {
  226. const {ctx} = this
  227. const canvas = this.canvas
  228. let {
  229. borderRadius = 0,
  230. mode,
  231. padding = {},
  232. backgroundColor: bg,
  233. } = style
  234. const {paddingTop: pt = 0, paddingLeft: pl= 0, paddingRight: pr= 0, paddingBottom: pb = 0} = padding
  235. let {
  236. left: x,
  237. top: y,
  238. width: w,
  239. height: h
  240. } = box
  241. ctx.save()
  242. if(!custom) {
  243. this.setOpacity(style)
  244. this.setTransform(box, style)
  245. if(bg) {
  246. this.setBackground(bg, w, h)
  247. }
  248. this.setShadow(style)
  249. x = -w/2
  250. y = -h/2
  251. this.roundRect(x, y, w, h, borderRadius, borderRadius ? true : false, false)
  252. }
  253. ctx.clip()
  254. const _modeImage = (img) => {
  255. x += pl
  256. y += pt
  257. w = w - pl - pr
  258. h = h - pt - pb
  259. // 获得图片原始大小
  260. let rWidth = img.width
  261. let rHeight = img.height
  262. let startX = 0
  263. let startY = 0
  264. // 绘画区域比例
  265. const cp = w / h
  266. // 原图比例
  267. const op = rWidth / rHeight
  268. if(!img.width) {
  269. mode = 'scaleToFill'
  270. }
  271. switch(mode) {
  272. case 'scaleToFill':
  273. ctx.drawImage(img.src, x, y, w, h);
  274. break;
  275. case 'aspectFit':
  276. if(cp >= op) {
  277. rWidth = h * op;
  278. rHeight = h
  279. startX = x + Math.round(w - rWidth) / 2
  280. startY = y
  281. } else {
  282. rWidth = w
  283. rHeight = w / op;
  284. startX = x
  285. startY = y + Math.round(h - rHeight) / 2
  286. }
  287. ctx.drawImage(img.src, startX, startY, rWidth, rHeight);
  288. break;
  289. case 'aspectFill':
  290. if (cp >= op) {
  291. rHeight = rWidth / cp;
  292. // startY = Math.round((h - rHeight) / 2)
  293. } else {
  294. rWidth = rHeight * cp;
  295. startX = Math.round(((img.width || w) - rWidth) / 2)
  296. }
  297. // 百度小程序 开发工具 顺序有问题 暂不知晓真机
  298. // #ifdef MP-BAIDU
  299. ctx.drawImage(img.src, x, y, w, h, startX, startY, rWidth, rHeight)
  300. // #endif
  301. // #ifndef MP-BAIDU
  302. ctx.drawImage(img.src, startX, startY, rWidth, rHeight, x, y, w, h)
  303. // #endif
  304. break;
  305. default:
  306. ctx.drawImage(img.src, x, y, w, h);
  307. }
  308. }
  309. const _restore = () => {
  310. ctx.restore()
  311. this.setBorder(box, style)
  312. setTimeout(() => {
  313. resolve(true)
  314. }, this.sleep)
  315. }
  316. const _drawImage = (img) => {
  317. if (this.use2dCanvas) {
  318. const Image = canvas.createImage()
  319. Image.onload = () => {
  320. img.src = Image
  321. _modeImage(img)
  322. _restore()
  323. }
  324. Image.onerror = () => {
  325. console.error(`createImage fail: ${JSON.stringify(img)}`)
  326. reject(new Error(`createImage fail: ${JSON.stringify(img)}`))
  327. }
  328. Image.src = img.src
  329. } else {
  330. _modeImage(img)
  331. _restore()
  332. }
  333. }
  334. if(typeof img === 'string') {
  335. const {path: src, width, height} = await getImageInfo(img, this.isH5PathToBase64)
  336. _drawImage({src, width, height})
  337. } else {
  338. _drawImage(img)
  339. }
  340. })
  341. }
  342. drawText(text, box, style, rules) {
  343. const {ctx} = this
  344. let {
  345. left: x,
  346. top: y,
  347. width: w,
  348. height: h,
  349. offsetLeft: ol = 0
  350. } = box
  351. let {
  352. color = '#000000',
  353. lineHeight = '1.4em',
  354. fontSize = 14,
  355. fontWeight,
  356. fontFamily,
  357. textStyle,
  358. textAlign = 'left',
  359. verticalAlign: va = 'top',
  360. backgroundColor: bg,
  361. maxLines,
  362. display,
  363. padding = {},
  364. borderRadius = 0,
  365. textDecoration: td
  366. } = style
  367. const {paddingTop: pt = 0, paddingLeft: pl = 0} = padding
  368. lineHeight = toPx(lineHeight, fontSize)
  369. if (!text) return
  370. ctx.save()
  371. this.setOpacity(style)
  372. this.setTransform(box, style)
  373. x = -w/2
  374. y = -h/2
  375. ctx.setTextBaseline(va)
  376. ctx.setFonts({fontFamily, fontSize, fontWeight, textStyle})
  377. ctx.setTextAlign(textAlign)
  378. if(bg) {
  379. this.setBackground(bg, w, h)
  380. this.roundRect(x, y, w, h, borderRadius, 1, 0)
  381. }
  382. if(display && display.includes('lock')) {
  383. x += pl
  384. y += pt
  385. }
  386. this.setShadow(style)
  387. ctx.setFillStyle(color)
  388. let rulesObj = {};
  389. if(rules) {
  390. if (rules.word.length > 0) {
  391. for (let i = 0; i < rules.word.length; i++) {
  392. let startIndex = 0,
  393. index;
  394. while ((index = text.indexOf(rules.word[i], startIndex)) > -1) {
  395. rulesObj[index] = {
  396. reset: true
  397. };
  398. for (let j = 0; j < rules.word[i].length; j++) {
  399. rulesObj[index + j] = {
  400. reset: true
  401. };
  402. }
  403. startIndex = index + 1;
  404. }
  405. }
  406. }
  407. }
  408. // 水平布局
  409. switch (textAlign) {
  410. case 'left':
  411. break
  412. case 'center':
  413. x += 0.5 * w
  414. break
  415. case 'right':
  416. x += w
  417. break
  418. default:
  419. break
  420. }
  421. const textWidth = ctx.measureText(text, fontSize).width
  422. const actualHeight = Math.ceil(textWidth / w) * lineHeight
  423. let paddingTop = Math.ceil((h - actualHeight) / 2)
  424. if (paddingTop < 0) paddingTop = 0
  425. // 垂直布局
  426. switch (va) {
  427. case 'top':
  428. break
  429. case 'middle':
  430. y += fontSize / 2
  431. break
  432. case 'bottom':
  433. y += fontSize
  434. break
  435. default:
  436. break
  437. }
  438. // 绘线
  439. const _drawLine = (x, y, textWidth) => {
  440. const { system } = uni.getSystemInfoSync()
  441. if(/win|mac/.test(system)){
  442. y += (fontSize / 3)
  443. }
  444. // 垂直布局
  445. switch (va) {
  446. case 'top':
  447. break
  448. case 'middle':
  449. y -= fontSize / 2
  450. break
  451. case 'bottom':
  452. y -= fontSize
  453. break
  454. default:
  455. break
  456. }
  457. let to = x
  458. switch (textAlign) {
  459. case 'left':
  460. x = x
  461. to+= textWidth
  462. break
  463. case 'center':
  464. x = x - textWidth / 2
  465. to = x + textWidth
  466. break
  467. case 'right':
  468. to = x
  469. x = x - textWidth
  470. break
  471. default:
  472. break
  473. }
  474. if(td) {
  475. ctx.setLineWidth(fontSize / 13);
  476. ctx.beginPath();
  477. if (/\bunderline\b/.test(td)) {
  478. y -= inlinePaddingTop * 0.8
  479. ctx.moveTo(x, y);
  480. ctx.lineTo(to, y);
  481. }
  482. if (/\boverline\b/.test(td)) {
  483. y += inlinePaddingTop
  484. ctx.moveTo(x, y - lineHeight);
  485. ctx.lineTo(to, y - lineHeight);
  486. }
  487. if (/\bline-through\b/.test(td)) {
  488. ctx.moveTo(x , y - lineHeight / 2 );
  489. ctx.lineTo(to, y - lineHeight /2 );
  490. }
  491. ctx.closePath();
  492. ctx.setStrokeStyle(color);
  493. ctx.stroke();
  494. }
  495. }
  496. const _reset = (text, x, y) => {
  497. const rs = Object.keys(rulesObj)
  498. for (let i = 0; i < rs.length; i++) {
  499. const item = rulesObj[rs[i]]
  500. // ctx.globalCompositeOperation = "destination-out";
  501. ctx.save();
  502. ctx.setFillStyle(rules.color);
  503. if(item.char) {
  504. ctx.fillText(item.char, item.x , item.y)
  505. }
  506. ctx.restore();
  507. }
  508. }
  509. const _setText = (isReset, char) => {
  510. if(isReset) {
  511. const t1 = Math.round(ctx.measureText('\u0020', fontSize).width)
  512. const t2 = Math.round(ctx.measureText('\u3000', fontSize).width)
  513. const width = Math.round(ctx.measureText(char, fontSize).width)
  514. let _char = ''
  515. let _num = 1
  516. if(width == t2){
  517. _char ='\u3000'
  518. _num = 1
  519. } else {
  520. _char = '\u0020'
  521. _num = Math.ceil(width / t1)
  522. }
  523. return {char: new Array(_num).fill(_char).join(''), width}
  524. } else {
  525. return {char}
  526. }
  527. }
  528. const _setRulesObj = (text, index, x, y) => {
  529. rulesObj[index].x = x
  530. rulesObj[index].y = y
  531. rulesObj[index].char = text
  532. }
  533. const _setRules = (x, rs, text, textWidth, {startIndex = 0, endIndex}) => {
  534. let clonetext = text
  535. if(/·/.test(text)) {
  536. clonetext = clonetext.replace(/·/g, '.')
  537. textWidth = ctx.measureText(clonetext, fontSize).width
  538. }
  539. let _text = text.split('')
  540. let _x = x
  541. let first = true
  542. for (let i = 0; i < rs.length; i++) {
  543. const index = rs[i]
  544. const key = index - startIndex
  545. const t = _text[key]
  546. if(t) {
  547. let {char, width} = _setText(rulesObj[index], t)
  548. _text[key] = char
  549. if(first) {
  550. first = false
  551. let dot = 0
  552. let dot2 = 0
  553. let num = 0
  554. if(textAlign == 'center') {
  555. _x = x - 0.5 * (textWidth - width - (dot2 - dot) * num)
  556. }
  557. if(textAlign == 'right') {
  558. _x = x - textWidth + width + (dot2 - dot) * num
  559. }
  560. }
  561. _setRulesObj(t, index, _x + ctx.measureText(clonetext.substring(0, key), fontSize).width, y + inlinePaddingTop)
  562. } else {
  563. continue
  564. }
  565. }
  566. return _text
  567. }
  568. const inlinePaddingTop = Math.ceil((lineHeight - fontSize) / 2)
  569. // 不超过一行
  570. if (textWidth + ol <= w && !text.includes('\n')) {
  571. x = x + ol
  572. const rs = Object.keys(rulesObj)
  573. let _text = ''
  574. if(rs) {
  575. _text = _setRules(x, rs, text, textWidth, {})
  576. _reset()
  577. }
  578. ctx.fillText(_text.join(''), x, y + inlinePaddingTop)
  579. y += lineHeight
  580. _drawLine(x, y, textWidth)
  581. ctx.restore()
  582. this.setBorder(box, style)
  583. return
  584. }
  585. // 多行文本
  586. const chars = text.split('')
  587. const _y = y
  588. let _x = x
  589. // 逐行绘制
  590. let line = ''
  591. let lineIndex = 0
  592. let startIndex = 0
  593. for(let index = 0 ; index <= chars.length; index++){
  594. let ch = chars[index] || ''
  595. const isLine = ch === '\n'
  596. const isRight = ch == ''// index == chars.length
  597. ch = isLine ? '' : ch;
  598. let textline = line + ch
  599. let textWidth = ctx.measureText(textline, fontSize).width
  600. // 绘制行数大于最大行数,则直接跳出循环
  601. if (lineIndex >= maxLines) {
  602. break;
  603. }
  604. if(lineIndex == 0) {
  605. textWidth = textWidth + ol
  606. _x = x + ol
  607. } else {
  608. textWidth = textWidth
  609. _x = x
  610. }
  611. if (textWidth > w || isLine || isRight) {
  612. let endIndex = index
  613. lineIndex++
  614. line = isRight && textWidth <= w ? textline : line
  615. if(lineIndex === maxLines && textWidth > w) {
  616. while( ctx.measureText(`${line}...`, fontSize).width > w) {
  617. if (line.length <= 1) {
  618. // 如果只有一个字符时,直接跳出循环
  619. break;
  620. }
  621. line = line.substring(0, line.length - 1);
  622. }
  623. line += '...'
  624. }
  625. const rs = Object.keys(rulesObj)
  626. let _text = ''
  627. if(rs) {
  628. _text = _setRules(x, rs, line, textWidth, {startIndex, endIndex})
  629. _reset()
  630. }
  631. ctx.fillText(_text.join(''), _x, y + inlinePaddingTop)
  632. y += lineHeight
  633. _drawLine(_x, y, textWidth)
  634. line = ch
  635. startIndex = endIndex + (isLine ? 1 : 0)
  636. if ((y + lineHeight) > (_y + h)) break
  637. } else {
  638. line = textline
  639. }
  640. }
  641. // const rs = Object.keys(rulesObj)
  642. // if(rs) {
  643. // _reset()
  644. // }
  645. ctx.restore()
  646. this.setBorder(box, style)
  647. }
  648. async drawNode(element) {
  649. const {
  650. layoutBox,
  651. computedStyle,
  652. name,
  653. rules
  654. } = element
  655. const {
  656. src,
  657. text
  658. } = element.attributes
  659. if (name === 'view') {
  660. this.drawView(layoutBox, computedStyle)
  661. } else if (name === 'image' && src) {
  662. await this.drawImage(element.attributes, layoutBox, computedStyle, false)
  663. } else if (name === 'text') {
  664. this.drawText(text, layoutBox, computedStyle, rules)
  665. } else if (name === 'qrcode') {
  666. QR.api.draw(text, this, layoutBox, computedStyle)
  667. }
  668. if (!element.children) return
  669. const childs = Object.values ? Object.values(element.children) : Object.keys(element.children).map((key) => element.children[key]);
  670. for (const child of childs) {
  671. await this.drawNode(child)
  672. }
  673. }
  674. }