[新增]wxmlToCanvas
This commit is contained in:
10
node_modules/wxml-to-canvas/.babelrc
generated
vendored
Normal file
10
node_modules/wxml-to-canvas/.babelrc
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"plugins": [
|
||||
["module-resolver", {
|
||||
"root": ["./src"],
|
||||
"alias": {}
|
||||
}],
|
||||
"@babel/transform-runtime"
|
||||
],
|
||||
"presets": ["@babel/preset-env"]
|
||||
}
|
||||
99
node_modules/wxml-to-canvas/.eslintrc.js
generated
vendored
Normal file
99
node_modules/wxml-to-canvas/.eslintrc.js
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
module.exports = {
|
||||
'extends': [
|
||||
'airbnb-base',
|
||||
'plugin:promise/recommended'
|
||||
],
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 9,
|
||||
'ecmaFeatures': {
|
||||
'jsx': false
|
||||
},
|
||||
'sourceType': 'module'
|
||||
},
|
||||
'env': {
|
||||
'es6': true,
|
||||
'node': true,
|
||||
'jest': true
|
||||
},
|
||||
'plugins': [
|
||||
'import',
|
||||
'node',
|
||||
'promise'
|
||||
],
|
||||
'rules': {
|
||||
'arrow-parens': 'off',
|
||||
'comma-dangle': [
|
||||
'error',
|
||||
'only-multiline'
|
||||
],
|
||||
'complexity': ['error', 10],
|
||||
'func-names': 'off',
|
||||
'global-require': 'off',
|
||||
'handle-callback-err': [
|
||||
'error',
|
||||
'^(err|error)$'
|
||||
],
|
||||
'import/no-unresolved': [
|
||||
'error',
|
||||
{
|
||||
'caseSensitive': true,
|
||||
'commonjs': true,
|
||||
'ignore': ['^[^.]']
|
||||
}
|
||||
],
|
||||
'import/prefer-default-export': 'off',
|
||||
'linebreak-style': 'off',
|
||||
'no-catch-shadow': 'error',
|
||||
'no-continue': 'off',
|
||||
'no-div-regex': 'warn',
|
||||
'no-else-return': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'no-plusplus': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-multi-assign': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'node/no-deprecated-api': 'error',
|
||||
'node/process-exit-as-throw': 'error',
|
||||
'object-curly-spacing': [
|
||||
'error',
|
||||
'never'
|
||||
],
|
||||
'operator-linebreak': [
|
||||
'error',
|
||||
'after',
|
||||
{
|
||||
'overrides': {
|
||||
':': 'before',
|
||||
'?': 'before'
|
||||
}
|
||||
}
|
||||
],
|
||||
'prefer-arrow-callback': 'off',
|
||||
'prefer-destructuring': 'off',
|
||||
'prefer-template': 'off',
|
||||
'quote-props': [
|
||||
1,
|
||||
'as-needed',
|
||||
{
|
||||
'unnecessary': true
|
||||
}
|
||||
],
|
||||
'semi': [
|
||||
'error',
|
||||
'never'
|
||||
],
|
||||
'no-await-in-loop': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
'promise/always-return': 'off',
|
||||
},
|
||||
'globals': {
|
||||
'window': true,
|
||||
'document': true,
|
||||
'App': true,
|
||||
'Page': true,
|
||||
'Component': true,
|
||||
'Behavior': true,
|
||||
'wx': true,
|
||||
'getCurrentPages': true,
|
||||
}
|
||||
}
|
||||
21
node_modules/wxml-to-canvas/LICENSE
generated
vendored
Normal file
21
node_modules/wxml-to-canvas/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 wechat-miniprogram
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
187
node_modules/wxml-to-canvas/README.md
generated
vendored
Normal file
187
node_modules/wxml-to-canvas/README.md
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
# wxml-to-canvas
|
||||
|
||||
[](https://www.npmjs.com/package/wxml-to-canvas)
|
||||
[](https://github.com/wechat-miniprogram/wxml-to-canvas)
|
||||
|
||||
小程序内通过静态模板和样式绘制 canvas ,导出图片,可用于生成分享图等场景。[代码片段](https://developers.weixin.qq.com/s/r6UBlEm17pc6)
|
||||
|
||||
|
||||
## 使用方法
|
||||
|
||||
#### Step1. npm 安装,参考 [小程序 npm 支持](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html)
|
||||
|
||||
```
|
||||
npm install --save wxml-to-canvas
|
||||
```
|
||||
|
||||
#### Step2. JSON 组件声明
|
||||
|
||||
```
|
||||
{
|
||||
"usingComponents": {
|
||||
"wxml-to-canvas": "wxml-to-canvas",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Step3. wxml 引入组件
|
||||
|
||||
```
|
||||
<video class="video" src="{{src}}">
|
||||
<wxml-to-canvas class="widget"></wxml-to-canvas>
|
||||
</video>
|
||||
<image src="{{src}}" style="width: {{width}}px; height: {{height}}px"></image>
|
||||
```
|
||||
|
||||
##### 属性列表
|
||||
|
||||
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|
||||
| --------------- | ------- | ------- | ---- | ---------------------- |
|
||||
| width | Number | 400 | 否 | 画布宽度 |
|
||||
| height | Number | 300 | 否 | 画布高度 |
|
||||
|
||||
|
||||
#### Step4. js 获取实例
|
||||
|
||||
```
|
||||
const {wxml, style} = require('./demo.js')
|
||||
Page({
|
||||
data: {
|
||||
src: ''
|
||||
},
|
||||
onLoad() {
|
||||
this.widget = this.selectComponent('.widget')
|
||||
},
|
||||
renderToCanvas() {
|
||||
const p1 = this.widget.renderToCanvas({ wxml, style })
|
||||
p1.then((res) => {
|
||||
this.container = res
|
||||
this.extraImage()
|
||||
})
|
||||
},
|
||||
extraImage() {
|
||||
const p2 = this.widget.canvasToTempFilePath()
|
||||
p2.then(res => {
|
||||
this.setData({
|
||||
src: res.tempFilePath,
|
||||
width: this.container.layoutBox.width,
|
||||
height: this.container.layoutBox.height
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## wxml 模板
|
||||
|
||||
支持 `view`、`text`、`image` 三种标签,通过 class 匹配 style 对象中的样式。
|
||||
|
||||
```
|
||||
<view class="container" >
|
||||
<view class="item-box red">
|
||||
</view>
|
||||
<view class="item-box green" >
|
||||
<text class="text">yeah!</text>
|
||||
</view>
|
||||
<view class="item-box blue">
|
||||
<image class="img" src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3582589792,4046843010&fm=26&gp=0.jpg"></image>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
## 样式
|
||||
|
||||
对象属性值为对应 wxml 标签的 cass 驼峰形式。**需为每个元素指定 width 和 height 属性**,否则会导致布局错误。
|
||||
|
||||
存在多个 className 时,位置靠后的优先级更高,子元素会继承父级元素的可继承属性。
|
||||
|
||||
元素均为 flex 布局。left/top 等 仅在 absolute 定位下生效。
|
||||
|
||||
```
|
||||
const style = {
|
||||
container: {
|
||||
width: 300,
|
||||
height: 200,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
backgroundColor: '#ccc',
|
||||
alignItems: 'center',
|
||||
},
|
||||
itemBox: {
|
||||
width: 80,
|
||||
height: 60,
|
||||
},
|
||||
red: {
|
||||
backgroundColor: '#ff0000'
|
||||
},
|
||||
green: {
|
||||
backgroundColor: '#00ff00'
|
||||
},
|
||||
blue: {
|
||||
backgroundColor: '#0000ff'
|
||||
},
|
||||
text: {
|
||||
width: 80,
|
||||
height: 60,
|
||||
textAlign: 'center',
|
||||
verticalAlign: 'middle',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 接口
|
||||
|
||||
#### f1. `renderToCanvas({wxml, style}): Promise`
|
||||
|
||||
渲染到 canvas,传入 wxml 模板 和 style 对象,返回的容器对象包含布局和样式信息。
|
||||
|
||||
#### f2. `canvasToTempFilePath({fileType, quality}): Promise`
|
||||
|
||||
提取画布中容器所在区域内容生成相同大小的图片,返回临时文件地址。
|
||||
|
||||
`fileType` 支持 `jpg`、`png` 两种格式,quality 为图片的质量,目前仅对 jpg 有效。取值范围为 (0, 1],不在范围内时当作 1.0 处理。
|
||||
|
||||
## 支持的 css 属性
|
||||
|
||||
### 布局相关
|
||||
|
||||
| 属性名 | 支持的值或类型 | 默认值 |
|
||||
| --------------------- | --------------------------------------------------------- | ---------- |
|
||||
| width | number | 0 |
|
||||
| height | number | 0 |
|
||||
| position | relative, absolute | relative |
|
||||
| left | number | 0 |
|
||||
| top | number | 0 |
|
||||
| right | number | 0 |
|
||||
| bottom | number | 0 |
|
||||
| margin | number | 0 |
|
||||
| padding | number | 0 |
|
||||
| borderWidth | number | 0 |
|
||||
| borderRadius | number | 0 |
|
||||
| flexDirection | column, row | row |
|
||||
| flexShrink | number | 1 |
|
||||
| flexGrow | number | |
|
||||
| flexWrap | wrap, nowrap | nowrap |
|
||||
| justifyContent | flex-start, center, flex-end, space-between, space-around | flex-start |
|
||||
| alignItems, alignSelf | flex-start, center, flex-end, stretch | flex-start |
|
||||
|
||||
支持 marginLeft、paddingLeft 等
|
||||
|
||||
### 文字
|
||||
|
||||
| 属性名 | 支持的值或类型 | 默认值 |
|
||||
| --------------- | ------------------- | ----------- |
|
||||
| fontSize | number | 14 |
|
||||
| lineHeight | number / string | '1.4em' |
|
||||
| textAlign | left, center, right | left |
|
||||
| verticalAlign | top, middle, bottom | top |
|
||||
| color | string | #000000 |
|
||||
| backgroundColor | string | transparent |
|
||||
|
||||
lineHeight 可取带 em 单位的字符串或数字类型。
|
||||
|
||||
### 变形
|
||||
|
||||
| 属性名 | 支持的值或类型 | 默认值 |
|
||||
| ------ | -------------- | ------ |
|
||||
| scale | number | 1 |
|
||||
26
node_modules/wxml-to-canvas/gulpfile.js
generated
vendored
Normal file
26
node_modules/wxml-to-canvas/gulpfile.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
const gulp = require('gulp')
|
||||
const clean = require('gulp-clean')
|
||||
|
||||
const config = require('./tools/config')
|
||||
const BuildTask = require('./tools/build')
|
||||
const id = require('./package.json').name || 'miniprogram-custom-component'
|
||||
|
||||
// 构建任务实例
|
||||
// eslint-disable-next-line no-new
|
||||
new BuildTask(id, config.entry)
|
||||
|
||||
// 清空生成目录和文件
|
||||
gulp.task('clean', gulp.series(() => gulp.src(config.distPath, {read: false, allowEmpty: true}).pipe(clean()), done => {
|
||||
if (config.isDev) {
|
||||
return gulp.src(config.demoDist, {read: false, allowEmpty: true})
|
||||
.pipe(clean())
|
||||
}
|
||||
|
||||
return done()
|
||||
}))
|
||||
// 监听文件变化并进行开发模式构建
|
||||
gulp.task('watch', gulp.series(`${id}-watch`))
|
||||
// 开发模式构建
|
||||
gulp.task('dev', gulp.series(`${id}-dev`))
|
||||
// 生产模式构建
|
||||
gulp.task('default', gulp.series(`${id}-default`))
|
||||
779
node_modules/wxml-to-canvas/miniprogram_dist/index.js
generated
vendored
Normal file
779
node_modules/wxml-to-canvas/miniprogram_dist/index.js
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
|
||||
|
||||
/***/ })
|
||||
/******/ ]);
|
||||
});
|
||||
4
node_modules/wxml-to-canvas/miniprogram_dist/index.json
generated
vendored
Normal file
4
node_modules/wxml-to-canvas/miniprogram_dist/index.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
2
node_modules/wxml-to-canvas/miniprogram_dist/index.wxml
generated
vendored
Normal file
2
node_modules/wxml-to-canvas/miniprogram_dist/index.wxml
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
<canvas wx:if="{{use2dCanvas}}" id="weui-canvas" type="2d" style="width: {{width}}px; height: {{height}}px;"></canvas>
|
||||
<canvas wx:else canvas-id="weui-canvas" style="width: {{width}}px; height: {{height}}px;"></canvas>
|
||||
0
node_modules/wxml-to-canvas/miniprogram_dist/index.wxss
generated
vendored
Normal file
0
node_modules/wxml-to-canvas/miniprogram_dist/index.wxss
generated
vendored
Normal file
57
node_modules/wxml-to-canvas/miniprogram_dist/utils.js
generated
vendored
Normal file
57
node_modules/wxml-to-canvas/miniprogram_dist/utils.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
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
|
||||
}
|
||||
91
node_modules/wxml-to-canvas/package.json
generated
vendored
Normal file
91
node_modules/wxml-to-canvas/package.json
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"_from": "wxml-to-canvas",
|
||||
"_id": "wxml-to-canvas@1.1.1",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-3mDjHzujY/UgdCOXij/MnmwJYerVjwkyQHMBFBE8zh89DK7h7UTzoydWFqEBjIC0rfZM+AXl5kDh9hUcsNpSmg==",
|
||||
"_location": "/wxml-to-canvas",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "tag",
|
||||
"registry": true,
|
||||
"raw": "wxml-to-canvas",
|
||||
"name": "wxml-to-canvas",
|
||||
"escapedName": "wxml-to-canvas",
|
||||
"rawSpec": "",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "latest"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#USER",
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/wxml-to-canvas/-/wxml-to-canvas-1.1.1.tgz",
|
||||
"_shasum": "64771473fb1e251bdad94f8c6ffa7dd64290e7ca",
|
||||
"_spec": "wxml-to-canvas",
|
||||
"_where": "/Users/WebTmm/Desktop/AGuestSaas",
|
||||
"author": {
|
||||
"name": "sanfordsun"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"widget-ui": "^1.0.2"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "[](https://www.npmjs.com/package/wxml-to-canvas) [](https://github.com/wechat-miniprogram/wxml-to-canvas)",
|
||||
"devDependencies": {
|
||||
"colors": "^1.3.1",
|
||||
"eslint": "^5.14.1",
|
||||
"eslint-config-airbnb-base": "13.1.0",
|
||||
"eslint-loader": "^2.1.2",
|
||||
"eslint-plugin-import": "^2.16.0",
|
||||
"eslint-plugin-node": "^7.0.1",
|
||||
"eslint-plugin-promise": "^3.8.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-clean": "^0.4.0",
|
||||
"gulp-if": "^2.0.2",
|
||||
"gulp-install": "^1.1.0",
|
||||
"gulp-less": "^4.0.1",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"jest": "^23.5.0",
|
||||
"miniprogram-simulate": "^1.0.0",
|
||||
"through2": "^2.0.3",
|
||||
"vinyl": "^2.2.0",
|
||||
"webpack": "^4.29.5",
|
||||
"webpack-cli": "^3.3.10",
|
||||
"webpack-node-externals": "^1.7.2"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"testURL": "https://jest.test",
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.js"
|
||||
],
|
||||
"moduleDirectories": [
|
||||
"node_modules",
|
||||
"src"
|
||||
]
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "miniprogram_dist/index.js",
|
||||
"miniprogram": "miniprogram_dist",
|
||||
"name": "wxml-to-canvas",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": ""
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp",
|
||||
"clean": "gulp clean",
|
||||
"clean-dev": "gulp clean --develop",
|
||||
"coverage": "jest ./test/* --coverage --bail",
|
||||
"dev": "gulp dev --develop",
|
||||
"dist": "npm run build",
|
||||
"lint": "eslint \"src/**/*.js\" --fix",
|
||||
"lint-tools": "eslint \"tools/**/*.js\" --rule \"import/no-extraneous-dependencies: false\" --fix",
|
||||
"test": "jest --bail",
|
||||
"test-debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --bail",
|
||||
"watch": "gulp watch --develop --watch"
|
||||
},
|
||||
"version": "1.1.1"
|
||||
}
|
||||
225
node_modules/wxml-to-canvas/src/draw.js
generated
vendored
Normal file
225
node_modules/wxml-to-canvas/src/draw.js
generated
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
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
|
||||
}
|
||||
117
node_modules/wxml-to-canvas/src/index.js
generated
vendored
Normal file
117
node_modules/wxml-to-canvas/src/index.js
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
|
||||
const xmlParse = require('./xml-parser')
|
||||
const {Widget} = require('./widget')
|
||||
const {Draw} = require('./draw')
|
||||
const {compareVersion} = require('./utils')
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
4
node_modules/wxml-to-canvas/src/index.json
generated
vendored
Normal file
4
node_modules/wxml-to-canvas/src/index.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
2
node_modules/wxml-to-canvas/src/index.wxml
generated
vendored
Normal file
2
node_modules/wxml-to-canvas/src/index.wxml
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
<canvas wx:if="{{use2dCanvas}}" id="weui-canvas" type="2d" style="width: {{width}}px; height: {{height}}px;"></canvas>
|
||||
<canvas wx:else canvas-id="weui-canvas" style="width: {{width}}px; height: {{height}}px;"></canvas>
|
||||
0
node_modules/wxml-to-canvas/src/index.wxss
generated
vendored
Normal file
0
node_modules/wxml-to-canvas/src/index.wxss
generated
vendored
Normal file
57
node_modules/wxml-to-canvas/src/utils.js
generated
vendored
Normal file
57
node_modules/wxml-to-canvas/src/utils.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
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
|
||||
}
|
||||
81
node_modules/wxml-to-canvas/src/widget.js
generated
vendored
Normal file
81
node_modules/wxml-to-canvas/src/widget.js
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
const Block = require('widget-ui')
|
||||
const {splitLineToCamelCase} = require('./utils')
|
||||
|
||||
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}
|
||||
164
node_modules/wxml-to-canvas/src/xml-parser.js
generated
vendored
Normal file
164
node_modules/wxml-to-canvas/src/xml-parser.js
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
|
||||
/**
|
||||
* 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
|
||||
Reference in New Issue
Block a user