123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700 |
- <template>
- <uni-shadow-root class="wxml-to-canvas-index"><canvas id="canvas" type="2d" :style="'width: '+(width)+'px; height: '+(height)+'px;'"></canvas></uni-shadow-root>
- </template>
- <script>
- global['__wxVueOptions'] = {components:{}}
- global['__wxRoute'] = 'wxml-to-canvas/index'
- (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 = 0);
- /******/ })
- /************************************************************************/
- /******/ ([
- /* 0 */
- /***/ (function(module, exports, __webpack_require__) {
-
-
- const xmlParse = __webpack_require__(1)
- const {Widget} = __webpack_require__(2)
- const {Draw} = __webpack_require__(5)
-
- Component({
- properties: {
- width: {
- type: Number,
- value: 400
- },
- height: {
- type: Number,
- value: 300
- }
- },
- lifetimes: {
- attached() {
- const dpr = wx.getSystemInfoSync().pixelRatio
- const query = this.createSelectorQuery()
- this.dpr = dpr
- query.select('#canvas')
- .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
- })
- }
- },
- methods: {
- async renderToCanvas(args) {
- const {wxml, style} = args
-
- // 清空画布
- const ctx = this.ctx
- const canvas = this.canvas
- if (!ctx || !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(canvas, ctx)
- await draw.drawNode(container)
- return Promise.resolve(container)
- },
-
- canvasToTempFilePath(args = {}) {
- return new Promise((resolve, reject) => {
- const {
- top, left, width, height
- } = this.boundary
- wx.canvasToTempFilePath({
- x: left,
- y: top,
- width,
- height,
- destWidth: width * this.dpr,
- destHeight: height * this.dpr,
- canvas: this.canvas,
- fileType: args.fileType || 'png',
- quality: args.quality || 1,
- success: resolve,
- fail: reject
- })
- })
- }
- }
- })
-
-
- /***/ }),
- /* 1 */
- /***/ (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
-
-
- /***/ }),
- /* 2 */
- /***/ (function(module, exports, __webpack_require__) {
-
- const Block = __webpack_require__(3)
- const {splitLineToCamelCase} = __webpack_require__(4)
-
- 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}
-
-
- /***/ }),
- /* 3 */
- /***/ (function(module, exports) {
-
- module.exports = require("../widget-ui/index");
-
- /***/ }),
- /* 4 */
- /***/ (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('')
-
- module.exports = {
- hex,
- splitLineToCamelCase
- }
-
-
- /***/ }),
- /* 5 */
- /***/ (function(module, exports) {
-
- class Draw {
- constructor(canvas, context) {
- this.canvas = canvas
- this.ctx = context
- }
-
- 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 Image = canvas.createImage()
- Image.onload = () => {
- ctx.drawImage(Image, x, y, w, h)
- ctx.restore()
- resolve()
- }
- Image.onerror = () => { reject() }
- Image.src = 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 (!text || (lineHeight > h)) return
-
- ctx.save()
- if (lineHeight) { // 2em
- lineHeight = Math.ceil(parseFloat(lineHeight.replace('em')) * fontSize)
- }
- 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
- }
-
-
- /***/ })
- /******/ ]);
- });
- export default global['__wxComponents']['wxml-to-canvas/index']
- </script>
- <style platform="mp-weixin">
- </style>
|