|
@@ -0,0 +1,779 @@
|
|
|
+(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(/<!--[\s\S]*?-->/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
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/***/ })
|
|
|
+/******/ ]);
|
|
|
+});
|