PainterCore/lib/pen.js

658 lines
22 KiB
JavaScript
Raw Normal View History

2018-07-05 15:27:12 +08:00
const QR = require('./qrcode.js');
2018-12-06 12:27:07 +08:00
const GD = require('./gradient.js');
2018-07-05 15:27:12 +08:00
export default class Painter {
constructor(ctx, data) {
this.ctx = ctx;
this.data = data;
this.globalWidth = {};
this.globalHeight = {};
2018-07-05 15:27:12 +08:00
}
2019-11-18 14:08:07 +08:00
isMoving = false
movingCache = {}
paint(callback, isMoving, movingCache) {
2018-07-05 15:27:12 +08:00
this.style = {
width: this.data.width.toPx(),
height: this.data.height.toPx(),
};
2019-11-18 14:08:07 +08:00
if (isMoving) {
this.isMoving = true
this.movingCache = movingCache
}
2018-07-05 15:27:12 +08:00
this._background();
for (const view of this.data.views) {
this._drawAbsolute(view);
}
this.ctx.draw(false, () => {
2019-11-18 14:08:07 +08:00
callback && callback(this.callbackInfo);
2018-07-05 15:27:12 +08:00
});
}
_background() {
this.ctx.save();
2018-07-05 18:00:45 +08:00
const {
width,
2018-07-10 18:24:28 +08:00
height,
2018-07-05 18:00:45 +08:00
} = this.style;
2018-07-05 15:27:12 +08:00
const bg = this.data.background;
this.ctx.translate(width / 2, height / 2);
2018-07-20 18:59:47 +08:00
this._doClip(this.data.borderRadius, width, height);
2018-07-05 15:27:12 +08:00
if (!bg) {
2019-11-13 19:03:20 +08:00
// 如果未设置背景,则默认使用透明色
this.ctx.fillStyle = 'transparent';
2018-07-05 15:27:12 +08:00
this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
} else if (bg.startsWith('#') || bg.startsWith('rgba') || bg.toLowerCase() === 'transparent') {
2018-07-05 15:27:12 +08:00
// 背景填充颜色
this.ctx.fillStyle = bg;
2018-07-05 15:27:12 +08:00
this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
2019-01-04 14:41:32 +08:00
} else if (GD.api.isGradient(bg)) {
GD.api.doGradient(bg, width, height, this.ctx);
this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
2018-07-05 15:27:12 +08:00
} else {
// 背景填充图片
this.ctx.drawImage(bg, -(width / 2), -(height / 2), width, height);
}
this.ctx.restore();
}
_drawAbsolute(view) {
2019-11-21 16:09:13 +08:00
if (!(view && view.type)) {
// 过滤无效 view
return
}
2018-07-05 15:27:12 +08:00
// 证明 css 为数组形式,需要合并
if (view.css && view.css.length) {
2018-07-05 15:27:12 +08:00
/* 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;
}
}
2019-11-20 11:10:21 +08:00
_border({ borderRadius = 0, width, height, borderWidth = 0, borderStyle = 'solid' }) {
let r1 = 0,
r2 = 0,
r3 = 0,
r4 = 0
if (borderRadius) {
2019-11-20 11:10:21 +08:00
const border = borderRadius.split(/\s+/)
if (border.length === 4) {
r1 = Math.min(border[0].toPx(), width / 2, height / 2);
r2 = Math.min(border[1].toPx(), width / 2, height / 2);
r3 = Math.min(border[2].toPx(), width / 2, height / 2);
r4 = Math.min(border[3].toPx(), width / 2, height / 2);
} else {
r1 = r2 = r3 = r4 = Math.min(borderRadius && borderRadius.toPx(), width / 2, height / 2);
}
}
2019-11-20 11:10:21 +08:00
const lineWidth = borderWidth && borderWidth.toPx();
this.ctx.lineWidth = lineWidth;
if (borderStyle === 'dashed') {
2019-11-20 11:10:21 +08:00
this.ctx.setLineDash([lineWidth * 4 / 3, lineWidth * 4 / 3]);
2019-11-20 10:14:08 +08:00
// this.ctx.lineDashOffset = 2 * lineWidth
} else if (borderStyle === 'dotted') {
this.ctx.setLineDash([lineWidth, lineWidth]);
}
const notSolid = borderStyle !== 'solid'
this.ctx.beginPath();
2019-11-20 09:32:16 +08:00
notSolid && r1 === 0 && this.ctx.moveTo(-width / 2 - lineWidth, -height / 2 - lineWidth / 2) // 顶边虚线规避重叠规则
r1 !== 0 && this.ctx.arc(-width / 2 + r1, -height / 2 + r1, r1 + lineWidth / 2, 1 * Math.PI, 1.5 * Math.PI); //左上角圆弧
this.ctx.lineTo(r2 === 0 ? notSolid ? width / 2 : width / 2 + lineWidth / 2 : width / 2 - r2, -height / 2 - lineWidth / 2); // 顶边线
2019-11-20 09:32:16 +08:00
notSolid && r2 === 0 && this.ctx.moveTo(width / 2 + lineWidth / 2, -height / 2 - lineWidth) // 右边虚线规避重叠规则
r2 !== 0 && this.ctx.arc(width / 2 - r2, -height / 2 + r2, r2 + lineWidth / 2, 1.5 * Math.PI, 2 * Math.PI); // 右上角圆弧
this.ctx.lineTo(width / 2 + lineWidth / 2, r3 === 0 ? notSolid ? height / 2 : height / 2 + lineWidth / 2 : height / 2 - r3); // 右边线
2019-11-20 09:32:16 +08:00
notSolid && r3 === 0 && this.ctx.moveTo(width / 2 + lineWidth, height / 2 + lineWidth / 2) // 底边虚线规避重叠规则
r3 !== 0 && this.ctx.arc(width / 2 - r3, height / 2 - r3, r3 + lineWidth / 2, 0, 0.5 * Math.PI); // 右下角圆弧
this.ctx.lineTo(r4 === 0 ? notSolid ? -width / 2 : -width / 2 - lineWidth / 2 : -width / 2 + r4, height / 2 + lineWidth / 2); // 底边线
2019-11-20 09:32:16 +08:00
notSolid && r4 === 0 && this.ctx.moveTo(-width / 2 - lineWidth / 2, height / 2 + lineWidth) // 左边虚线规避重叠规则
r4 !== 0 && this.ctx.arc(-width / 2 + r4, height / 2 - r4, r4 + lineWidth / 2, 0.5 * Math.PI, 1 * Math.PI); // 左下角圆弧
this.ctx.lineTo(-width / 2 - lineWidth / 2, r1 === 0 ? notSolid ? -height / 2 : -height / 2 - lineWidth / 2 : -height / 2 + r1); // 左边线
2019-11-20 10:14:08 +08:00
notSolid && r1 === 0 && this.ctx.moveTo(-width / 2 - lineWidth, -height / 2 - lineWidth / 2) // 顶边虚线规避重叠规则
2019-11-20 10:14:08 +08:00
if (!notSolid) {
this.ctx.closePath();
}
}
2018-07-20 18:59:47 +08:00
/**
* 根据 borderRadius 进行裁减
*/
_doClip(borderRadius, width, height, borderStyle) {
2018-07-05 15:27:12 +08:00
if (borderRadius && width && height) {
// 防止在某些机型上周边有黑框现象,此处如果直接设置 fillStyle 为透明,在 Android 机型上会导致被裁减的图片也变为透明, iOS 和 IDE 上不会
// globalAlpha 在 1.9.90 起支持,低版本下无效,但把 fillStyle 设为了 white相对默认的 black 要好点
this.ctx.globalAlpha = 0;
this.ctx.fillStyle = 'white';
this._border({ borderRadius, width, height, borderStyle })
2018-07-05 15:27:12 +08:00
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')) {
2018-07-05 15:27:12 +08:00
this.ctx.clip();
}
this.ctx.globalAlpha = 1;
2018-07-05 15:27:12 +08:00
}
}
2018-07-20 18:59:47 +08:00
/**
* 画边框
*/
_doBorder(view, width, height) {
if (!view.css) {
return;
}
2018-07-20 18:59:47 +08:00
const {
borderRadius,
borderWidth,
borderColor,
borderStyle
2018-07-20 18:59:47 +08:00
} = view.css;
if (!borderWidth) {
return;
}
this.ctx.save();
this._preProcess(view, true);
this.ctx.strokeStyle = (borderColor || 'black');
this._border({ borderRadius, width, height, borderWidth, borderStyle })
2018-07-20 18:59:47 +08:00
this.ctx.stroke();
this.ctx.restore();
}
_preProcess(view, notClip) {
2019-07-15 17:47:17 +08:00
let width = 0;
2018-07-05 15:27:12 +08:00
let height;
2018-07-11 16:54:16 +08:00
let extra;
switch (view.type) {
2019-06-10 12:21:03 +08:00
case 'text': {
const textArray = view.text.split('\n');
2019-07-15 17:47:17 +08:00
// 处理多个连续的'\n'
for (let i = 0; i < textArray.length; ++i) {
if (textArray[i] === '') {
textArray[i] = ' ';
}
}
const fontWeight = view.css.fontWeight === 'bold' ? 'bold' : 'normal';
view.css.fontSize = view.css.fontSize ? view.css.fontSize : '20rpx';
2018-10-16 14:40:48 +08:00
this.ctx.font = `normal ${fontWeight} ${view.css.fontSize.toPx()}px ${view.css.fontFamily ? view.css.fontFamily : 'sans-serif'}`;
// this.ctx.setFontSize(view.css.fontSize.toPx());
// 计算行数
2019-07-15 17:47:17 +08:00
let lines = 0;
const linesArray = [];
for (let i = 0; i < textArray.length; ++i) {
const textLength = this.ctx.measureText(textArray[i]).width;
const partWidth = view.css.width ? view.css.width.toPx() : textLength;
const calLines = Math.ceil(textLength / partWidth);
width = partWidth > width ? partWidth : width;
lines += calLines;
linesArray[i] = calLines;
}
lines = view.css.maxLines < lines ? view.css.maxLines : lines;
const lineHeight = view.css.lineHeight ? view.css.lineHeight.toPx() : view.css.fontSize.toPx();
height = lineHeight * lines;
extra = {
lines: lines,
2019-06-10 12:21:03 +08:00
lineHeight: lineHeight,
textArray: textArray,
linesArray: linesArray,
};
break;
2019-06-10 12:21:03 +08:00
}
case 'image': {
2019-07-17 17:40:47 +08:00
// image的长宽设置成auto的逻辑处理
const ratio = getApp().systemInfo.pixelRatio ? getApp().systemInfo.pixelRatio : 2;
// 有css却未设置width或height则默认为auto
if (view.css) {
if (!view.css.width) {
view.css.width = 'auto';
}
if (!view.css.height) {
view.css.height = 'auto';
}
}
if (!view.css || (view.css.width === 'auto' && view.css.height === 'auto')) {
2019-07-17 17:40:47 +08:00
width = Math.round(view.sWidth / ratio);
height = Math.round(view.sHeight / ratio);
} else if (view.css.width === 'auto') {
height = view.css.height.toPx();
width = view.sWidth / view.sHeight * height;
} else if (view.css.height === 'auto') {
width = view.css.width.toPx();
height = view.sHeight / view.sWidth * width;
2019-07-17 17:40:47 +08:00
} else {
width = view.css.width.toPx();
height = view.css.height.toPx();
2019-07-17 17:40:47 +08:00
}
break;
2019-06-10 12:21:03 +08:00
}
default:
if (!(view.css.width && view.css.height)) {
console.error('You should set width and height');
return;
}
width = view.css.width.toPx();
height = view.css.height.toPx();
break;
2018-07-05 15:27:12 +08:00
}
2019-06-10 12:21:03 +08:00
let x;
if (view.css && view.css.right) {
if (typeof view.css.right === 'string') {
2019-11-21 19:41:37 +08:00
x = this.style.width - view.css.right.toPx(true, this.style.width);
2019-06-10 12:21:03 +08:00
} else {
// 可以用数组方式,把文字长度计算进去
// [right, 文字id, 乘数(默认 1]
const rights = view.css.right;
2019-11-21 19:41:37 +08:00
x = this.style.width - rights[0].toPx(true, this.style.width) - this.globalWidth[rights[1]] * (rights[2] || 1);
2019-06-10 12:21:03 +08:00
}
} else if (view.css && view.css.left) {
if (typeof view.css.left === 'string') {
2019-11-21 19:41:37 +08:00
x = view.css.left.toPx(true, this.style.width);
2019-06-10 12:21:03 +08:00
} else {
const lefts = view.css.left;
2019-11-21 19:41:37 +08:00
x = lefts[0].toPx(true, this.style.width) + this.globalWidth[lefts[1]] * (lefts[2] || 1);
2019-06-10 12:21:03 +08:00
}
} else {
x = 0;
}
2019-11-06 16:17:11 +08:00
//const y = view.css && view.css.bottom ? this.style.height - height - view.css.bottom.toPx(true) : (view.css && view.css.top ? view.css.top.toPx(true) : 0);
let y;
if (view.css && view.css.bottom) {
2019-11-21 19:41:37 +08:00
y = this.style.height - height - view.css.bottom.toPx(true, this.style.height);
2019-11-06 16:17:11 +08:00
} else {
if (view.css && view.css.top) {
if (typeof view.css.top === 'string') {
2019-11-21 19:41:37 +08:00
y = view.css.top.toPx(true, this.style.height);
2019-11-06 16:17:11 +08:00
} else {
const tops = view.css.top;
2019-11-21 19:41:37 +08:00
y = tops[0].toPx(true, this.style.height) + this.globalHeight[tops[1]] * (tops[2] || 1);
2019-11-06 16:17:11 +08:00
}
} else {
y = 0
}
}
const angle = view.css && view.css.rotate ? this._getAngle(view.css.rotate) : 0;
2018-07-17 20:54:35 +08:00
// 当设置了 right 时,默认 align 用 right反之用 left
const align = view.css && view.css.align ? view.css.align : (view.css && view.css.right ? 'right' : 'left');
2019-11-13 10:56:24 +08:00
// 记录绘制时的画布
let xa = 0;
const ya = y + height / 2;
2018-07-05 18:00:45 +08:00
switch (align) {
case 'center':
2019-11-13 10:56:24 +08:00
xa = x;
2018-07-05 18:00:45 +08:00
break;
2018-07-10 18:24:28 +08:00
case 'right':
2019-11-13 10:56:24 +08:00
xa = x - width / 2;
2018-07-05 18:00:45 +08:00
break;
default:
2019-11-13 10:56:24 +08:00
xa = x + width / 2;
2018-07-05 18:00:45 +08:00
break;
}
2019-11-13 10:56:24 +08:00
this.ctx.translate(xa, ya);
// 记录该 view 的有效点击区域
// TODO ,旋转和裁剪的判断
// 记录在真实画布上的左侧
let left = x
if (align === 'center') {
left = x - width / 2
} else if (align === 'right') {
left = x - width
}
view.rect = {
left,
top: y,
right: left + width,
bottom: y + height,
x: view.css && view.css.right ? (x - width) : x,
y
};
2019-11-15 11:52:16 +08:00
const pd = this._doPaddings(view);
view.rect.left = view.rect.left - pd[3];
view.rect.top = view.rect.top - pd[0];
view.rect.right = view.rect.right + pd[1];
view.rect.bottom = view.rect.bottom + pd[2];
2019-11-13 10:56:24 +08:00
2018-07-05 15:27:12 +08:00
this.ctx.rotate(angle);
2019-10-06 17:13:10 +08:00
if (!notClip && view.css && view.css.borderRadius && view.type !== 'rect') {
this._doClip(view.css.borderRadius, width, height, view.css.borderStyle);
2018-07-20 18:59:47 +08:00
}
2019-01-21 15:03:21 +08:00
this._doShadow(view);
if (view.id) {
this.globalWidth[view.id] = width;
this.globalHeight[view.id] = height;
}
2018-07-05 15:27:12 +08:00
return {
2018-07-05 18:00:45 +08:00
width: width,
height: height,
x: x,
y: y,
2018-07-11 16:54:16 +08:00
extra: extra,
2018-07-05 15:27:12 +08:00
};
}
2019-11-15 11:52:16 +08:00
_doPaddings(view) {
2019-06-10 12:21:03 +08:00
const {
padding,
} = view.css;
let pd = [0, 0, 0, 0];
if (padding) {
2019-06-10 12:21:03 +08:00
const pdg = padding.split(/\s+/);
if (pdg.length === 1) {
const x = pdg[0].toPx();
pd = [x, x, x, x];
}
2019-06-10 12:21:03 +08:00
if (pdg.length === 2) {
const x = pdg[0].toPx();
const y = pdg[1].toPx();
pd = [x, y, x, y];
}
2019-06-10 12:21:03 +08:00
if (pdg.length === 3) {
const x = pdg[0].toPx();
const y = pdg[1].toPx();
const z = pdg[2].toPx();
pd = [x, y, z, y];
}
2019-06-10 12:21:03 +08:00
if (pdg.length === 4) {
const x = pdg[0].toPx();
const y = pdg[1].toPx();
const z = pdg[2].toPx();
const a = pdg[3].toPx();
pd = [x, y, z, a];
}
}
2019-11-15 11:52:16 +08:00
return pd;
}
// 画文字的背景图片
_doBackground(view) {
this.ctx.save();
const {
width: rawWidth,
height: rawHeight,
} = this._preProcess(view, true);
const {
background,
} = view.css;
let pd = this._doPaddings(view);
const width = rawWidth + pd[1] + pd[3];
const height = rawHeight + pd[0] + pd[2];
2019-11-15 11:52:16 +08:00
this._doClip(view.css.borderRadius, width, height, view.css.borderStyle)
if (GD.api.isGradient(background)) {
GD.api.doGradient(background, width, height, this.ctx);
} else {
this.ctx.fillStyle = background;
}
this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
this.ctx.restore();
}
2018-07-05 15:27:12 +08:00
_drawQRCode(view) {
this.ctx.save();
const {
2018-07-05 18:00:45 +08:00
width,
height,
2018-07-05 15:27:12 +08:00
} = this._preProcess(view);
2018-07-20 18:59:47 +08:00
QR.api.draw(view.content, this.ctx, -width / 2, -height / 2, width, height, view.css.background, view.css.color);
2018-07-05 15:27:12 +08:00
this.ctx.restore();
2018-07-20 18:59:47 +08:00
this._doBorder(view, width, height);
2018-07-05 15:27:12 +08:00
}
_drawAbsImage(view) {
if (!view.url) {
return;
}
this.ctx.save();
const {
2018-07-05 18:00:45 +08:00
width,
height,
2018-07-05 15:27:12 +08:00
} = this._preProcess(view);
2018-07-27 14:39:36 +08:00
// 获得缩放到图片大小级别的裁减框
2018-11-30 16:55:10 +08:00
let rWidth = view.sWidth;
let rHeight = view.sHeight;
2018-07-27 14:39:36 +08:00
let startX = 0;
let startY = 0;
2018-11-30 16:55:10 +08:00
// 绘画区域比例
const cp = width / height;
// 原图比例
const op = view.sWidth / view.sHeight;
if (cp >= op) {
rHeight = rWidth / cp;
startY = Math.round((view.sHeight - rHeight) / 2);
2018-07-27 14:39:36 +08:00
} else {
2018-11-30 16:55:10 +08:00
rWidth = rHeight * cp;
2018-07-27 14:39:36 +08:00
startX = Math.round((view.sWidth - rWidth) / 2);
}
if (view.css && view.css.mode === 'scaleToFill') {
2018-07-27 14:39:36 +08:00
this.ctx.drawImage(view.url, -(width / 2), -(height / 2), width, height);
} else {
this.ctx.drawImage(view.url, startX, startY, rWidth, rHeight, -(width / 2), -(height / 2), width, height);
}
2018-07-05 15:27:12 +08:00
this.ctx.restore();
2018-07-20 18:59:47 +08:00
this._doBorder(view, width, height);
2018-07-05 15:27:12 +08:00
}
2019-11-18 14:08:07 +08:00
callbackInfo = {}
2018-07-05 15:27:12 +08:00
_fillAbsText(view) {
if (!view.text) {
return;
}
if (view.css.background) {
2019-01-26 17:41:17 +08:00
// 生成背景
this._doBackground(view);
2019-01-26 17:41:17 +08:00
}
2018-07-05 15:27:12 +08:00
this.ctx.save();
const {
2018-07-05 18:00:45 +08:00
width,
height,
2018-07-11 16:54:16 +08:00
extra,
} = this._preProcess(view, view.css.background && view.css.borderRadius);
2019-11-18 14:08:07 +08:00
if (this.isMoving && JSON.stringify(this.movingCache) !== JSON.stringify({})) {
this.globalWidth[view.id] = this.movingCache.globalWidth
this.ctx.setTextAlign(view.css.textAlign ? view.css.textAlign : 'left');
for (const i of this.movingCache.lineArray) {
const {
measuredWith,
text,
x,
y,
} = i
if (view.css.textStyle === 'stroke') {
this.ctx.strokeText(text, x, y, measuredWith);
} else {
this.ctx.fillText(text, x, y, measuredWith);
2018-07-17 13:51:36 +08:00
}
if (view.css.textDecoration) {
this.ctx.beginPath();
2019-11-18 14:08:07 +08:00
this.ctx.moveTo(...this.callbackInfo.textDecoration.moveTo);
this.ctx.lineTo(...this.callbackInfo.textDecoration.lineTo);
this.ctx.closePath();
this.ctx.strokeStyle = view.css.color;
this.ctx.stroke();
2018-07-17 13:51:36 +08:00
}
2018-07-11 16:54:16 +08:00
}
2019-11-18 14:08:07 +08:00
} else {
this.ctx.fillStyle = (view.css.color || 'black');
const {
lines,
lineHeight,
textArray,
linesArray,
} = extra;
// 如果设置了id则保留 text 的长度
if (view.id) {
let textWidth = 0;
for (let i = 0; i < textArray.length; ++i) {
textWidth = this.ctx.measureText(textArray[i]).width > textWidth ? this.ctx.measureText(textArray[i]).width : textWidth;
}
this.globalWidth[view.id] = width ? (textWidth < width ? textWidth : width) : textWidth;
if (!this.isMoving) {
Object.assign(this.callbackInfo, {
globalWidth: this.globalWidth[view.id]
})
}
}
let lineIndex = 0;
for (let j = 0; j < textArray.length; ++j) {
const preLineLength = Math.round(textArray[j].length / linesArray[j]);
let start = 0;
let alreadyCount = 0;
for (let i = 0; i < linesArray[j]; ++i) {
// 绘制行数大于最大行数,则直接跳出循环
if (lineIndex >= lines) {
break;
}
alreadyCount = preLineLength;
let text = textArray[j].substr(start, alreadyCount);
let measuredWith = this.ctx.measureText(text).width;
// 如果测量大小小于width一个字符的大小则进行补齐如果测量大小超出 width则进行减除
// 如果已经到文本末尾,也不要进行该循环
while ((start + alreadyCount <= textArray[j].length) && (width - measuredWith > view.css.fontSize.toPx() || measuredWith > width)) {
if (measuredWith < width) {
text = textArray[j].substr(start, ++alreadyCount);
} else {
if (text.length <= 1) {
// 如果只有一个字符时,直接跳出循环
break;
}
text = textArray[j].substr(start, --alreadyCount);
}
measuredWith = this.ctx.measureText(text).width;
}
start += text.length
// 如果是最后一行了,发现还有未绘制完的内容,则加...
if (lineIndex === lines - 1 && (j < textArray.length - 1 || start < textArray[j].length)) {
while (this.ctx.measureText(`${text}...`).width > width) {
if (text.length <= 1) {
// 如果只有一个字符时,直接跳出循环
break;
}
text = text.substring(0, text.length - 1);
}
text += '...';
measuredWith = this.ctx.measureText(text).width;
}
this.ctx.setTextAlign(view.css.textAlign ? view.css.textAlign : 'left');
let x;
switch (view.css.textAlign) {
case 'center':
x = 0;
break;
case 'right':
x = (width / 2);
break;
default:
x = -(width / 2);
break;
}
const y = -(height / 2) + (lineIndex === 0 ? view.css.fontSize.toPx() : (view.css.fontSize.toPx() + lineIndex * lineHeight));
lineIndex++;
if (view.css.textStyle === 'stroke') {
this.ctx.strokeText(text, x, y, measuredWith);
} else {
this.ctx.fillText(text, x, y, measuredWith);
}
const fontSize = view.css.fontSize.toPx();
if (view.css.textDecoration) {
this.ctx.beginPath();
if (/\bunderline\b/.test(view.css.textDecoration)) {
this.ctx.moveTo(x, y);
this.ctx.lineTo(x + measuredWith, y);
!this.isMoving && (this.callbackInfo.textDecoration = {
moveTo: [x, y],
lineTo: [x + measuredWith, y]
})
}
if (/\boverline\b/.test(view.css.textDecoration)) {
this.ctx.moveTo(x, y - fontSize);
this.ctx.lineTo(x + measuredWith, y - fontSize);
!this.isMoving && (this.callbackInfo.textDecoration = {
moveTo: [x, y - fontSize],
lineTo: [x + measuredWith, y - fontSize]
})
}
if (/\bline-through\b/.test(view.css.textDecoration)) {
this.ctx.moveTo(x, y - fontSize / 3);
this.ctx.lineTo(x + measuredWith, y - fontSize / 3);
!this.isMoving && (this.callbackInfo.textDecoration = {
moveTo: [x, y - fontSize / 3],
lineTo: [x + measuredWith, y - fontSize / 3]
})
}
this.ctx.closePath();
this.ctx.strokeStyle = view.css.color;
this.ctx.stroke();
}
if (!this.isMoving) {
this.callbackInfo.lineArray ? this.callbackInfo.lineArray.push({ text, x, y, measuredWith }) : this.callbackInfo.lineArray = [{ text, x, y, measuredWith }]
}
}
}
2018-07-11 16:54:16 +08:00
}
2018-07-05 15:27:12 +08:00
this.ctx.restore();
2018-07-20 18:59:47 +08:00
this._doBorder(view, width, height);
2018-07-05 15:27:12 +08:00
}
_drawAbsRect(view) {
this.ctx.save();
const {
2018-07-05 18:00:45 +08:00
width,
height,
2018-07-05 15:27:12 +08:00
} = this._preProcess(view);
2019-01-04 14:41:32 +08:00
if (GD.api.isGradient(view.css.color)) {
GD.api.doGradient(view.css.color, width, height, this.ctx);
} else {
this.ctx.fillStyle = view.css.color;
2019-01-04 14:41:32 +08:00
}
const { borderRadius, borderStyle, borderWidth } = view.css
this._border({ borderRadius, width, height, borderWidth, borderStyle })
2019-10-06 17:13:10 +08:00
this.ctx.fill();
2018-07-05 15:27:12 +08:00
this.ctx.restore();
2018-07-20 18:59:47 +08:00
this._doBorder(view, width, height);
2018-07-05 15:27:12 +08:00
}
2019-01-21 15:03:21 +08:00
// shadow 支持 (x, y, blur, color), 不支持 spread
// shadow:0px 0px 10px rgba(0,0,0,0.1);
2019-01-21 15:03:21 +08:00
_doShadow(view) {
if (!view.css || !view.css.shadow) {
return;
}
2019-11-20 11:10:21 +08:00
const box = view.css.shadow.replace(/,\s+/g, ',').split(/\s+/);
if (box.length > 4) {
2019-01-21 15:03:21 +08:00
console.error('shadow don\'t spread option');
return;
}
2019-06-10 12:21:03 +08:00
this.ctx.shadowOffsetX = parseInt(box[0], 10);
this.ctx.shadowOffsetY = parseInt(box[1], 10);
this.ctx.shadowBlur = parseInt(box[2], 10);
this.ctx.shadowColor = box[3];
}
2018-07-05 15:27:12 +08:00
_getAngle(angle) {
return Number(angle) * Math.PI / 180;
}
}