index.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. (function webpackUniversalModuleDefinition(root, factory) {
  2. if(typeof exports === 'object' && typeof module === 'object')
  3. module.exports = factory();
  4. else if(typeof define === 'function' && define.amd)
  5. define([], factory);
  6. else {
  7. var a = factory();
  8. for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
  9. }
  10. })(window, function() {
  11. return /******/ (function(modules) { // webpackBootstrap
  12. /******/ // The module cache
  13. /******/ var installedModules = {};
  14. /******/
  15. /******/ // The require function
  16. /******/ function __webpack_require__(moduleId) {
  17. /******/
  18. /******/ // Check if module is in cache
  19. /******/ if(installedModules[moduleId]) {
  20. /******/ return installedModules[moduleId].exports;
  21. /******/ }
  22. /******/ // Create a new module (and put it into the cache)
  23. /******/ var module = installedModules[moduleId] = {
  24. /******/ i: moduleId,
  25. /******/ l: false,
  26. /******/ exports: {}
  27. /******/ };
  28. /******/
  29. /******/ // Execute the module function
  30. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  31. /******/
  32. /******/ // Flag the module as loaded
  33. /******/ module.l = true;
  34. /******/
  35. /******/ // Return the exports of the module
  36. /******/ return module.exports;
  37. /******/ }
  38. /******/
  39. /******/
  40. /******/ // expose the modules object (__webpack_modules__)
  41. /******/ __webpack_require__.m = modules;
  42. /******/
  43. /******/ // expose the module cache
  44. /******/ __webpack_require__.c = installedModules;
  45. /******/
  46. /******/ // define getter function for harmony exports
  47. /******/ __webpack_require__.d = function(exports, name, getter) {
  48. /******/ if(!__webpack_require__.o(exports, name)) {
  49. /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
  50. /******/ }
  51. /******/ };
  52. /******/
  53. /******/ // define __esModule on exports
  54. /******/ __webpack_require__.r = function(exports) {
  55. /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  56. /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  57. /******/ }
  58. /******/ Object.defineProperty(exports, '__esModule', { value: true });
  59. /******/ };
  60. /******/
  61. /******/ // create a fake namespace object
  62. /******/ // mode & 1: value is a module id, require it
  63. /******/ // mode & 2: merge all properties of value into the ns
  64. /******/ // mode & 4: return value when already ns object
  65. /******/ // mode & 8|1: behave like require
  66. /******/ __webpack_require__.t = function(value, mode) {
  67. /******/ if(mode & 1) value = __webpack_require__(value);
  68. /******/ if(mode & 8) return value;
  69. /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
  70. /******/ var ns = Object.create(null);
  71. /******/ __webpack_require__.r(ns);
  72. /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
  73. /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
  74. /******/ return ns;
  75. /******/ };
  76. /******/
  77. /******/ // getDefaultExport function for compatibility with non-harmony modules
  78. /******/ __webpack_require__.n = function(module) {
  79. /******/ var getter = module && module.__esModule ?
  80. /******/ function getDefault() { return module['default']; } :
  81. /******/ function getModuleExports() { return module; };
  82. /******/ __webpack_require__.d(getter, 'a', getter);
  83. /******/ return getter;
  84. /******/ };
  85. /******/
  86. /******/ // Object.prototype.hasOwnProperty.call
  87. /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  88. /******/
  89. /******/ // __webpack_public_path__
  90. /******/ __webpack_require__.p = "";
  91. /******/
  92. /******/
  93. /******/ // Load entry module and return exports
  94. /******/ return __webpack_require__(__webpack_require__.s = 1);
  95. /******/ })
  96. /************************************************************************/
  97. /******/ ([
  98. /* 0 */
  99. /***/ (function(module, exports) {
  100. const hex = (color) => {
  101. let result = null
  102. if (/^#/.test(color) && (color.length === 7 || color.length === 9)) {
  103. return color
  104. // eslint-disable-next-line no-cond-assign
  105. } else if ((result = /^(rgb|rgba)\((.+)\)/.exec(color)) !== null) {
  106. return '#' + result[2].split(',').map((part, index) => {
  107. part = part.trim()
  108. part = index === 3 ? Math.floor(parseFloat(part) * 255) : parseInt(part, 10)
  109. part = part.toString(16)
  110. if (part.length === 1) {
  111. part = '0' + part
  112. }
  113. return part
  114. }).join('')
  115. } else {
  116. return '#00000000'
  117. }
  118. }
  119. const splitLineToCamelCase = (str) => str.split('-').map((part, index) => {
  120. if (index === 0) {
  121. return part
  122. }
  123. return part[0].toUpperCase() + part.slice(1)
  124. }).join('')
  125. const compareVersion = (v1, v2) => {
  126. v1 = v1.split('.')
  127. v2 = v2.split('.')
  128. const len = Math.max(v1.length, v2.length)
  129. while (v1.length < len) {
  130. v1.push('0')
  131. }
  132. while (v2.length < len) {
  133. v2.push('0')
  134. }
  135. for (let i = 0; i < len; i++) {
  136. const num1 = parseInt(v1[i], 10)
  137. const num2 = parseInt(v2[i], 10)
  138. if (num1 > num2) {
  139. return 1
  140. } else if (num1 < num2) {
  141. return -1
  142. }
  143. }
  144. return 0
  145. }
  146. module.exports = {
  147. hex,
  148. splitLineToCamelCase,
  149. compareVersion
  150. }
  151. /***/ }),
  152. /* 1 */
  153. /***/ (function(module, exports, __webpack_require__) {
  154. const xmlParse = __webpack_require__(2)
  155. const {Widget} = __webpack_require__(3)
  156. const {Draw} = __webpack_require__(5)
  157. const {compareVersion} = __webpack_require__(0)
  158. const canvasId = 'weui-canvas'
  159. Component({
  160. properties: {
  161. width: {
  162. type: Number,
  163. value: 400
  164. },
  165. height: {
  166. type: Number,
  167. value: 300
  168. }
  169. },
  170. data: {
  171. use2dCanvas: false, // 2.9.2 后可用canvas 2d 接口
  172. },
  173. lifetimes: {
  174. attached() {
  175. const {SDKVersion, pixelRatio: dpr} = wx.getSystemInfoSync()
  176. const use2dCanvas = compareVersion(SDKVersion, '2.9.2') >= 0
  177. this.dpr = dpr
  178. this.setData({use2dCanvas}, () => {
  179. if (use2dCanvas) {
  180. const query = this.createSelectorQuery()
  181. query.select(`#${canvasId}`)
  182. .fields({node: true, size: true})
  183. .exec(res => {
  184. const canvas = res[0].node
  185. const ctx = canvas.getContext('2d')
  186. canvas.width = res[0].width * dpr
  187. canvas.height = res[0].height * dpr
  188. ctx.scale(dpr, dpr)
  189. this.ctx = ctx
  190. this.canvas = canvas
  191. })
  192. } else {
  193. this.ctx = wx.createCanvasContext(canvasId, this)
  194. }
  195. })
  196. }
  197. },
  198. methods: {
  199. async renderToCanvas(args) {
  200. const {wxml, style} = args
  201. const ctx = this.ctx
  202. const canvas = this.canvas
  203. const use2dCanvas = this.data.use2dCanvas
  204. if (use2dCanvas && !canvas) {
  205. return Promise.reject(new Error('renderToCanvas: fail canvas has not been created'))
  206. }
  207. ctx.clearRect(0, 0, this.data.width, this.data.height)
  208. const {root: xom} = xmlParse(wxml)
  209. const widget = new Widget(xom, style)
  210. const container = widget.init()
  211. this.boundary = {
  212. top: container.layoutBox.top,
  213. left: container.layoutBox.left,
  214. width: container.computedStyle.width,
  215. height: container.computedStyle.height,
  216. }
  217. const draw = new Draw(ctx, canvas, use2dCanvas)
  218. await draw.drawNode(container)
  219. if (!use2dCanvas) {
  220. await this.canvasDraw(ctx)
  221. }
  222. return Promise.resolve(container)
  223. },
  224. canvasDraw(ctx, reserve) {
  225. return new Promise(resolve => {
  226. ctx.draw(reserve, () => {
  227. resolve()
  228. })
  229. })
  230. },
  231. canvasToTempFilePath(args = {}) {
  232. const use2dCanvas = this.data.use2dCanvas
  233. return new Promise((resolve, reject) => {
  234. const {
  235. top, left, width, height
  236. } = this.boundary
  237. const copyArgs = {
  238. x: left,
  239. y: top,
  240. width,
  241. height,
  242. destWidth: width * this.dpr,
  243. destHeight: height * this.dpr,
  244. canvasId,
  245. fileType: args.fileType || 'png',
  246. quality: args.quality || 1,
  247. success: resolve,
  248. fail: reject
  249. }
  250. if (use2dCanvas) {
  251. delete copyArgs.canvasId
  252. copyArgs.canvas = this.canvas
  253. }
  254. wx.canvasToTempFilePath(copyArgs, this)
  255. })
  256. }
  257. }
  258. })
  259. /***/ }),
  260. /* 2 */
  261. /***/ (function(module, exports) {
  262. /**
  263. * Module dependencies.
  264. */
  265. /**
  266. * Expose `parse`.
  267. */
  268. /**
  269. * Parse the given string of `xml`.
  270. *
  271. * @param {String} xml
  272. * @return {Object}
  273. * @api public
  274. */
  275. function parse(xml) {
  276. xml = xml.trim()
  277. // strip comments
  278. xml = xml.replace(/<!--[\s\S]*?-->/g, '')
  279. return document()
  280. /**
  281. * XML document.
  282. */
  283. function document() {
  284. return {
  285. declaration: declaration(),
  286. root: tag()
  287. }
  288. }
  289. /**
  290. * Declaration.
  291. */
  292. function declaration() {
  293. const m = match(/^<\?xml\s*/)
  294. if (!m) return
  295. // tag
  296. const node = {
  297. attributes: {}
  298. }
  299. // attributes
  300. while (!(eos() || is('?>'))) {
  301. const attr = attribute()
  302. if (!attr) return node
  303. node.attributes[attr.name] = attr.value
  304. }
  305. match(/\?>\s*/)
  306. return node
  307. }
  308. /**
  309. * Tag.
  310. */
  311. function tag() {
  312. const m = match(/^<([\w-:.]+)\s*/)
  313. if (!m) return
  314. // name
  315. const node = {
  316. name: m[1],
  317. attributes: {},
  318. children: []
  319. }
  320. // attributes
  321. while (!(eos() || is('>') || is('?>') || is('/>'))) {
  322. const attr = attribute()
  323. if (!attr) return node
  324. node.attributes[attr.name] = attr.value
  325. }
  326. // self closing tag
  327. if (match(/^\s*\/>\s*/)) {
  328. return node
  329. }
  330. match(/\??>\s*/)
  331. // content
  332. node.content = content()
  333. // children
  334. let child
  335. while (child = tag()) {
  336. node.children.push(child)
  337. }
  338. // closing
  339. match(/^<\/[\w-:.]+>\s*/)
  340. return node
  341. }
  342. /**
  343. * Text content.
  344. */
  345. function content() {
  346. const m = match(/^([^<]*)/)
  347. if (m) return m[1]
  348. return ''
  349. }
  350. /**
  351. * Attribute.
  352. */
  353. function attribute() {
  354. const m = match(/([\w:-]+)\s*=\s*("[^"]*"|'[^']*'|\w+)\s*/)
  355. if (!m) return
  356. return {name: m[1], value: strip(m[2])}
  357. }
  358. /**
  359. * Strip quotes from `val`.
  360. */
  361. function strip(val) {
  362. return val.replace(/^['"]|['"]$/g, '')
  363. }
  364. /**
  365. * Match `re` and advance the string.
  366. */
  367. function match(re) {
  368. const m = xml.match(re)
  369. if (!m) return
  370. xml = xml.slice(m[0].length)
  371. return m
  372. }
  373. /**
  374. * End-of-source.
  375. */
  376. function eos() {
  377. return xml.length == 0
  378. }
  379. /**
  380. * Check for `prefix`.
  381. */
  382. function is(prefix) {
  383. return xml.indexOf(prefix) == 0
  384. }
  385. }
  386. module.exports = parse
  387. /***/ }),
  388. /* 3 */
  389. /***/ (function(module, exports, __webpack_require__) {
  390. const Block = __webpack_require__(4)
  391. const {splitLineToCamelCase} = __webpack_require__(0)
  392. class Element extends Block {
  393. constructor(prop) {
  394. super(prop.style)
  395. this.name = prop.name
  396. this.attributes = prop.attributes
  397. }
  398. }
  399. class Widget {
  400. constructor(xom, style) {
  401. this.xom = xom
  402. this.style = style
  403. this.inheritProps = ['fontSize', 'lineHeight', 'textAlign', 'verticalAlign', 'color']
  404. }
  405. init() {
  406. this.container = this.create(this.xom)
  407. this.container.layout()
  408. this.inheritStyle(this.container)
  409. return this.container
  410. }
  411. // 继承父节点的样式
  412. inheritStyle(node) {
  413. const parent = node.parent || null
  414. const children = node.children || {}
  415. const computedStyle = node.computedStyle
  416. if (parent) {
  417. this.inheritProps.forEach(prop => {
  418. computedStyle[prop] = computedStyle[prop] || parent.computedStyle[prop]
  419. })
  420. }
  421. Object.values(children).forEach(child => {
  422. this.inheritStyle(child)
  423. })
  424. }
  425. create(node) {
  426. let classNames = (node.attributes.class || '').split(' ')
  427. classNames = classNames.map(item => splitLineToCamelCase(item.trim()))
  428. const style = {}
  429. classNames.forEach(item => {
  430. Object.assign(style, this.style[item] || {})
  431. })
  432. const args = {name: node.name, style}
  433. const attrs = Object.keys(node.attributes)
  434. const attributes = {}
  435. for (const attr of attrs) {
  436. const value = node.attributes[attr]
  437. const CamelAttr = splitLineToCamelCase(attr)
  438. if (value === '' || value === 'true') {
  439. attributes[CamelAttr] = true
  440. } else if (value === 'false') {
  441. attributes[CamelAttr] = false
  442. } else {
  443. attributes[CamelAttr] = value
  444. }
  445. }
  446. attributes.text = node.content
  447. args.attributes = attributes
  448. const element = new Element(args)
  449. node.children.forEach(childNode => {
  450. const childElement = this.create(childNode)
  451. element.add(childElement)
  452. })
  453. return element
  454. }
  455. }
  456. module.exports = {Widget}
  457. /***/ }),
  458. /* 4 */
  459. /***/ (function(module, exports) {
  460. module.exports = require("widget-ui");
  461. /***/ }),
  462. /* 5 */
  463. /***/ (function(module, exports) {
  464. class Draw {
  465. constructor(context, canvas, use2dCanvas = false) {
  466. this.ctx = context
  467. this.canvas = canvas || null
  468. this.use2dCanvas = use2dCanvas
  469. }
  470. roundRect(x, y, w, h, r, fill = true, stroke = false) {
  471. if (r < 0) return
  472. const ctx = this.ctx
  473. ctx.beginPath()
  474. ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2)
  475. ctx.arc(x + w - r, y + r, r, Math.PI * 3 / 2, 0)
  476. ctx.arc(x + w - r, y + h - r, r, 0, Math.PI / 2)
  477. ctx.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI)
  478. ctx.lineTo(x, y + r)
  479. if (stroke) ctx.stroke()
  480. if (fill) ctx.fill()
  481. }
  482. drawView(box, style) {
  483. const ctx = this.ctx
  484. const {
  485. left: x, top: y, width: w, height: h
  486. } = box
  487. const {
  488. borderRadius = 0,
  489. borderWidth = 0,
  490. borderColor,
  491. color = '#000',
  492. backgroundColor = 'transparent',
  493. } = style
  494. ctx.save()
  495. // 外环
  496. if (borderWidth > 0) {
  497. ctx.fillStyle = borderColor || color
  498. this.roundRect(x, y, w, h, borderRadius)
  499. }
  500. // 内环
  501. ctx.fillStyle = backgroundColor
  502. const innerWidth = w - 2 * borderWidth
  503. const innerHeight = h - 2 * borderWidth
  504. const innerRadius = borderRadius - borderWidth >= 0 ? borderRadius - borderWidth : 0
  505. this.roundRect(x + borderWidth, y + borderWidth, innerWidth, innerHeight, innerRadius)
  506. ctx.restore()
  507. }
  508. async drawImage(img, box, style) {
  509. await new Promise((resolve, reject) => {
  510. const ctx = this.ctx
  511. const canvas = this.canvas
  512. const {
  513. borderRadius = 0
  514. } = style
  515. const {
  516. left: x, top: y, width: w, height: h
  517. } = box
  518. ctx.save()
  519. this.roundRect(x, y, w, h, borderRadius, false, false)
  520. ctx.clip()
  521. const _drawImage = (img) => {
  522. if (this.use2dCanvas) {
  523. const Image = canvas.createImage()
  524. Image.onload = () => {
  525. ctx.drawImage(Image, x, y, w, h)
  526. ctx.restore()
  527. resolve()
  528. }
  529. Image.onerror = () => { reject(new Error(`createImage fail: ${img}`)) }
  530. Image.src = img
  531. } else {
  532. ctx.drawImage(img, x, y, w, h)
  533. ctx.restore()
  534. resolve()
  535. }
  536. }
  537. const isTempFile = /^wxfile:\/\//.test(img)
  538. const isNetworkFile = /^https?:\/\//.test(img)
  539. if (isTempFile) {
  540. _drawImage(img)
  541. } else if (isNetworkFile) {
  542. wx.downloadFile({
  543. url: img,
  544. success(res) {
  545. if (res.statusCode === 200) {
  546. _drawImage(res.tempFilePath)
  547. } else {
  548. reject(new Error(`downloadFile:fail ${img}`))
  549. }
  550. },
  551. fail() {
  552. reject(new Error(`downloadFile:fail ${img}`))
  553. }
  554. })
  555. } else {
  556. reject(new Error(`image format error: ${img}`))
  557. }
  558. })
  559. }
  560. // eslint-disable-next-line complexity
  561. drawText(text, box, style) {
  562. const ctx = this.ctx
  563. let {
  564. left: x, top: y, width: w, height: h
  565. } = box
  566. let {
  567. color = '#000',
  568. lineHeight = '1.4em',
  569. fontSize = 14,
  570. textAlign = 'left',
  571. verticalAlign = 'top',
  572. backgroundColor = 'transparent'
  573. } = style
  574. if (typeof lineHeight === 'string') { // 2em
  575. lineHeight = Math.ceil(parseFloat(lineHeight.replace('em')) * fontSize)
  576. }
  577. if (!text || (lineHeight > h)) return
  578. ctx.save()
  579. ctx.textBaseline = 'top'
  580. ctx.font = `${fontSize}px sans-serif`
  581. ctx.textAlign = textAlign
  582. // 背景色
  583. ctx.fillStyle = backgroundColor
  584. this.roundRect(x, y, w, h, 0)
  585. // 文字颜色
  586. ctx.fillStyle = color
  587. // 水平布局
  588. switch (textAlign) {
  589. case 'left':
  590. break
  591. case 'center':
  592. x += 0.5 * w
  593. break
  594. case 'right':
  595. x += w
  596. break
  597. default: break
  598. }
  599. const textWidth = ctx.measureText(text).width
  600. const actualHeight = Math.ceil(textWidth / w) * lineHeight
  601. let paddingTop = Math.ceil((h - actualHeight) / 2)
  602. if (paddingTop < 0) paddingTop = 0
  603. // 垂直布局
  604. switch (verticalAlign) {
  605. case 'top':
  606. break
  607. case 'middle':
  608. y += paddingTop
  609. break
  610. case 'bottom':
  611. y += 2 * paddingTop
  612. break
  613. default: break
  614. }
  615. const inlinePaddingTop = Math.ceil((lineHeight - fontSize) / 2)
  616. // 不超过一行
  617. if (textWidth <= w) {
  618. ctx.fillText(text, x, y + inlinePaddingTop)
  619. return
  620. }
  621. // 多行文本
  622. const chars = text.split('')
  623. const _y = y
  624. // 逐行绘制
  625. let line = ''
  626. for (const ch of chars) {
  627. const testLine = line + ch
  628. const testWidth = ctx.measureText(testLine).width
  629. if (testWidth > w) {
  630. ctx.fillText(line, x, y + inlinePaddingTop)
  631. y += lineHeight
  632. line = ch
  633. if ((y + lineHeight) > (_y + h)) break
  634. } else {
  635. line = testLine
  636. }
  637. }
  638. // 避免溢出
  639. if ((y + lineHeight) <= (_y + h)) {
  640. ctx.fillText(line, x, y + inlinePaddingTop)
  641. }
  642. ctx.restore()
  643. }
  644. async drawNode(element) {
  645. const {layoutBox, computedStyle, name} = element
  646. const {src, text} = element.attributes
  647. if (name === 'view') {
  648. this.drawView(layoutBox, computedStyle)
  649. } else if (name === 'image') {
  650. await this.drawImage(src, layoutBox, computedStyle)
  651. } else if (name === 'text') {
  652. this.drawText(text, layoutBox, computedStyle)
  653. }
  654. const childs = Object.values(element.children)
  655. for (const child of childs) {
  656. await this.drawNode(child)
  657. }
  658. }
  659. }
  660. module.exports = {
  661. Draw
  662. }
  663. /***/ })
  664. /******/ ]);
  665. });