| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const { createCanvas } = require('canvas') |
|
|
| const ZERO_PAD = { t: 0, r: 0, b: 0, l: 0 } |
|
|
| function normPad (pad) { |
| if (!pad) return ZERO_PAD |
| if (typeof pad === 'number') return { t: pad, r: pad, b: pad, l: pad } |
| return { t: pad.t || 0, r: pad.r || 0, b: pad.b || 0, l: pad.l || 0 } |
| } |
|
|
| |
| |
| |
| |
| |
| function leaf (canvas, opts = {}) { |
| if (!canvas) return null |
| if (canvas.width <= 1 && canvas.height <= 1) return null |
| let w = opts.w !== undefined ? opts.w : canvas.width |
| if (opts.maxW && w > opts.maxW) w = opts.maxW |
| return { |
| kind: 'leaf', |
| canvas, |
| srcY: 0, |
| srcH: canvas.height, |
| w, |
| h: opts.h !== undefined ? opts.h : canvas.height, |
| bleed: !!opts.bleed, |
| paint: opts.paint || null |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function box (opts = {}) { |
| return { |
| kind: 'box', |
| dir: opts.dir || 'col', |
| gap: opts.gap || 0, |
| pad: normPad(opts.pad), |
| align: opts.align || 'start', |
| justify: opts.justify || 'start', |
| stretch: !!opts.stretch, |
| bg: opts.bg || null, |
| fg: opts.fg || null, |
| minW: opts.minW || 0, |
| maxW: opts.maxW || 0, |
| children: (opts.children || []).filter(Boolean) |
| } |
| } |
|
|
| |
| |
| |
| function gapBefore (box, child, index) { |
| if (index === 0) return 0 |
| return child.mt !== undefined ? child.mt : box.gap |
| } |
|
|
| |
| function measure (n) { |
| if (n.kind === 'leaf') return n |
| let w = 0 |
| let h = 0 |
| for (const c of n.children) measure(c) |
| if (n.dir === 'col') { |
| |
| let outerW = 0 |
| for (let i = 0; i < n.children.length; i++) { |
| const c = n.children[i] |
| const cw = c.bleed ? c.w : c.w + n.pad.l + n.pad.r |
| if (cw > outerW) outerW = cw |
| h += gapBefore(n, c, i) + c.h |
| } |
| n.w = outerW |
| } else { |
| for (const c of n.children) { |
| if (c.h > h) h = c.h |
| w += c.w |
| } |
| w += n.gap * Math.max(0, n.children.length - 1) |
| n.w = w + n.pad.l + n.pad.r |
| } |
| |
| |
| n.w = Math.ceil(n.w) |
| n.h = Math.ceil(h + n.pad.t + n.pad.b) |
| if (n.w < n.minW) n.w = n.minW |
| |
| |
| if (n.maxW && n.w > n.maxW) n.w = n.maxW |
| return n |
| } |
|
|
| |
| |
| |
| |
| |
| function place (n, x, y, stretchW) { |
| if (stretchW !== undefined && n.stretch) n.w = stretchW |
| n.x = Math.round(x) |
| n.y = Math.round(y) |
| x = n.x |
| y = n.y |
| if (n.kind === 'leaf') return |
| const innerW = n.w - n.pad.l - n.pad.r |
| if (n.dir === 'col') { |
| let cy = y + n.pad.t |
| for (let i = 0; i < n.children.length; i++) { |
| const c = n.children[i] |
| let cx = x + n.pad.l |
| if (c.bleed) cx = x + Math.max(0, (n.w - c.w) / 2) |
| else if (n.align === 'center' && c.w < innerW) cx += (innerW - c.w) / 2 |
| cy += gapBefore(n, c, i) |
| place(c, cx, cy, innerW) |
| cy += c.h |
| } |
| } else { |
| const crossY = (c) => n.align === 'center' |
| ? y + n.pad.t + (n.h - n.pad.t - n.pad.b - c.h) / 2 |
| : y + n.pad.t |
| if (n.justify === 'between' && n.children.length === 2) { |
| const [a, b] = n.children |
| |
| const availA = innerW - b.w - n.gap |
| if (a.w > availA) a.w = Math.max(0, availA) |
| place(a, x + n.pad.l, crossY(a)) |
| place(b, x + n.w - n.pad.r - b.w, crossY(b)) |
| return |
| } |
| let cx = x + n.pad.l |
| for (const c of n.children) { |
| place(c, cx, crossY(c)) |
| cx += c.w + n.gap |
| } |
| } |
| } |
|
|
| |
| function render (ctx, n) { |
| if (n.bg) n.bg(ctx, n) |
| if (n.kind === 'leaf') { |
| if (n.paint) { |
| n.paint(ctx, n) |
| } else if (n.canvas.width > n.w + 1) { |
| |
| |
| ctx.drawImage(fadeOverflow(n), n.x, n.y) |
| } else { |
| ctx.drawImage(n.canvas, 0, n.srcY, n.canvas.width, n.srcH, n.x, n.y, n.canvas.width, n.srcH) |
| } |
| } else { |
| for (const c of n.children) render(ctx, c) |
| } |
| if (n.fg) n.fg(ctx, n) |
| } |
|
|
| |
| |
| |
| |
| |
| function fadeOverflow (n) { |
| const w = Math.max(1, Math.round(n.w)) |
| const out = createCanvas(w, n.srcH) |
| const ctx = out.getContext('2d') |
| ctx.drawImage(n.canvas, 0, n.srcY, w, n.srcH, 0, 0, w, n.srcH) |
|
|
| |
| const fadeW = Math.min(w, Math.round(n.srcH * 0.9)) |
| const grad = ctx.createLinearGradient(w - fadeW, 0, w, 0) |
| grad.addColorStop(0, 'rgba(0, 0, 0, 0)') |
| grad.addColorStop(1, 'rgba(0, 0, 0, 1)') |
| ctx.globalCompositeOperation = 'destination-out' |
| ctx.fillStyle = grad |
| ctx.fillRect(w - fadeW, 0, fadeW, n.srcH) |
| return out |
| } |
|
|
| module.exports = { leaf, box, measure, place, render } |
|
|