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); const isBase64 = /^data:image\/(\w+);base64,/.test(img);
if (isTempFile) { _drawImage(img); } else if (isBase64) { _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}`)); } }); }
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') { 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); } } }
export default Draw;
|