Spaces:
Running
Running
Download libra-scales/js/engine.js from Ethscriptions/POLS: direct link, hf CLI and curl.
- Browser
- Download file 17.1 kB
-
https://huggingface.co/spaces/Ethscriptions/POLS/resolve/main/libra-scales/js/engine.js
- Command line
-
hf download hf://spaces/Ethscriptions/POLS/libra-scales/js/engine.js
-
curl -L -o engine.js https://huggingface.co/spaces/Ethscriptions/POLS/resolve/main/libra-scales/js/engine.js
17.1 kB
| /* ♎ 天秤座 · 星空中的一杆秤 —— 绘图引擎(全新编写) | |
| * 所有绘制都是时间 t 的纯函数,实时播放与逐帧导出完全一致。 */ | |
| (function () { | |
| 'use strict'; | |
| const E = {}; | |
| const TAU = Math.PI * 2; | |
| E.TAU = TAU; | |
| /* ---------- 数学 & 缓动 ---------- */ | |
| E.clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x)); | |
| E.lerp = (a, b, k) => a + (b - a) * k; | |
| E.seg = (t, a, b) => E.clamp((t - a) / (b - a)); | |
| E.smooth = k => k * k * (3 - 2 * k); | |
| E.ease = { | |
| inOut: k => (k < .5 ? 4 * k * k * k : 1 - Math.pow(-2 * k + 2, 3) / 2), | |
| out: k => 1 - Math.pow(1 - k, 3), | |
| in: k => k * k * k, | |
| outQuint: k => 1 - Math.pow(1 - k, 5), | |
| outBack: k => { const c1 = 1.70158, c3 = c1 + 1; return 1 + c3 * Math.pow(k - 1, 3) + c1 * Math.pow(k - 1, 2); }, | |
| outElastic: k => k === 0 ? 0 : k === 1 ? 1 : Math.pow(2, -10 * k) * Math.sin((k * 10 - .75) * (TAU / 3)) + 1, | |
| }; | |
| // 进场/出场包络 | |
| E.env = (t, a, b, fi = .6, fo = .6) => Math.min(E.smooth(E.seg(t, a, a + fi)), 1 - E.smooth(E.seg(t, b - fo, b))); | |
| E.rng = function (seed) { | |
| let s = seed >>> 0; | |
| return function () { | |
| s = (s + 0x6D2B79F5) >>> 0; let r = s; | |
| r = Math.imul(r ^ (r >>> 15), r | 1); | |
| r ^= r + Math.imul(r ^ (r >>> 7), r | 61); | |
| return ((r ^ (r >>> 14)) >>> 0) / 4294967296; | |
| }; | |
| }; | |
| /* ---------- 调色板 / 字体 ---------- */ | |
| E.C = { | |
| gold: '#f6d58e', goldDeep: '#d9a441', rose: '#f4a9c4', roseDeep: '#d9668f', | |
| ink: '#060918', night: '#0b1030', violet: '#2a1a5c', teal: '#8fe6e1', sky: '#9fc4ff', | |
| white: '#fff8ec', red: '#ff6a52', green: '#9df2b0', mute: 'rgba(230,226,255,.55)', | |
| }; | |
| E.FONT_SERIF = '"Noto Serif SC","Source Han Serif SC","Songti SC","STSong","SimSun",serif'; | |
| E.FONT_SANS = '"Noto Sans SC","PingFang SC","Source Han Sans SC","Microsoft YaHei","Heiti SC",sans-serif'; | |
| /* ---------- 辉光精灵缓存(比 shadowBlur 快得多) ---------- */ | |
| const glowCache = new Map(); | |
| const withA = (col, a) => col.replace(/[\d.]+\)$/, a + ')'); | |
| E.glowSprite = function (color, size = 128) { | |
| const key = color + size; | |
| if (glowCache.has(key)) return glowCache.get(key); | |
| const c = document.createElement('canvas'); c.width = c.height = size; | |
| const g = c.getContext('2d'); | |
| const gr = g.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2); | |
| gr.addColorStop(0, withA(color, 1)); gr.addColorStop(.18, withA(color, .55)); | |
| gr.addColorStop(.5, withA(color, .12)); gr.addColorStop(1, withA(color, 0)); | |
| g.fillStyle = gr; g.fillRect(0, 0, size, size); | |
| glowCache.set(key, c); return c; | |
| }; | |
| E.glow = function (ctx, x, y, r, color = 'rgba(255,220,150,1)', a = 1) { | |
| if (!(a > 0) || !(r > 0)) return; | |
| const prev = ctx.globalCompositeOperation, pa = ctx.globalAlpha; | |
| ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = pa * Math.min(1, a); | |
| ctx.drawImage(E.glowSprite(color), x - r, y - r, r * 2, r * 2); | |
| ctx.globalCompositeOperation = prev; ctx.globalAlpha = pa; | |
| }; | |
| // 星点:内核 + 十字星芒 + 辉光 | |
| E.star = function (ctx, x, y, r, color = 'rgba(255,240,210,1)', a = 1, spikes = true) { | |
| if (!(a > 0)) return; | |
| E.glow(ctx, x, y, r * 7, color, a * .9); | |
| const pa = ctx.globalAlpha; ctx.globalAlpha = pa * Math.min(1, a); | |
| ctx.fillStyle = '#fffaf0'; ctx.beginPath(); ctx.arc(x, y, Math.max(.5, r), 0, TAU); ctx.fill(); | |
| if (spikes) { | |
| const prev = ctx.globalCompositeOperation; ctx.globalCompositeOperation = 'lighter'; | |
| const L = r * 9; | |
| for (const [dx, dy] of [[1, 0], [0, 1]]) { | |
| const g = ctx.createLinearGradient(x - dx * L, y - dy * L, x + dx * L, y + dy * L); | |
| g.addColorStop(0, withA(color, 0)); g.addColorStop(.5, color); g.addColorStop(1, withA(color, 0)); | |
| ctx.strokeStyle = g; ctx.lineWidth = Math.max(1, r * .35); | |
| ctx.beginPath(); ctx.moveTo(x - dx * L, y - dy * L); ctx.lineTo(x + dx * L, y + dy * L); ctx.stroke(); | |
| } | |
| ctx.globalCompositeOperation = prev; | |
| } | |
| ctx.globalAlpha = pa; | |
| }; | |
| /* ---------- 星空背景(预生成) ---------- */ | |
| const R0 = E.rng(20260923); | |
| const STARS = []; | |
| for (let i = 0; i < 900; i++) { | |
| const layer = i < 600 ? 0 : i < 820 ? 1 : 2; | |
| const h = R0(); | |
| STARS.push({ x: R0(), y: R0(), layer, r: [0.9, 1.4, 2.0][layer] * (0.6 + R0() * .8), ph: R0() * TAU, sp: .5 + R0() * 2.2, | |
| hue: h < .15 ? 'rgba(255,200,170,1)' : h < .3 ? 'rgba(180,210,255,1)' : 'rgba(255,248,235,1)' }); | |
| } | |
| let nebula = null; | |
| function buildNebula() { | |
| const N = 1024, c = document.createElement('canvas'); c.width = c.height = N; | |
| const g = c.getContext('2d'); const r = E.rng(777); | |
| g.globalCompositeOperation = 'lighter'; | |
| const cols = [[120, 70, 200], [220, 110, 170], [60, 110, 210], [240, 190, 120]]; | |
| for (let i = 0; i < 120; i++) { | |
| const [cr, cg, cb] = cols[Math.floor(r() * cols.length)]; | |
| const k = r(); const x = k * N, y = N * (.75 - k * .5) + (r() - .5) * N * .35; | |
| const rad = N * (.05 + r() * .16); | |
| const gr = g.createRadialGradient(x, y, 0, x, y, rad); | |
| gr.addColorStop(0, `rgba(${cr},${cg},${cb},${.05 + r() * .07})`); gr.addColorStop(1, 'rgba(0,0,0,0)'); | |
| g.fillStyle = gr; g.beginPath(); g.arc(x, y, rad, 0, TAU); g.fill(); | |
| } | |
| for (let i = 0; i < 2500; i++) { | |
| const k = r(); const x = k * N, y = N * (.75 - k * .5) + (r() - .5) * N * .25 * (1 + r()); | |
| g.fillStyle = `rgba(255,240,230,${r() * .25})`; g.fillRect(x, y, 1, 1); | |
| } | |
| nebula = c; | |
| } | |
| E.background = function (ctx, t, W, H, opt = {}) { | |
| if (!nebula) buildNebula(); | |
| const g = ctx.createLinearGradient(0, 0, W * .3, H); | |
| g.addColorStop(0, '#05071a'); g.addColorStop(.55, opt.mid || '#0d1035'); g.addColorStop(1, opt.bottom || '#1c1240'); | |
| ctx.fillStyle = g; ctx.fillRect(0, 0, W, H); | |
| const S = Math.max(W, H), sz = Math.min(W, H) / 1080; | |
| ctx.save(); ctx.globalAlpha = opt.nebula ?? .95; | |
| ctx.translate(W / 2, H / 2); ctx.rotate(-.12 + t * .0025); const sc = 1.35 + .05 * Math.sin(t * .03); | |
| ctx.drawImage(nebula, -S * sc / 2 + Math.sin(t * .02) * 30 * sz, -S * sc / 2, S * sc, S * sc); | |
| ctx.restore(); | |
| const drift = opt.drift ?? 1, dx = t * 4 * drift * sz, dy = t * 1.2 * drift * sz; | |
| const sa = opt.stars ?? 1; | |
| for (const s of STARS) { | |
| const par = [.3, .6, 1][s.layer]; | |
| const x = (s.x * S + dx * par) % S, y = (s.y * S + dy * par) % S; | |
| if (x > W || y > H) continue; | |
| const tw = .55 + .45 * Math.sin(t * s.sp + s.ph); | |
| const a = tw * sa * (s.layer === 0 ? .55 : .9); | |
| const r = s.r * Math.max(.6, sz); | |
| if (s.layer === 2) E.glow(ctx, x, y, r * 6, s.hue, a * .5); | |
| ctx.globalAlpha = a; ctx.fillStyle = s.hue; | |
| ctx.fillRect(x - r / 2, y - r / 2, r, r); | |
| } | |
| ctx.globalAlpha = 1; | |
| const v = ctx.createRadialGradient(W / 2, H / 2, Math.min(W, H) * .3, W / 2, H / 2, Math.max(W, H) * .75); | |
| v.addColorStop(0, 'rgba(0,0,0,0)'); v.addColorStop(1, 'rgba(2,3,12,.75)'); | |
| ctx.fillStyle = v; ctx.fillRect(0, 0, W, H); | |
| }; | |
| // 流星 | |
| E.meteors = function (ctx, t, W, H, seed = 1, rate = .25, a = 1) { | |
| const base = Math.floor(t * rate), sz = Math.min(W, H) / 1080; | |
| for (let k = base - 2; k <= base; k++) { | |
| const r = E.rng(k * 9973 + seed * 31 + 7); const t0 = k / rate + r() / rate; | |
| const u = (t - t0) / 1.3; if (u < 0 || u > 1) continue; | |
| const x0 = W * (.2 + r() * .9), y0 = H * (r() * .45); | |
| const ang = Math.PI * (.78 + r() * .1), len = Math.max(W, H) * (.25 + r() * .2); | |
| const hx = x0 + Math.cos(ang) * len * u, hy = y0 + Math.sin(ang) * len * u; | |
| const tl = len * .35; | |
| const g = ctx.createLinearGradient(hx, hy, hx - Math.cos(ang) * tl, hy - Math.sin(ang) * tl); | |
| const al = Math.sin(u * Math.PI) * a; | |
| g.addColorStop(0, `rgba(255,245,220,${al})`); g.addColorStop(1, 'rgba(255,245,220,0)'); | |
| ctx.strokeStyle = g; ctx.lineWidth = 2.4 * sz; ctx.lineCap = 'round'; | |
| ctx.beginPath(); ctx.moveTo(hx, hy); ctx.lineTo(hx - Math.cos(ang) * tl, hy - Math.sin(ang) * tl); ctx.stroke(); | |
| E.glow(ctx, hx, hy, 16 * sz, 'rgba(255,240,200,1)', al); | |
| } | |
| }; | |
| /* ---------- 文字 ---------- */ | |
| E.text = function (ctx, str, x, y, o = {}) { | |
| const size = o.size || 40; | |
| if (size < 2 || (o.alpha ?? 1) <= 0) return; | |
| ctx.save(); | |
| ctx.globalAlpha *= Math.min(1, o.alpha ?? 1); | |
| ctx.font = `${o.weight || 600} ${size}px ${o.font || E.FONT_SERIF}`; | |
| ctx.textAlign = o.align || 'center'; ctx.textBaseline = o.baseline || 'middle'; | |
| if ('letterSpacing' in ctx) ctx.letterSpacing = (o.spacing || 0) + 'px'; | |
| if (o.glow) { ctx.shadowColor = o.glow; ctx.shadowBlur = o.blur ?? size * .6; } | |
| if (o.stroke) { ctx.lineWidth = o.strokeW || size * .12; ctx.strokeStyle = o.stroke; ctx.lineJoin = 'round'; ctx.strokeText(str, x, y); } | |
| if (o.gradient) { | |
| const m = ctx.measureText(str).width; const lx = o.align === 'left' ? x : o.align === 'right' ? x - m : x - m / 2; | |
| const g = ctx.createLinearGradient(lx, y - size / 2, lx + m, y + size / 2); | |
| o.gradient.forEach((c, i) => g.addColorStop(i / (o.gradient.length - 1), c)); | |
| ctx.fillStyle = g; | |
| } else ctx.fillStyle = o.color || E.C.white; | |
| ctx.fillText(str, x, y); | |
| ctx.restore(); | |
| }; | |
| // 逐字浮现 | |
| E.typeText = function (ctx, str, x, y, k, o = {}) { | |
| if (k <= 0) return; | |
| const chars = [...str]; const size = o.size || 40; | |
| ctx.save(); ctx.font = `${o.weight || 600} ${size}px ${o.font || E.FONT_SERIF}`; | |
| if ('letterSpacing' in ctx) ctx.letterSpacing = '0px'; | |
| const sp = o.spacing || 0; | |
| const widths = chars.map(c => ctx.measureText(c).width + sp); | |
| const total = widths.reduce((a, b) => a + b, 0) - sp; | |
| let cx = o.align === 'left' ? x : x - total / 2; | |
| ctx.restore(); | |
| chars.forEach((c, i) => { | |
| const kk = E.clamp(k * (chars.length + 3) - i, 0, 1); | |
| if (kk > 0) { | |
| const e = E.ease.out(kk); | |
| const opt = Object.assign({}, o, { align: 'center', alpha: (o.alpha ?? 1) * e, spacing: 0 }); | |
| if (o.gradient) { opt.gradient = null; opt.color = o.gradient[Math.min(o.gradient.length - 1, Math.floor(i / chars.length * o.gradient.length))]; } | |
| E.text(ctx, c, cx + (widths[i] - sp) / 2, y + (1 - e) * size * .5, opt); | |
| } | |
| cx += widths[i]; | |
| }); | |
| }; | |
| E.wrap = function (ctx, str, maxW) { | |
| const out = []; let line = ''; | |
| for (const ch of str) { | |
| if (ctx.measureText(line + ch).width > maxW && line) { out.push(line); line = ch; } else line += ch; | |
| } | |
| if (line) out.push(line); | |
| for (let i = 1; i < out.length; i++) if (/^[,。、:;!?」』)”]/.test(out[i])) { out[i - 1] += out[i][0]; out[i] = out[i].slice(1); } | |
| // 尽量平衡两行长度 | |
| if (out.length === 2) { const all = out.join(''); const n = Math.ceil([...all].length / 2); const a = [...all]; return [a.slice(0, n).join(''), a.slice(n).join('')]; } | |
| return out.filter(Boolean); | |
| }; | |
| /* ---------- 图形:天平 ---------- */ | |
| // opt: tilt(弧度), alpha, color, glow, beamK(秤杆生长0-1), pansK, pillarK, leftLoad/rightLoad 回调 | |
| E.scales = function (ctx, x, y, s, opt = {}) { | |
| const a = opt.alpha ?? 1; if (a <= 0) return; | |
| const col = opt.color || E.C.gold; const tilt = opt.tilt || 0; | |
| const pillarK = opt.pillarK ?? 1, beamK = opt.beamK ?? 1, pansK = opt.pansK ?? 1; | |
| ctx.save(); ctx.globalAlpha *= Math.min(1, a); ctx.translate(x, y); | |
| ctx.lineCap = 'round'; ctx.lineJoin = 'round'; | |
| const glowCol = opt.glow || 'rgba(246,213,142,.9)'; | |
| const setGlow = () => { ctx.shadowColor = glowCol; ctx.shadowBlur = 18 * s; ctx.strokeStyle = col; ctx.fillStyle = col; }; | |
| setGlow(); | |
| if (pillarK > 0) { | |
| const ph = 312 * s * E.clamp(pillarK); | |
| ctx.lineWidth = 7 * s; | |
| ctx.beginPath(); ctx.moveTo(0, 330 * s); ctx.lineTo(0, 330 * s - ph); ctx.stroke(); | |
| const bw = 110 * s * E.ease.out(E.clamp(pillarK * 2)); | |
| ctx.lineWidth = 6 * s; | |
| ctx.beginPath(); ctx.moveTo(-bw, 340 * s); ctx.quadraticCurveTo(0, 300 * s, bw, 340 * s); ctx.stroke(); | |
| ctx.beginPath(); ctx.moveTo(-bw * .8, 352 * s); ctx.lineTo(bw * .8, 352 * s); ctx.stroke(); | |
| if (pillarK > .95) { | |
| ctx.lineWidth = 4 * s; | |
| ctx.beginPath(); ctx.arc(0, 18 * s, 13 * s, 0, TAU); ctx.stroke(); | |
| E.star(ctx, 0, -16 * s, 3.4 * s, 'rgba(255,230,170,1)', E.seg(pillarK, .95, 1)); | |
| setGlow(); | |
| } | |
| } | |
| const L = 250 * s; | |
| ctx.save(); ctx.translate(0, 18 * s); ctx.rotate(tilt); | |
| if (beamK > 0) { | |
| const l = L * E.ease.inOut(E.clamp(beamK)); | |
| ctx.lineWidth = 6 * s; | |
| ctx.beginPath(); ctx.moveTo(-l, 0); ctx.lineTo(l, 0); ctx.stroke(); | |
| if (beamK > .9) { | |
| ctx.lineWidth = 3 * s; | |
| for (const d of [-1, 1]) { ctx.beginPath(); ctx.moveTo(d * 30 * s, 0); ctx.quadraticCurveTo(d * L * .4, -26 * s, d * L * .8, 0); ctx.stroke(); } | |
| } | |
| for (const d of [-1, 1]) { ctx.beginPath(); ctx.arc(d * l, 0, 7 * s, 0, TAU); ctx.fill(); } | |
| } | |
| ctx.restore(); | |
| const ends = []; | |
| for (const d of [-1, 1]) { | |
| const ex = d * L * Math.cos(tilt), ey = 18 * s + d * L * Math.sin(tilt); | |
| ends.push([ex, ey]); | |
| if (pansK <= 0 || beamK < .98) continue; | |
| const k = E.ease.out(E.clamp(pansK)); | |
| const ch = 170 * s * k, pw = 92 * s; | |
| ctx.lineWidth = 2.2 * s; | |
| ctx.beginPath(); | |
| ctx.moveTo(ex, ey); ctx.lineTo(ex - pw * k, ey + ch); | |
| ctx.moveTo(ex, ey); ctx.lineTo(ex + pw * k, ey + ch); | |
| ctx.moveTo(ex, ey); ctx.lineTo(ex, ey + ch); | |
| ctx.stroke(); | |
| ctx.lineWidth = 5 * s; | |
| ctx.beginPath(); ctx.moveTo(ex - pw * k - 8 * s, ey + ch); | |
| ctx.quadraticCurveTo(ex, ey + ch + 60 * s * k, ex + pw * k + 8 * s, ey + ch); ctx.closePath(); | |
| const pa = ctx.globalAlpha; ctx.globalAlpha = pa * .22; ctx.fill(); ctx.globalAlpha = pa; ctx.stroke(); | |
| const load = d < 0 ? opt.leftLoad : opt.rightLoad; | |
| if (load && k > .9) { ctx.save(); ctx.shadowBlur = 0; load(ctx, ex, ey + ch - 4 * s, s); ctx.restore(); setGlow(); } | |
| } | |
| ctx.restore(); | |
| return ends.map(([ex, ey]) => [x + ex, y + ey]); | |
| }; | |
| // ♎ 符号(可描边动画) | |
| E.libraGlyph = function (ctx, x, y, s, k = 1, o = {}) { | |
| if (k <= 0) return; | |
| ctx.save(); ctx.translate(x, y); | |
| ctx.lineCap = 'round'; ctx.lineJoin = 'round'; | |
| ctx.strokeStyle = o.color || E.C.gold; ctx.lineWidth = (o.width || 9) * s; | |
| ctx.shadowColor = o.glow || 'rgba(246,213,142,.9)'; ctx.shadowBlur = 22 * s; | |
| ctx.globalAlpha *= o.alpha ?? 1; | |
| const w = 100 * s, r = 38 * s, yTop = 6 * s, yBot = 46 * s; | |
| const p = new Path2D(); | |
| p.moveTo(-w, yTop); p.lineTo(-r * 1.15, yTop); | |
| p.bezierCurveTo(-r * 1.9, -r * 1.2, -r * .6, -r * 2.25, 0, -r * 2.2); | |
| p.bezierCurveTo(r * .6, -r * 2.25, r * 1.9, -r * 1.2, r * 1.15, yTop); | |
| p.lineTo(w, yTop); | |
| p.moveTo(-w, yBot); p.lineTo(w, yBot); | |
| const total = 900 * s; | |
| if (k < 1) ctx.setLineDash([total * k, total]); | |
| ctx.stroke(p); | |
| ctx.restore(); | |
| }; | |
| // ♀ 金星符号 | |
| E.venusGlyph = function (ctx, x, y, s, k = 1, col = E.C.rose) { | |
| if (k <= 0) return; | |
| ctx.save(); ctx.translate(x, y); ctx.strokeStyle = col; ctx.lineWidth = 7 * s; ctx.lineCap = 'round'; | |
| ctx.shadowColor = col; ctx.shadowBlur = 16 * s; | |
| const total = 600 * s; if (k < 1) ctx.setLineDash([total * k, total]); | |
| ctx.beginPath(); ctx.arc(0, -25 * s, 38 * s, -Math.PI / 2, Math.PI * 1.5); | |
| ctx.moveTo(0, 13 * s); ctx.lineTo(0, 75 * s); ctx.moveTo(-26 * s, 45 * s); ctx.lineTo(26 * s, 45 * s); ctx.stroke(); | |
| ctx.restore(); | |
| }; | |
| // 圆角矩形面板 | |
| E.panel = function (ctx, x, y, w, h, r, o = {}) { | |
| if ((o.alpha ?? 1) <= 0) return; | |
| ctx.save(); ctx.globalAlpha *= Math.min(1, o.alpha ?? 1); | |
| ctx.beginPath(); if (ctx.roundRect) ctx.roundRect(x, y, w, h, r); else ctx.rect(x, y, w, h); | |
| ctx.fillStyle = o.fill || 'rgba(20,18,50,.55)'; ctx.fill(); | |
| ctx.lineWidth = o.lw || 1.5; ctx.strokeStyle = o.stroke || 'rgba(246,213,142,.45)'; ctx.stroke(); | |
| ctx.restore(); | |
| }; | |
| E.lineK = function (ctx, x1, y1, x2, y2, k) { | |
| if (k <= 0) return; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(E.lerp(x1, x2, k), E.lerp(y1, y2, k)); ctx.stroke(); | |
| }; | |
| // 天球投影:赤经(小时)、赤纬(度) → 以中心为原点的“度”坐标(东在左,与仰望南天一致) | |
| E.sky = function (ra, dec, ra0 = 15.2, dec0 = -18) { | |
| const x = -(ra - ra0) * 15 * Math.cos(dec * Math.PI / 180); | |
| return [x, -(dec - dec0)]; | |
| }; | |
| // 兼容旧浏览器的 roundRect | |
| if (typeof CanvasRenderingContext2D !== 'undefined' && !CanvasRenderingContext2D.prototype.roundRect) { | |
| CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { | |
| r = Math.min(typeof r === 'number' ? r : 0, Math.abs(w) / 2, Math.abs(h) / 2); | |
| this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); | |
| this.arcTo(x, y + h, x, y, r); this.arcTo(x, y, x + w, y, r); this.closePath(); | |
| }; | |
| } | |
| window.E = E; | |
| })(); | |