UNPKG

x-data-spreadsheet

Version:
404 lines (367 loc) 9.04 kB
/* global window */ function dpr() { return window.devicePixelRatio || 1; } function thinLineWidth() { return dpr() - 0.5; } function npx(px) { return parseInt(px * dpr(), 10); } function npxLine(px) { const n = npx(px); return n > 0 ? n - 0.5 : 0.5; } class DrawBox { constructor(x, y, w, h, padding = 0) { this.x = x; this.y = y; this.width = w; this.height = h; this.padding = padding; this.bgcolor = '#ffffff'; // border: [width, style, color] this.borderTop = null; this.borderRight = null; this.borderBottom = null; this.borderLeft = null; } setBorders({ top, bottom, left, right, }) { if (top) this.borderTop = top; if (right) this.borderRight = right; if (bottom) this.borderBottom = bottom; if (left) this.borderLeft = left; } innerWidth() { return this.width - (this.padding * 2) - 2; } innerHeight() { return this.height - (this.padding * 2) - 2; } textx(align) { const { width, padding } = this; let { x } = this; if (align === 'left') { x += padding; } else if (align === 'center') { x += width / 2; } else if (align === 'right') { x += width - padding; } return x; } texty(align, h) { const { height, padding } = this; let { y } = this; if (align === 'top') { y += padding; } else if (align === 'middle') { y += height / 2 - h / 2; } else if (align === 'bottom') { y += height - padding - h; } return y; } topxys() { const { x, y, width } = this; return [[x, y], [x + width, y]]; } rightxys() { const { x, y, width, height, } = this; return [[x + width, y], [x + width, y + height]]; } bottomxys() { const { x, y, width, height, } = this; return [[x, y + height], [x + width, y + height]]; } leftxys() { const { x, y, height, } = this; return [[x, y], [x, y + height]]; } } function drawFontLine(type, tx, ty, align, valign, blheight, blwidth) { const floffset = { x: 0, y: 0 }; if (type === 'underline') { if (valign === 'bottom') { floffset.y = 0; } else if (valign === 'top') { floffset.y = -(blheight + 2); } else { floffset.y = -blheight / 2; } } else if (type === 'strike') { if (valign === 'bottom') { floffset.y = blheight / 2; } else if (valign === 'top') { floffset.y = -((blheight / 2) + 2); } } if (align === 'center') { floffset.x = blwidth / 2; } else if (align === 'right') { floffset.x = blwidth; } this.line( [tx - floffset.x, ty - floffset.y], [tx - floffset.x + blwidth, ty - floffset.y], ); } class Draw { constructor(el, width, height) { this.el = el; this.ctx = el.getContext('2d'); this.resize(width, height); this.ctx.scale(dpr(), dpr()); } resize(width, height) { // console.log('dpr:', dpr); this.el.style.width = `${width}px`; this.el.style.height = `${height}px`; this.el.width = npx(width); this.el.height = npx(height); } clear() { const { width, height } = this.el; this.ctx.clearRect(0, 0, width, height); return this; } attr(options) { Object.assign(this.ctx, options); return this; } save() { this.ctx.save(); this.ctx.beginPath(); return this; } restore() { this.ctx.restore(); return this; } beginPath() { this.ctx.beginPath(); return this; } translate(x, y) { this.ctx.translate(npx(x), npx(y)); return this; } scale(x, y) { this.ctx.scale(x, y); return this; } clearRect(x, y, w, h) { this.ctx.clearRect(x, y, w, h); return this; } fillRect(x, y, w, h) { this.ctx.fillRect(npx(x) - 0.5, npx(y) - 0.5, npx(w), npx(h)); return this; } fillText(text, x, y) { this.ctx.fillText(text, npx(x), npx(y)); return this; } /* txt: render text box: DrawBox attr: { align: left | center | right valign: top | middle | bottom color: '#333333', strike: false, font: { name: 'Arial', size: 14, bold: false, italic: false, } } textWrap: text wrapping */ text(mtxt, box, attr = {}, textWrap = true) { const { ctx } = this; const { align, valign, font, color, strike, underline, } = attr; const tx = box.textx(align); ctx.save(); ctx.beginPath(); this.attr({ textAlign: align, textBaseline: valign, font: `${font.italic ? 'italic' : ''} ${font.bold ? 'bold' : ''} ${npx(font.size)}px ${font.name}`, fillStyle: color, strokeStyle: color, }); const txts = `${mtxt}`.split('\n'); const biw = box.innerWidth(); const ntxts = []; txts.forEach((it) => { const txtWidth = ctx.measureText(it).width; if (textWrap && txtWidth > npx(biw)) { let textLine = { w: 0, len: 0, start: 0 }; for (let i = 0; i < it.length; i += 1) { if (textLine.w >= npx(biw)) { ntxts.push(it.substr(textLine.start, textLine.len)); textLine = { w: 0, len: 0, start: i }; } textLine.len += 1; textLine.w += ctx.measureText(it[i]).width + 1; } if (textLine.len > 0) { ntxts.push(it.substr(textLine.start, textLine.len)); } } else { ntxts.push(it); } }); const txtHeight = (ntxts.length - 1) * (font.size + 2); let ty = box.texty(valign, txtHeight); ntxts.forEach((txt) => { const txtWidth = ctx.measureText(txt).width; this.fillText(txt, tx, ty); if (strike) { drawFontLine.call(this, 'strike', tx, ty, align, valign, font.size, txtWidth); } if (underline) { drawFontLine.call(this, 'underline', tx, ty, align, valign, font.size, txtWidth); } ty += font.size + 2; }); ctx.restore(); return this; } border(style, color) { const { ctx } = this; ctx.lineWidth = thinLineWidth; ctx.strokeStyle = color; // console.log('style:', style); if (style === 'medium') { ctx.lineWidth = npx(2) - 0.5; } else if (style === 'thick') { ctx.lineWidth = npx(3); } else if (style === 'dashed') { ctx.setLineDash([npx(3), npx(2)]); } else if (style === 'dotted') { ctx.setLineDash([npx(1), npx(1)]); } else if (style === 'double') { ctx.setLineDash([npx(2), 0]); } return this; } line(...xys) { const { ctx } = this; if (xys.length > 1) { ctx.beginPath(); const [x, y] = xys[0]; ctx.moveTo(npxLine(x), npxLine(y)); for (let i = 1; i < xys.length; i += 1) { const [x1, y1] = xys[i]; ctx.lineTo(npxLine(x1), npxLine(y1)); } ctx.stroke(); } return this; } strokeBorders(box) { const { ctx } = this; ctx.save(); // border const { borderTop, borderRight, borderBottom, borderLeft, } = box; if (borderTop) { this.border(...borderTop); // console.log('box.topxys:', box.topxys()); this.line(...box.topxys()); } if (borderRight) { this.border(...borderRight); this.line(...box.rightxys()); } if (borderBottom) { this.border(...borderBottom); this.line(...box.bottomxys()); } if (borderLeft) { this.border(...borderLeft); this.line(...box.leftxys()); } ctx.restore(); } dropdown(box) { const { ctx } = this; const { x, y, width, height, } = box; const sx = x + width - 15; const sy = y + height - 15; ctx.save(); ctx.beginPath(); ctx.moveTo(npx(sx), npx(sy)); ctx.lineTo(npx(sx + 8), npx(sy)); ctx.lineTo(npx(sx + 4), npx(sy + 6)); ctx.closePath(); ctx.fillStyle = 'rgba(0, 0, 0, .45)'; ctx.fill(); ctx.restore(); } error(box) { const { ctx } = this; const { x, y, width } = box; const sx = x + width - 1; ctx.save(); ctx.beginPath(); ctx.moveTo(npx(sx - 8), npx(y - 1)); ctx.lineTo(npx(sx), npx(y - 1)); ctx.lineTo(npx(sx), npx(y + 8)); ctx.closePath(); ctx.fillStyle = 'rgba(255, 0, 0, .65)'; ctx.fill(); ctx.restore(); } frozen(box) { const { ctx } = this; const { x, y, width } = box; const sx = x + width - 1; ctx.save(); ctx.beginPath(); ctx.moveTo(npx(sx - 8), npx(y - 1)); ctx.lineTo(npx(sx), npx(y - 1)); ctx.lineTo(npx(sx), npx(y + 8)); ctx.closePath(); ctx.fillStyle = 'rgba(0, 255, 0, .85)'; ctx.fill(); ctx.restore(); } rect(box, dtextcb) { const { ctx } = this; const { x, y, width, height, bgcolor, } = box; ctx.save(); ctx.beginPath(); ctx.fillStyle = bgcolor || '#fff'; ctx.rect(npxLine(x + 1), npxLine(y + 1), npx(width - 2), npx(height - 2)); ctx.clip(); ctx.fill(); dtextcb(); ctx.restore(); } } export default {}; export { Draw, DrawBox, thinLineWidth, npx, };