初始化代码
This commit is contained in:
219
uniapp/uni-app/components/miniprogram_npm/wxml-to-canvas/draw.js
Normal file
219
uniapp/uni-app/components/miniprogram_npm/wxml-to-canvas/draw.js
Normal file
@@ -0,0 +1,219 @@
|
||||
class Draw {
|
||||
constructor(canvas, context) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = context;
|
||||
}
|
||||
|
||||
roundRect(x, y, w, h, r, fill = true, stroke = false) {
|
||||
if (r < 0) return;
|
||||
const ctx = this.ctx;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2);
|
||||
ctx.arc(x + w - r, y + r, r, Math.PI * 3 / 2, 0);
|
||||
ctx.arc(x + w - r, y + h - r, r, 0, Math.PI / 2);
|
||||
ctx.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI);
|
||||
ctx.lineTo(x, y + r);
|
||||
if (stroke) ctx.stroke();
|
||||
if (fill) ctx.fill();
|
||||
}
|
||||
|
||||
drawView(box, style) {
|
||||
const ctx = this.ctx;
|
||||
const {
|
||||
left: x,
|
||||
top: y,
|
||||
width: w,
|
||||
height: h
|
||||
} = box;
|
||||
const {
|
||||
borderRadius = 0,
|
||||
borderWidth = 0,
|
||||
borderColor,
|
||||
color = '#000',
|
||||
backgroundColor = 'transparent'
|
||||
} = style;
|
||||
ctx.save(); // 外环
|
||||
|
||||
if (borderWidth > 0) {
|
||||
ctx.fillStyle = borderColor || color;
|
||||
this.roundRect(x, y, w, h, borderRadius);
|
||||
} // 内环
|
||||
|
||||
|
||||
ctx.fillStyle = backgroundColor;
|
||||
const innerWidth = w - 2 * borderWidth;
|
||||
const innerHeight = h - 2 * borderWidth;
|
||||
const innerRadius = borderRadius - borderWidth >= 0 ? borderRadius - borderWidth : 0;
|
||||
this.roundRect(x + borderWidth, y + borderWidth, innerWidth, innerHeight, innerRadius);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
async drawImage(img, box, style) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const ctx = this.ctx;
|
||||
const canvas = this.canvas;
|
||||
const {
|
||||
borderRadius = 0
|
||||
} = style;
|
||||
const {
|
||||
left: x,
|
||||
top: y,
|
||||
width: w,
|
||||
height: h
|
||||
} = box;
|
||||
ctx.save();
|
||||
this.roundRect(x, y, w, h, borderRadius, false, false);
|
||||
ctx.clip();
|
||||
const Image = canvas.createImage();
|
||||
|
||||
Image.onload = () => {
|
||||
ctx.drawImage(Image, x, y, w, h);
|
||||
ctx.restore();
|
||||
resolve();
|
||||
};
|
||||
|
||||
Image.onerror = () => {
|
||||
reject();
|
||||
};
|
||||
|
||||
Image.src = img;
|
||||
});
|
||||
} // eslint-disable-next-line complexity
|
||||
|
||||
|
||||
drawText(text, box, style) {
|
||||
const ctx = this.ctx;
|
||||
let {
|
||||
left: x,
|
||||
top: y,
|
||||
width: w,
|
||||
height: h
|
||||
} = box;
|
||||
let {
|
||||
color = '#000',
|
||||
lineHeight = '1.4em',
|
||||
fontSize = 14,
|
||||
textAlign = 'left',
|
||||
verticalAlign = 'top',
|
||||
backgroundColor = 'transparent'
|
||||
} = style;
|
||||
if (!text || lineHeight > h) return;
|
||||
ctx.save();
|
||||
|
||||
if (lineHeight) {
|
||||
// 2em
|
||||
lineHeight = Math.ceil(parseFloat(lineHeight.replace('em')) * fontSize);
|
||||
}
|
||||
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.font = `${fontSize}px sans-serif`;
|
||||
ctx.textAlign = textAlign; // 背景色
|
||||
|
||||
ctx.fillStyle = backgroundColor;
|
||||
this.roundRect(x, y, w, h, 0); // 文字颜色
|
||||
|
||||
ctx.fillStyle = color; // 水平布局
|
||||
|
||||
switch (textAlign) {
|
||||
case 'left':
|
||||
break;
|
||||
|
||||
case 'center':
|
||||
x += 0.5 * w;
|
||||
break;
|
||||
|
||||
case 'right':
|
||||
x += w;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const textWidth = ctx.measureText(text).width;
|
||||
const actualHeight = Math.ceil(textWidth / w) * lineHeight;
|
||||
let paddingTop = Math.ceil((h - actualHeight) / 2);
|
||||
if (paddingTop < 0) paddingTop = 0; // 垂直布局
|
||||
|
||||
switch (verticalAlign) {
|
||||
case 'top':
|
||||
break;
|
||||
|
||||
case 'middle':
|
||||
y += paddingTop;
|
||||
break;
|
||||
|
||||
case 'bottom':
|
||||
y += 2 * paddingTop;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const inlinePaddingTop = Math.ceil((lineHeight - fontSize) / 2); // 不超过一行
|
||||
|
||||
if (textWidth <= w) {
|
||||
ctx.fillText(text, x, y + inlinePaddingTop);
|
||||
return;
|
||||
} // 多行文本
|
||||
|
||||
|
||||
const chars = text.split('');
|
||||
const _y = y; // 逐行绘制
|
||||
|
||||
let line = '';
|
||||
|
||||
for (const ch of chars) {
|
||||
const testLine = line + ch;
|
||||
const testWidth = ctx.measureText(testLine).width;
|
||||
|
||||
if (testWidth > w) {
|
||||
ctx.fillText(line, x, y + inlinePaddingTop);
|
||||
y += lineHeight;
|
||||
line = ch;
|
||||
if (y + lineHeight > _y + h) break;
|
||||
} else {
|
||||
line = testLine;
|
||||
}
|
||||
} // 避免溢出
|
||||
|
||||
|
||||
if (y + lineHeight <= _y + h) {
|
||||
ctx.fillText(line, x, y + inlinePaddingTop);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
async drawNode(element) {
|
||||
const {
|
||||
layoutBox,
|
||||
computedStyle,
|
||||
name
|
||||
} = element;
|
||||
const {
|
||||
src,
|
||||
text
|
||||
} = element.attributes;
|
||||
|
||||
if (name === 'view') {
|
||||
this.drawView(layoutBox, computedStyle);
|
||||
} else if (name === 'image') {
|
||||
await this.drawImage(src, layoutBox, computedStyle);
|
||||
} else if (name === 'text') {
|
||||
this.drawText(text, layoutBox, computedStyle);
|
||||
}
|
||||
|
||||
const childs = Object.values(element.children);
|
||||
|
||||
for (const child of childs) {
|
||||
await this.drawNode(child);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Draw
|
||||
};
|
||||
Reference in New Issue
Block a user