172 lines
5.0 KiB
JavaScript
172 lines
5.0 KiB
JavaScript
import Downloader from './downloader';
|
||
|
||
const downloader = new Downloader();
|
||
const QR = require('./qrcode.js');
|
||
|
||
export default class Painter {
|
||
constructor(ctx, data) {
|
||
this.ctx = ctx;
|
||
this.data = data;
|
||
}
|
||
|
||
paint(callback) {
|
||
this.style = {
|
||
width: this.data.width.toPx(),
|
||
height: this.data.height.toPx(),
|
||
};
|
||
this._background();
|
||
for (const view of this.data.views) {
|
||
this._drawAbsolute(view);
|
||
}
|
||
this.ctx.draw(false, () => {
|
||
callback();
|
||
});
|
||
}
|
||
|
||
_background() {
|
||
this.ctx.save();
|
||
const { width, height } = this.style;
|
||
const bg = this.data.background;
|
||
this.ctx.translate(width / 2, height / 2);
|
||
|
||
this._doBorder(this.data.borderRadius, width, height);
|
||
if (!bg) {
|
||
// 如果未设置背景,则默认使用白色
|
||
this.ctx.setFillStyle('#fff');
|
||
this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
|
||
} else if (bg.startsWith('#') || bg.startsWith('rgba')) {
|
||
// 背景填充颜色
|
||
this.ctx.setFillStyle(bg);
|
||
this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
|
||
} else {
|
||
// 背景填充图片
|
||
this.ctx.drawImage(bg, -(width / 2), -(height / 2), width, height);
|
||
}
|
||
this.ctx.restore();
|
||
}
|
||
|
||
_drawAbsolute(view) {
|
||
// 证明 css 为数组形式,需要合并
|
||
if (view.css.length) {
|
||
/* eslint-disable no-param-reassign */
|
||
view.css = Object.assign(...view.css);
|
||
}
|
||
switch (view.type) {
|
||
case 'image':
|
||
this._drawAbsImage(view);
|
||
break;
|
||
case 'text':
|
||
this._fillAbsText(view);
|
||
break;
|
||
case 'rect':
|
||
this._drawAbsRect(view);
|
||
break;
|
||
case 'qrcode':
|
||
this._drawQRCode(view);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
_doBorder(borderRadius, width, height) {
|
||
if (borderRadius && width && height) {
|
||
const r = Math.min(borderRadius.toPx(), width / 2, height / 2);
|
||
|
||
this.ctx.beginPath();
|
||
this.ctx.arc(-width / 2 + r, -height / 2 + r, r, 1 * Math.PI, 1.5 * Math.PI);
|
||
this.ctx.lineTo(width / 2 - r, -height / 2);
|
||
this.ctx.arc(width / 2 - r, -height / 2 + r, r, 1.5 * Math.PI, 2 * Math.PI);
|
||
this.ctx.lineTo(width / 2, height / 2 - r);
|
||
this.ctx.arc(width / 2 - r, height / 2 - r, r, 0, 0.5 * Math.PI);
|
||
this.ctx.lineTo(-width / 2 + r, height / 2);
|
||
this.ctx.arc(-width / 2 + r, height / 2 - r, r, 0.5 * Math.PI, 1 * Math.PI);
|
||
this.ctx.closePath();
|
||
this.ctx.fill();
|
||
// 在 ios 的 6.6.6 版本上 clip 有 bug,禁掉此类型上的 clip,也就意味着,在此版本微信的 ios 设备下无法使用 border 属性
|
||
if (!(getApp().systemInfo
|
||
&& getApp().systemInfo.version <= '6.6.6'
|
||
&& getApp().systemInfo.platform === 'ios')) {
|
||
this.ctx.clip();
|
||
}
|
||
}
|
||
}
|
||
|
||
_preProcess(view) {
|
||
let width;
|
||
let height;
|
||
let x;
|
||
let y;
|
||
if (view.type === 'text') {
|
||
this.ctx.setFillStyle(view.css.color ? view.css.color : 'black');
|
||
this.ctx.setFontSize(view.css.fontSize.toPx());
|
||
/* eslint-disable prefer-destructuring */
|
||
width = this.ctx.measureText(view.text).width;
|
||
height = view.css.fontSize.toPx();
|
||
x = view.css.right ? this.style.width - width - view.css.right.toPx() : (view.css.left ? view.css.left.toPx() : 0);
|
||
y = view.css.bottom ? this.style.height - height - view.css.bottom.toPx() : (view.css.top ? view.css.top.toPx() : 0);
|
||
} else {
|
||
width = view.css.width.toPx();
|
||
height = view.css.height.toPx();
|
||
x = view.css.right ? this.style.width - width - view.css.right.toPx() : (view.css.left ? view.css.left.toPx() : 0);
|
||
y = view.css.bottom ? this.style.height - height - view.css.bottom.toPx() : (view.css.top ? view.css.top.toPx() : 0);
|
||
}
|
||
const angle = view.css.rotate ? this._getAngle(view.css.rotate) : 0;
|
||
this.ctx.translate(x + width / 2, y + height / 2);
|
||
this.ctx.rotate(angle);
|
||
this._doBorder(view.css.borderRadius, width, height);
|
||
|
||
return {
|
||
width: width, height: height, x: x, y: y,
|
||
};
|
||
}
|
||
|
||
_drawQRCode(view) {
|
||
this.ctx.save();
|
||
const {
|
||
width, height,
|
||
} = this._preProcess(view);
|
||
QR.api.draw(view.content, this.ctx, -width / 2, -height / 2, width, height, view.css.background);
|
||
this.ctx.restore();
|
||
}
|
||
|
||
_drawAbsImage(view) {
|
||
if (!view.url) {
|
||
return;
|
||
}
|
||
this.ctx.save();
|
||
const {
|
||
width, height,
|
||
} = this._preProcess(view);
|
||
this.ctx.drawImage(view.url, -(width / 2), -(height / 2), width, height);
|
||
this.ctx.restore();
|
||
}
|
||
|
||
_fillAbsText(view) {
|
||
if (!view.text) {
|
||
return;
|
||
}
|
||
this.ctx.save();
|
||
const {
|
||
width, height,
|
||
} = this._preProcess(view);
|
||
|
||
this.ctx.fillText(view.text, -(width / 2), (height / 2));
|
||
this.ctx.restore();
|
||
}
|
||
|
||
_drawAbsRect(view) {
|
||
this.ctx.save();
|
||
const {
|
||
width, height,
|
||
} = this._preProcess(view);
|
||
this.ctx.setFillStyle(view.css.color);
|
||
this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
|
||
this.ctx.restore();
|
||
}
|
||
|
||
_getAngle(angle) {
|
||
return Number(angle) * Math.PI / 180;
|
||
}
|
||
}
|