(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else { var a = factory(); for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; } })(window, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 1); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports) { const hex = (color) => { let result = null if (/^#/.test(color) && (color.length === 7 || color.length === 9)) { return color // eslint-disable-next-line no-cond-assign } else if ((result = /^(rgb|rgba)\((.+)\)/.exec(color)) !== null) { return '#' + result[2].split(',').map((part, index) => { part = part.trim() part = index === 3 ? Math.floor(parseFloat(part) * 255) : parseInt(part, 10) part = part.toString(16) if (part.length === 1) { part = '0' + part } return part }).join('') } else { return '#00000000' } } const splitLineToCamelCase = (str) => str.split('-').map((part, index) => { if (index === 0) { return part } return part[0].toUpperCase() + part.slice(1) }).join('') const compareVersion = (v1, v2) => { v1 = v1.split('.') v2 = v2.split('.') const len = Math.max(v1.length, v2.length) while (v1.length < len) { v1.push('0') } while (v2.length < len) { v2.push('0') } for (let i = 0; i < len; i++) { const num1 = parseInt(v1[i], 10) const num2 = parseInt(v2[i], 10) if (num1 > num2) { return 1 } else if (num1 < num2) { return -1 } } return 0 } module.exports = { hex, splitLineToCamelCase, compareVersion } /***/ }), /* 1 */ /***/ (function(module, exports, __webpack_require__) { const xmlParse = __webpack_require__(2) const {Widget} = __webpack_require__(3) const {Draw} = __webpack_require__(5) const {compareVersion} = __webpack_require__(0) const canvasId = 'weui-canvas' Component({ properties: { width: { type: Number, value: 400 }, height: { type: Number, value: 300 } }, data: { use2dCanvas: false, // 2.9.2 后可用canvas 2d 接口 }, lifetimes: { attached() { const {SDKVersion, pixelRatio: dpr} = wx.getSystemInfoSync() const use2dCanvas = compareVersion(SDKVersion, '2.9.2') >= 0 this.dpr = dpr this.setData({use2dCanvas}, () => { if (use2dCanvas) { const query = this.createSelectorQuery() query.select(`#${canvasId}`) .fields({node: true, size: true}) .exec(res => { const canvas = res[0].node const ctx = canvas.getContext('2d') canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr ctx.scale(dpr, dpr) this.ctx = ctx this.canvas = canvas }) } else { this.ctx = wx.createCanvasContext(canvasId, this) } }) } }, methods: { async renderToCanvas(args) { const {wxml, style} = args const ctx = this.ctx const canvas = this.canvas const use2dCanvas = this.data.use2dCanvas if (use2dCanvas && !canvas) { return Promise.reject(new Error('renderToCanvas: fail canvas has not been created')) } ctx.clearRect(0, 0, this.data.width, this.data.height) const {root: xom} = xmlParse(wxml) const widget = new Widget(xom, style) const container = widget.init() this.boundary = { top: container.layoutBox.top, left: container.layoutBox.left, width: container.computedStyle.width, height: container.computedStyle.height, } const draw = new Draw(ctx, canvas, use2dCanvas) await draw.drawNode(container) if (!use2dCanvas) { await this.canvasDraw(ctx) } return Promise.resolve(container) }, canvasDraw(ctx, reserve) { return new Promise(resolve => { ctx.draw(reserve, () => { resolve() }) }) }, canvasToTempFilePath(args = {}) { const use2dCanvas = this.data.use2dCanvas return new Promise((resolve, reject) => { const { top, left, width, height } = this.boundary const copyArgs = { x: left, y: top, width, height, destWidth: width * this.dpr, destHeight: height * this.dpr, canvasId, fileType: args.fileType || 'png', quality: args.quality || 1, success: resolve, fail: reject } if (use2dCanvas) { delete copyArgs.canvasId copyArgs.canvas = this.canvas } wx.canvasToTempFilePath(copyArgs, this) }) } } }) /***/ }), /* 2 */ /***/ (function(module, exports) { /** * Module dependencies. */ /** * Expose `parse`. */ /** * Parse the given string of `xml`. * * @param {String} xml * @return {Object} * @api public */ function parse(xml) { xml = xml.trim() // strip comments xml = xml.replace(//g, '') return document() /** * XML document. */ function document() { return { declaration: declaration(), root: tag() } } /** * Declaration. */ function declaration() { const m = match(/^<\?xml\s*/) if (!m) return // tag const node = { attributes: {} } // attributes while (!(eos() || is('?>'))) { const attr = attribute() if (!attr) return node node.attributes[attr.name] = attr.value } match(/\?>\s*/) return node } /** * Tag. */ function tag() { const m = match(/^<([\w-:.]+)\s*/) if (!m) return // name const node = { name: m[1], attributes: {}, children: [] } // attributes while (!(eos() || is('>') || is('?>') || is('/>'))) { const attr = attribute() if (!attr) return node node.attributes[attr.name] = attr.value } // self closing tag if (match(/^\s*\/>\s*/)) { return node } match(/\??>\s*/) // content node.content = content() // children let child while (child = tag()) { node.children.push(child) } // closing match(/^<\/[\w-:.]+>\s*/) return node } /** * Text content. */ function content() { const m = match(/^([^<]*)/) if (m) return m[1] return '' } /** * Attribute. */ function attribute() { const m = match(/([\w:-]+)\s*=\s*("[^"]*"|'[^']*'|\w+)\s*/) if (!m) return return {name: m[1], value: strip(m[2])} } /** * Strip quotes from `val`. */ function strip(val) { return val.replace(/^['"]|['"]$/g, '') } /** * Match `re` and advance the string. */ function match(re) { const m = xml.match(re) if (!m) return xml = xml.slice(m[0].length) return m } /** * End-of-source. */ function eos() { return xml.length == 0 } /** * Check for `prefix`. */ function is(prefix) { return xml.indexOf(prefix) == 0 } } module.exports = parse /***/ }), /* 3 */ /***/ (function(module, exports, __webpack_require__) { const Block = __webpack_require__(4) const {splitLineToCamelCase} = __webpack_require__(0) class Element extends Block { constructor(prop) { super(prop.style) this.name = prop.name this.attributes = prop.attributes } } class Widget { constructor(xom, style) { this.xom = xom this.style = style this.inheritProps = ['fontSize', 'lineHeight', 'textAlign', 'verticalAlign', 'color'] } init() { this.container = this.create(this.xom) this.container.layout() this.inheritStyle(this.container) return this.container } // 继承父节点的样式 inheritStyle(node) { const parent = node.parent || null const children = node.children || {} const computedStyle = node.computedStyle if (parent) { this.inheritProps.forEach(prop => { computedStyle[prop] = computedStyle[prop] || parent.computedStyle[prop] }) } Object.values(children).forEach(child => { this.inheritStyle(child) }) } create(node) { let classNames = (node.attributes.class || '').split(' ') classNames = classNames.map(item => splitLineToCamelCase(item.trim())) const style = {} classNames.forEach(item => { Object.assign(style, this.style[item] || {}) }) const args = {name: node.name, style} const attrs = Object.keys(node.attributes) const attributes = {} for (const attr of attrs) { const value = node.attributes[attr] const CamelAttr = splitLineToCamelCase(attr) if (value === '' || value === 'true') { attributes[CamelAttr] = true } else if (value === 'false') { attributes[CamelAttr] = false } else { attributes[CamelAttr] = value } } attributes.text = node.content args.attributes = attributes const element = new Element(args) node.children.forEach(childNode => { const childElement = this.create(childNode) element.add(childElement) }) return element } } module.exports = {Widget} /***/ }), /* 4 */ /***/ (function(module, exports) { module.exports = require("widget-ui"); /***/ }), /* 5 */ /***/ (function(module, exports) { class Draw { constructor(context, canvas, use2dCanvas = false) { this.ctx = context this.canvas = canvas || null this.use2dCanvas = use2dCanvas } roundRect(x, y, w, h, r, fill = true, stroke = false) { if (r < 0) return const ctx = this.ctx ctx.beginPath() ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2) ctx.arc(x + w - r, y + r, r, Math.PI * 3 / 2, 0) ctx.arc(x + w - r, y + h - r, r, 0, Math.PI / 2) ctx.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI) ctx.lineTo(x, y + r) if (stroke) ctx.stroke() if (fill) ctx.fill() } drawView(box, style) { const ctx = this.ctx const { left: x, top: y, width: w, height: h } = box const { borderRadius = 0, borderWidth = 0, borderColor, color = '#000', backgroundColor = 'transparent', } = style ctx.save() // 外环 if (borderWidth > 0) { ctx.fillStyle = borderColor || color this.roundRect(x, y, w, h, borderRadius) } // 内环 ctx.fillStyle = backgroundColor const innerWidth = w - 2 * borderWidth const innerHeight = h - 2 * borderWidth const innerRadius = borderRadius - borderWidth >= 0 ? borderRadius - borderWidth : 0 this.roundRect(x + borderWidth, y + borderWidth, innerWidth, innerHeight, innerRadius) ctx.restore() } async drawImage(img, box, style) { await new Promise((resolve, reject) => { const ctx = this.ctx const canvas = this.canvas const { borderRadius = 0 } = style const { left: x, top: y, width: w, height: h } = box ctx.save() this.roundRect(x, y, w, h, borderRadius, false, false) ctx.clip() const _drawImage = (img) => { if (this.use2dCanvas) { const Image = canvas.createImage() Image.onload = () => { ctx.drawImage(Image, x, y, w, h) ctx.restore() resolve() } Image.onerror = () => { reject(new Error(`createImage fail: ${img}`)) } Image.src = img } else { ctx.drawImage(img, x, y, w, h) ctx.restore() resolve() } } const isTempFile = /^wxfile:\/\//.test(img) const isNetworkFile = /^https?:\/\//.test(img) if (isTempFile) { _drawImage(img) } else if (isNetworkFile) { wx.downloadFile({ url: img, success(res) { if (res.statusCode === 200) { _drawImage(res.tempFilePath) } else { reject(new Error(`downloadFile:fail ${img}`)) } }, fail() { reject(new Error(`downloadFile:fail ${img}`)) } }) } else { reject(new Error(`image format error: ${img}`)) } }) } // eslint-disable-next-line complexity drawText(text, box, style) { const ctx = this.ctx let { left: x, top: y, width: w, height: h } = box let { color = '#000', lineHeight = '1.4em', fontSize = 14, textAlign = 'left', verticalAlign = 'top', backgroundColor = 'transparent' } = style if (typeof lineHeight === 'string') { // 2em lineHeight = Math.ceil(parseFloat(lineHeight.replace('em')) * fontSize) } if (!text || (lineHeight > h)) return ctx.save() ctx.textBaseline = 'top' ctx.font = `${fontSize}px sans-serif` ctx.textAlign = textAlign // 背景色 ctx.fillStyle = backgroundColor this.roundRect(x, y, w, h, 0) // 文字颜色 ctx.fillStyle = color // 水平布局 switch (textAlign) { case 'left': break case 'center': x += 0.5 * w break case 'right': x += w break default: break } const textWidth = ctx.measureText(text).width const actualHeight = Math.ceil(textWidth / w) * lineHeight let paddingTop = Math.ceil((h - actualHeight) / 2) if (paddingTop < 0) paddingTop = 0 // 垂直布局 switch (verticalAlign) { case 'top': break case 'middle': y += paddingTop break case 'bottom': y += 2 * paddingTop break default: break } const inlinePaddingTop = Math.ceil((lineHeight - fontSize) / 2) // 不超过一行 if (textWidth <= w) { ctx.fillText(text, x, y + inlinePaddingTop) return } // 多行文本 const chars = text.split('') const _y = y // 逐行绘制 let line = '' for (const ch of chars) { const testLine = line + ch const testWidth = ctx.measureText(testLine).width if (testWidth > w) { ctx.fillText(line, x, y + inlinePaddingTop) y += lineHeight line = ch if ((y + lineHeight) > (_y + h)) break } else { line = testLine } } // 避免溢出 if ((y + lineHeight) <= (_y + h)) { ctx.fillText(line, x, y + inlinePaddingTop) } ctx.restore() } async drawNode(element) { const {layoutBox, computedStyle, name} = element const {src, text} = element.attributes if (name === 'view') { this.drawView(layoutBox, computedStyle) } else if (name === 'image') { await this.drawImage(src, layoutBox, computedStyle) } else if (name === 'text') { this.drawText(text, layoutBox, computedStyle) } const childs = Object.values(element.children) for (const child of childs) { await this.drawNode(child) } } } module.exports = { Draw } /***/ }) /******/ ]); });