EP-WachatApp/eaterplanet_ecommerce/components/painter/lib/pen.js

904 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const QR = require('./qrcode.js');
const GD = require('./gradient.js');
require('./string-polyfill.js');
export const penCache = {
// 用于存储带 id 的 view 的 rect 信息
viewRect: {},
textLines: {},
};
export const clearPenCache = id => {
if (id) {
penCache.viewRect[id] = null;
penCache.textLines[id] = null;
} else {
penCache.viewRect = {};
penCache.textLines = {};
}
};
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 && callback();
});
}
_background() {
this.ctx.save();
const { width, height } = this.style;
const bg = this.data.background;
this.ctx.translate(width / 2, height / 2);
this._doClip(this.data.borderRadius, width, height);
if (!bg) {
// 如果未设置背景,则默认使用透明色
this.ctx.fillStyle = 'transparent';
this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
} else if (bg.startsWith('#') || bg.startsWith('rgba') || bg.toLowerCase() === 'transparent') {
// 背景填充颜色
this.ctx.fillStyle = bg;
this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
} else if (GD.api.isGradient(bg)) {
GD.api.doGradient(bg, width, height, this.ctx);
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) {
if (!(view && view.type)) {
// 过滤无效 view
return;
}
// 证明 css 为数组形式,需要合并
if (view.css && 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 'inlineText':
this._fillAbsInlineText(view);
break;
case 'rect':
this._drawAbsRect(view);
break;
case 'qrcode':
this._drawQRCode(view);
break;
default:
break;
}
}
_border({ borderRadius = 0, width, height, borderWidth = 0, borderStyle = 'solid' }) {
let r1 = 0,
r2 = 0,
r3 = 0,
r4 = 0;
const minSize = Math.min(width, height);
if (borderRadius) {
const border = borderRadius.split(/\s+/);
if (border.length === 4) {
r1 = Math.min(border[0].toPx(false, minSize), width / 2, height / 2);
r2 = Math.min(border[1].toPx(false, minSize), width / 2, height / 2);
r3 = Math.min(border[2].toPx(false, minSize), width / 2, height / 2);
r4 = Math.min(border[3].toPx(false, minSize), width / 2, height / 2);
} else {
r1 = r2 = r3 = r4 = Math.min(borderRadius && borderRadius.toPx(false, minSize), width / 2, height / 2);
}
}
const lineWidth = borderWidth && borderWidth.toPx(false, minSize);
this.ctx.lineWidth = lineWidth;
if (borderStyle === 'dashed') {
this.ctx.setLineDash([(lineWidth * 4) / 3, (lineWidth * 4) / 3]);
// this.ctx.lineDashOffset = 2 * lineWidth
} else if (borderStyle === 'dotted') {
this.ctx.setLineDash([lineWidth, lineWidth]);
}
const notSolid = borderStyle !== 'solid';
this.ctx.beginPath();
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,
); // 顶边线
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,
); // 右边线
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,
); // 底边线
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,
); // 左边线
notSolid && r1 === 0 && this.ctx.moveTo(-width / 2 - lineWidth, -height / 2 - lineWidth / 2); // 顶边虚线规避重叠规则
if (!notSolid) {
this.ctx.closePath();
}
}
/**
* 根据 borderRadius 进行裁减
*/
_doClip(borderRadius, width, height, borderStyle) {
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,
});
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();
}
this.ctx.globalAlpha = 1;
}
}
/**
* 画边框
*/
_doBorder(view, width, height) {
if (!view.css) {
return;
}
const { borderRadius, borderWidth, borderColor, borderStyle } = view.css;
if (!borderWidth) {
return;
}
this.ctx.save();
this._preProcess(view, true);
this.ctx.strokeStyle = borderColor || 'black';
this._border({
borderRadius,
width,
height,
borderWidth,
borderStyle,
});
this.ctx.stroke();
this.ctx.restore();
}
_preProcess(view, notClip) {
let width = 0;
let height;
let extra;
const paddings = this._doPaddings(view);
switch (view.type) {
case 'inlineText': {
{
// 计算行数
let lines = 0;
// 文字总长度
let textLength = 0;
// 行高
let lineHeight = 0;
const textList = view.textList || [];
for (let i = 0; i < textList.length; i++) {
let subView = textList[i];
const fontWeight = subView.css.fontWeight || '400';
const textStyle = subView.css.textStyle || 'normal';
if (!subView.css.fontSize) {
subView.css.fontSize = '20rpx';
}
this.ctx.font = `${textStyle} ${fontWeight} ${subView.css.fontSize.toPx()}px "${subView.css.fontFamily || 'sans-serif'}"`;
textLength += this.ctx.measureText(subView.text).width;
let tempLineHeight = subView.css.lineHeight ? subView.css.lineHeight.toPx() : subView.css.fontSize.toPx();
lineHeight = Math.max(lineHeight, tempLineHeight);
}
width = view.css.width ? view.css.width.toPx(false, this.style.width) - paddings[1] - paddings[3] : textLength;;
const calLines = Math.ceil(textLength / width);
lines += calLines;
// lines = view.css.maxLines < lines ? view.css.maxLines : lines;
height = lineHeight * lines;
extra = {
lines: lines,
lineHeight: lineHeight,
// textArray: textArray,
// linesArray: linesArray,
};
}
break;
}
case 'text': {
const textArray = String(view.text).split('\n');
// 处理多个连续的'\n'
for (let i = 0; i < textArray.length; ++i) {
if (textArray[i] === '') {
textArray[i] = ' ';
}
}
const fontWeight = view.css.fontWeight || '400';
const textStyle = view.css.textStyle || 'normal';
if (!view.css.fontSize) {
view.css.fontSize = '20rpx';
}
this.ctx.font = `${textStyle} ${fontWeight} ${view.css.fontSize.toPx()}px "${
view.css.fontFamily || 'sans-serif'
}"`;
// 计算行数
let lines = 0;
const linesArray = [];
for (let i = 0; i < textArray.length; ++i) {
const textLength = this.ctx.measureText(textArray[i]).width;
const minWidth = view.css.fontSize.toPx() + paddings[1] + paddings[3];
let partWidth = view.css.width
? view.css.width.toPx(false, this.style.width) - paddings[1] - paddings[3]
: textLength;
if (partWidth < minWidth) {
partWidth = minWidth;
}
const calLines = Math.ceil(textLength / partWidth);
// 取最长的作为 width
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,
lineHeight: lineHeight,
textArray: textArray,
linesArray: linesArray,
};
break;
}
case 'image': {
// 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')) {
width = Math.round(view.sWidth / ratio);
height = Math.round(view.sHeight / ratio);
} else if (view.css.width === 'auto') {
height = view.css.height.toPx(false, this.style.height);
width = (view.sWidth / view.sHeight) * height;
} else if (view.css.height === 'auto') {
width = view.css.width.toPx(false, this.style.width);
height = (view.sHeight / view.sWidth) * width;
} else {
width = view.css.width.toPx(false, this.style.width);
height = view.css.height.toPx(false, this.style.height);
}
break;
}
default:
if (!(view.css.width && view.css.height)) {
console.error('You should set width and height');
return;
}
width = view.css.width.toPx(false, this.style.width);
height = view.css.height.toPx(false, this.style.height);
break;
}
let x;
if (view.css && view.css.right) {
if (typeof view.css.right === 'string') {
x = this.style.width - view.css.right.toPx(true, this.style.width);
} else {
// 可以用数组方式,把文字长度计算进去
// [right, 文字id, 乘数(默认 1]
const rights = view.css.right;
x =
this.style.width -
rights[0].toPx(true, this.style.width) -
penCache.viewRect[rights[1]].width * (rights[2] || 1);
}
} else if (view.css && view.css.left) {
if (typeof view.css.left === 'string') {
x = view.css.left.toPx(true, this.style.width);
} else {
const lefts = view.css.left;
x = lefts[0].toPx(true, this.style.width) + penCache.viewRect[lefts[1]].width * (lefts[2] || 1);
}
} else {
x = 0;
}
//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) {
y = this.style.height - height - view.css.bottom.toPx(true, this.style.height);
} else {
if (view.css && view.css.top) {
if (typeof view.css.top === 'string') {
y = view.css.top.toPx(true, this.style.height);
} else {
const tops = view.css.top;
y = tops[0].toPx(true, this.style.height) + penCache.viewRect[tops[1]].height * (tops[2] || 1);
}
} else {
y = 0;
}
}
const angle = view.css && view.css.rotate ? this._getAngle(view.css.rotate) : 0;
// 当设置了 right 时,默认 align 用 right反之用 left
const align = view.css && view.css.align ? view.css.align : view.css && view.css.right ? 'right' : 'left';
const verticalAlign = view.css && view.css.verticalAlign ? view.css.verticalAlign : 'top';
// 记录绘制时的画布
let xa = 0;
switch (align) {
case 'center':
xa = x;
break;
case 'right':
xa = x - width / 2;
break;
default:
xa = x + width / 2;
break;
}
let ya = 0;
switch (verticalAlign) {
case 'center':
ya = y;
break;
case 'bottom':
ya = y - height / 2;
break;
default:
ya = y + height / 2;
break;
}
this.ctx.translate(xa, ya);
// 记录该 view 的有效点击区域
// TODO ,旋转和裁剪的判断
// 记录在真实画布上的左侧
let left = x;
if (align === 'center') {
left = x - width / 2;
} else if (align === 'right') {
left = x - width;
}
var top = y;
if (verticalAlign === 'center') {
top = y - height / 2;
} else if (verticalAlign === 'bottom') {
top = y - height;
}
if (view.rect) {
view.rect.left = left;
view.rect.top = top;
view.rect.right = left + width;
view.rect.bottom = top + height;
view.rect.x = view.css && view.css.right ? x - width : x;
view.rect.y = y;
} else {
view.rect = {
left: left,
top: top,
right: left + width,
bottom: top + height,
x: view.css && view.css.right ? x - width : x,
y: y,
};
}
view.rect.left = view.rect.left - paddings[3];
view.rect.top = view.rect.top - paddings[0];
view.rect.right = view.rect.right + paddings[1];
view.rect.bottom = view.rect.bottom + paddings[2];
if (view.type === 'text') {
view.rect.minWidth = view.css.fontSize.toPx() + paddings[1] + paddings[3];
}
this.ctx.rotate(angle);
if (!notClip && view.css && view.css.borderRadius && view.type !== 'rect') {
this._doClip(view.css.borderRadius, width, height, view.css.borderStyle);
}
this._doShadow(view);
if (view.id) {
penCache.viewRect[view.id] = {
width,
height,
left: view.rect.left,
top: view.rect.top,
right: view.rect.right,
bottom: view.rect.bottom,
};
}
return {
width: width,
height: height,
x: x,
y: y,
extra: extra,
};
}
_doPaddings(view) {
const { padding } = view.css ? view.css : {};
let pd = [0, 0, 0, 0];
if (padding) {
const pdg = padding.split(/\s+/);
if (pdg.length === 1) {
const x = pdg[0].toPx();
pd = [x, x, x, x];
}
if (pdg.length === 2) {
const x = pdg[0].toPx();
const y = pdg[1].toPx();
pd = [x, y, x, y];
}
if (pdg.length === 3) {
const x = pdg[0].toPx();
const y = pdg[1].toPx();
const z = pdg[2].toPx();
pd = [x, y, z, y];
}
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];
}
}
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];
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();
}
_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, view.css.color);
this.ctx.restore();
this._doBorder(view, width, height);
}
_drawAbsImage(view) {
if (!view.url) {
return;
}
this.ctx.save();
const { width, height } = this._preProcess(view);
// 获得缩放到图片大小级别的裁减框
let rWidth = view.sWidth;
let rHeight = view.sHeight;
let startX = 0;
let startY = 0;
// 绘画区域比例
const cp = width / height;
// 原图比例
const op = view.sWidth / view.sHeight;
if (cp >= op) {
rHeight = rWidth / cp;
startY = Math.round((view.sHeight - rHeight) / 2);
} else {
rWidth = rHeight * cp;
startX = Math.round((view.sWidth - rWidth) / 2);
}
if (view.css && view.css.mode === 'scaleToFill') {
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);
view.rect.startX = startX / view.sWidth;
view.rect.startY = startY / view.sHeight;
view.rect.endX = (startX + rWidth) / view.sWidth;
view.rect.endY = (startY + rHeight) / view.sHeight;
}
this.ctx.restore();
this._doBorder(view, width, height);
}
/**
*
* @param {*} view
* @description 一行内文字多样式的方法
*
* 暂不支持配置 text-align默认left
* 暂不支持配置 maxLines
*/
_fillAbsInlineText(view) {
if (!view.textList) {
return;
}
if (view.css.background) {
// 生成背景
this._doBackground(view);
}
this.ctx.save();
const { width, height, extra } = this._preProcess(view, view.css.background && view.css.borderRadius);
const { lines, lineHeight } = extra;
let staticX = -(width / 2);
let lineIndex = 0; // 第几行
let x = staticX; // 开始x位置
let leftWidth = width; // 当前行剩余多少宽度可以使用
let getStyle = css => {
const fontWeight = css.fontWeight || '400';
const textStyle = css.textStyle || 'normal';
if (!css.fontSize) {
css.fontSize = '20rpx';
}
return `${textStyle} ${fontWeight} ${css.fontSize.toPx()}px "${css.fontFamily || 'sans-serif'}"`;
}
// 遍历行内的文字数组
for (let j = 0; j < view.textList.length; j++) {
const subView = view.textList[j];
// 某个文字开始位置
let start = 0;
// 文字已使用的数量
let alreadyCount = 0;
// 文字总长度
let textLength = subView.text.length;
// 文字总宽度
let textWidth = this.ctx.measureText(subView.text).width;
// 每个文字的平均宽度
let preWidth = Math.ceil(textWidth / textLength);
// 循环写文字
while (alreadyCount < textLength) {
// alreadyCount - start + 1 -> 当前摘取出来的文字
// 比较可用宽度,寻找最大可写文字长度
while ((alreadyCount - start + 1) * preWidth < leftWidth && alreadyCount < textLength) {
alreadyCount++;
}
// 取出文字
let text = subView.text.substr(start, alreadyCount - start);
const y = -(height / 2) + subView.css.fontSize.toPx() + lineIndex * lineHeight;
// 设置文字样式
this.ctx.font = getStyle(subView.css);
this.ctx.fillStyle = subView.css.color || 'black';
this.ctx.textAlign = 'left';
// 执行画布操作
if (subView.css.textStyle === 'stroke') {
this.ctx.strokeText(text, x, y);
} else {
this.ctx.fillText(text, x, y);
}
// 当次已使用宽度
let currentUsedWidth = this.ctx.measureText(text).width;
const fontSize = subView.css.fontSize.toPx();
// 画 textDecoration
let textDecoration;
if (subView.css.textDecoration) {
this.ctx.lineWidth = fontSize / 13;
this.ctx.beginPath();
if (/\bunderline\b/.test(subView.css.textDecoration)) {
this.ctx.moveTo(x, y);
this.ctx.lineTo(x + currentUsedWidth, y);
textDecoration = {
moveTo: [x, y],
lineTo: [x + currentUsedWidth, y],
};
}
if (/\boverline\b/.test(subView.css.textDecoration)) {
this.ctx.moveTo(x, y - fontSize);
this.ctx.lineTo(x + currentUsedWidth, y - fontSize);
textDecoration = {
moveTo: [x, y - fontSize],
lineTo: [x + currentUsedWidth, y - fontSize],
};
}
if (/\bline-through\b/.test(subView.css.textDecoration)) {
this.ctx.moveTo(x, y - fontSize / 3);
this.ctx.lineTo(x + currentUsedWidth, y - fontSize / 3);
textDecoration = {
moveTo: [x, y - fontSize / 3],
lineTo: [x + currentUsedWidth, y - fontSize / 3],
};
}
this.ctx.closePath();
this.ctx.strokeStyle = subView.css.color;
this.ctx.stroke();
}
// 重置数据
start = alreadyCount;
leftWidth -= currentUsedWidth;
x += currentUsedWidth;
// 如果剩余宽度 小于等于0 或者小于一个字的平均宽度,换行
if (leftWidth <= 0 || leftWidth < preWidth) {
leftWidth = width;
x = staticX;
lineIndex++;
}
}
}
this.ctx.restore();
this._doBorder(view, width, height);
}
_fillAbsText(view) {
if (!view.text) {
return;
}
if (view.css.background) {
// 生成背景
this._doBackground(view);
}
this.ctx.save();
const { width, height, extra } = this._preProcess(view, view.css.background && view.css.borderRadius);
this.ctx.fillStyle = view.css.color || 'black';
if (view.id && penCache.textLines[view.id]) {
this.ctx.textAlign = view.css.textAlign ? view.css.textAlign : 'left';
for (const i of penCache.textLines[view.id]) {
const { measuredWith, text, x, y, textDecoration } = i;
if (view.css.textStyle === 'stroke') {
this.ctx.strokeText(text, x, y, measuredWith);
} else {
this.ctx.fillText(text, x, y, measuredWith);
}
if (textDecoration) {
const fontSize = view.css.fontSize.toPx();
this.ctx.lineWidth = fontSize / 13;
this.ctx.beginPath();
this.ctx.moveTo(...textDecoration.moveTo);
this.ctx.lineTo(...textDecoration.lineTo);
this.ctx.closePath();
this.ctx.strokeStyle = view.css.color;
this.ctx.stroke();
}
}
} else {
const { lines, lineHeight, textArray, linesArray } = extra;
// 如果设置了id则保留 text 的长度
if (view.id) {
let textWidth = 0;
for (let i = 0; i < textArray.length; ++i) {
const _w = this.ctx.measureText(textArray[i]).width;
textWidth = _w > textWidth ? _w : textWidth;
}
penCache.viewRect[view.id].width = width ? (textWidth < width ? textWidth : width) : textWidth;
}
let lineIndex = 0;
for (let j = 0; j < textArray.length; ++j) {
const preLineLength = Math.ceil(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 > view.css.fontSize.toPx())
) {
if (measuredWith < width) {
text = textArray[j].substr(start, ++alreadyCount);
} else {
if (text.length <= 1) {
// 如果只有一个字符时,直接跳出循环
break;
}
text = textArray[j].substr(start, --alreadyCount);
// break;
}
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.textAlign = view.css.textAlign ? view.css.textAlign : 'left';
let x;
let lineX;
switch (view.css.textAlign) {
case 'center':
x = 0;
lineX = x - measuredWith / 2;
break;
case 'right':
x = width / 2;
lineX = x - measuredWith;
break;
default:
x = -(width / 2);
lineX = x;
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();
let textDecoration;
if (view.css.textDecoration) {
this.ctx.lineWidth = fontSize / 13;
this.ctx.beginPath();
if (/\bunderline\b/.test(view.css.textDecoration)) {
this.ctx.moveTo(lineX, y);
this.ctx.lineTo(lineX + measuredWith, y);
textDecoration = {
moveTo: [lineX, y],
lineTo: [lineX + measuredWith, y],
};
}
if (/\boverline\b/.test(view.css.textDecoration)) {
this.ctx.moveTo(lineX, y - fontSize);
this.ctx.lineTo(lineX + measuredWith, y - fontSize);
textDecoration = {
moveTo: [lineX, y - fontSize],
lineTo: [lineX + measuredWith, y - fontSize],
};
}
if (/\bline-through\b/.test(view.css.textDecoration)) {
this.ctx.moveTo(lineX, y - fontSize / 3);
this.ctx.lineTo(lineX + measuredWith, y - fontSize / 3);
textDecoration = {
moveTo: [lineX, y - fontSize / 3],
lineTo: [lineX + measuredWith, y - fontSize / 3],
};
}
this.ctx.closePath();
this.ctx.strokeStyle = view.css.color;
this.ctx.stroke();
}
if (view.id) {
penCache.textLines[view.id]
? penCache.textLines[view.id].push({
text,
x,
y,
measuredWith,
textDecoration,
})
: (penCache.textLines[view.id] = [
{
text,
x,
y,
measuredWith,
textDecoration,
},
]);
}
}
}
}
this.ctx.restore();
this._doBorder(view, width, height);
}
_drawAbsRect(view) {
this.ctx.save();
const { width, height } = this._preProcess(view);
if (GD.api.isGradient(view.css.color)) {
GD.api.doGradient(view.css.color, width, height, this.ctx);
} else {
this.ctx.fillStyle = view.css.color;
}
const { borderRadius, borderStyle, borderWidth } = view.css;
this._border({
borderRadius,
width,
height,
borderWidth,
borderStyle,
});
this.ctx.fill();
this.ctx.restore();
this._doBorder(view, width, height);
}
// shadow 支持 (x, y, blur, color), 不支持 spread
// shadow:0px 0px 10px rgba(0,0,0,0.1);
_doShadow(view) {
if (!view.css || !view.css.shadow) {
return;
}
const box = view.css.shadow.replace(/,\s+/g, ',').split(/\s+/);
if (box.length > 4) {
console.error("shadow don't spread option");
return;
}
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];
}
_getAngle(angle) {
return (Number(angle) * Math.PI) / 180;
}
}