draw.js 17 KB

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