diff --git a/static/css/_dark.scss b/static/css/_dark.scss index 4330872..51d75a6 100644 --- a/static/css/_dark.scss +++ b/static/css/_dark.scss @@ -211,11 +211,12 @@ position: relative; } -.darker-rounded-debug, .darker-text-render { +.darker-rounded-surrogate { position: absolute; top: 0; left: 0; pointer-events: none; + fill: none; } .input-wrapper { diff --git a/static/js/dark.js b/static/js/dark.js index 6f10c01..90cb018 100644 --- a/static/js/dark.js +++ b/static/js/dark.js @@ -34,6 +34,15 @@ var __generator = (this && this.__generator) || function (thisArg, body) { if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +}; console.log("喵呜喵呜喵"); var modes = ["light", "dark", "darker", "lighter"]; if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { @@ -161,6 +170,33 @@ function discretize(path) { discretizationCache.set(path, loop); return loop; } +var staticPathSet; +var tmpPathSets = new Map(); +window.tmpPathSets = tmpPathSets; +function ratify(ps) { + if (shaderCtx.gl === null) + throw new Error('WebGL not initialized!'); + var gl = shaderCtx.gl; + var ext = gl.getExtension("OES_vertex_array_object"); + var vao = ext.createVertexArrayOES(); + ext.bindVertexArrayOES(vao); + var buf = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buf); + gl.bufferData(gl.ARRAY_BUFFER, ps, gl.DYNAMIC_DRAW); + var a_pos_loc = gl.getAttribLocation(shaderCtx.prog, 'a_pos'); + gl.enableVertexAttribArray(a_pos_loc); + gl.vertexAttribPointer(a_pos_loc, 3, gl.FLOAT, false, 0, 0); + ext.bindVertexArrayOES(null); + gl.deleteBuffer(buf); + return [vao, ps.length / 3]; +} +function free(ps) { + if (shaderCtx.gl === null) + throw new Error('WebGL not initialized!'); + var gl = shaderCtx.gl; + var ext = gl.getExtension("OES_vertex_array_object"); + ext.deleteVertexArrayOES(ps[0]); +} function applyMode(m) { return __awaiter(this, void 0, void 0, function () { var tmpl_1, flames_1; @@ -181,9 +217,9 @@ function applyMode(m) { _a.sent(); setTimeout(function () { ensureCanvas(); - rescanAt(document.body); + var els = rescan(document.body); // TODO: async reassemble - reassemble(); + staticPathSet = ratify(assembleAll(els)); ensureObs(); renderLoop(); document.body.classList.remove('darker-engaging'); @@ -216,25 +252,41 @@ function tracker(e) { mx = e.clientX; my = e.clientY; } -function rescan(mutations, obs) { +function onMutate(mutations, obs) { for (var _i = 0, mutations_1 = mutations; _i < mutations_1.length; _i++) { var m = mutations_1[_i]; if (m.type === 'attributes') continue; if (m.type === 'characterData') { - // TODO: rescan node console.log('Don\'t know how to rescan characterData'); continue; } for (var _a = 0, _b = m.addedNodes; _a < _b.length; _a++) { var n = _b[_a]; - rescanAt(n); + // Skip elements added by ourselves + if (!n.classList.contains('popover')) + continue; + var els = rescan(n); + // Based on n + var ps = assembleAll(els); + tmpPathSets.set(n, ratify(ps)); + } + for (var _c = 0, _d = m.removedNodes; _c < _d.length; _c++) { + var n = _d[_c]; + var orig = tmpPathSets.get(n); + if (orig) + free(orig); + tmpPathSets.delete(n); } } - setTimeout(function () { return reassemble(); }); +} +function rescan(el) { + var buf = []; + rescanAt(el, buf); + return buf; } // TODO: allow scaning arbitrary HTML-side nodes -function rescanAt(el) { +function rescanAt(el, buf) { var _a, _b, _c, _d, _e, _f; if ((_a = el.classList) === null || _a === void 0 ? void 0 : _a.contains('sr-only')) return; @@ -242,7 +294,7 @@ function rescanAt(el) { return; // Check if is svg if (el.tagName === 'svg') { - rescanSVG(el); + rescanSVG(el, buf, []); return; } if (((_c = el.classList) === null || _c === void 0 ? void 0 : _c.contains('label-status')) || ((_d = el.classList) === null || _d === void 0 ? void 0 : _d.contains('label-new')) || ((_e = el.classList) === null || _e === void 0 ? void 0 : _e.contains('input-wrapper')) || ((_f = el.classList) === null || _f === void 0 ? void 0 : _f.contains('popover'))) { @@ -253,16 +305,11 @@ function rescanAt(el) { svg.setAttribute('viewbox', "0 0 ".concat(width, " ").concat(height)); svg.style.width = width + 'px'; svg.style.height = height + 'px'; - svg.classList.add('darker-rounded-debug'); + svg.classList.add('darker-rounded-surrogate'); var path = document.createElementNS("http://www.w3.org/2000/svg", 'path'); path.setAttribute('d', d); - path.classList.add('darker-processed'); - path.classList.add('darker-surrogate'); svg.appendChild(path); el.appendChild(svg); - svg.classList.add('darker-traced'); - svg.classList.add('darker-traced-misc'); - rescanAt(svg); } for (var _i = 0, _h = el.childNodes; _i < _h.length; _i++) { var child = _h[_i]; @@ -301,7 +348,8 @@ function rescanAt(el) { var glyph = resolveGlyph(first, fsNum, isBold); if (!glyph) continue; - node.classList.add('darker-traced'); + // node.classList.add('darker-traced'); + buf.push(node); node.setAttribute('data-glyph', glyph); // console.log(first, glyph); // Debug @@ -330,25 +378,25 @@ function rescanAt(el) { continue; if (el.classList.contains('darker-text-svg')) continue; - rescanAt(child); + rescanAt(child, buf); } } } var symbolCache = {}; var svgCache = {}; var svgIDGen = 0; +// FIXME: return string instead function splitPathSegs(path) { - var d = path; + var d = path.trim(); var segs = []; while (true) { - // console.log(d); var nextMoveIdx = d.substring(1).toLowerCase().indexOf('m'); if (nextMoveIdx === -1) { segs.push(d); break; } segs.push(d.substring(0, nextMoveIdx + 1)); - d = d.substring(nextMoveIdx + 1); + d = d.substring(nextMoveIdx + 1).trim(); } var last = { x: 0, y: 0 }; var paths = []; @@ -391,61 +439,61 @@ function splitPathSegs(path) { return outerPaths; } // TODO: cache DOM -function rescanSVG(el) { +function rescanSVG(el, buf, pathCollector) { var _a; - if (el.classList.contains('darker-processed')) - return; if (el.tagName === 'path') { - var d = (_a = el.getAttribute('d')) !== null && _a !== void 0 ? _a : ''; - var segs = splitPathSegs(d); - for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) { - var path = segs_2[_i]; - el.parentNode.appendChild(path); + var d = (_a = el.getAttribute('d')) === null || _a === void 0 ? void 0 : _a.trim(); + try { + var segs = splitPathSegs(d); + for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) { + var path = segs_2[_i]; + pathCollector.push(path.getAttribute('d')); + } + } + catch (e) { + console.error(e); + console.log(el); } } else if (el.tagName === 'use') { - el.classList.add('darker-traced'); + buf.push(el); + // el.classList.add('darker-traced'); // const xlink = el.getAttribute('xlink:href'); } - // TODO: trace texts + var childPathCollector = pathCollector; + if (el.tagName === 'symbol' && el.id !== '') { + childPathCollector = []; + } + else if (el.tagName === 'svg' && el.getAttribute('display') !== 'none') { + childPathCollector = []; + } for (var _b = 0, _c = el.children; _b < _c.length; _b++) { var child = _c[_b]; - rescanSVG(child); + rescanSVG(child, buf, childPathCollector); } if (el.tagName === 'symbol' && el.id !== '') { // Cache symbol content - var allPaths = el.querySelectorAll(".darker-surrogate"); - var ret = []; - for (var _d = 0, allPaths_1 = allPaths; _d < allPaths_1.length; _d++) { - var p = allPaths_1[_d]; - ret.push(p.getAttribute('d')); - } - symbolCache[el.id] = ret; + symbolCache[el.id] = childPathCollector; } else if (el.tagName === 'svg' && el.getAttribute('display') !== 'none') { var id = svgIDGen++; el.id = "darker-svg-".concat(id); - var allPaths = el.querySelectorAll(".darker-surrogate"); - var ret = []; - for (var _e = 0, allPaths_2 = allPaths; _e < allPaths_2.length; _e++) { - var p = allPaths_2[_e]; - ret.push(p.getAttribute('d')); - } - svgCache[el.id] = ret; + // console.log(el.id, childPathCollector) + svgCache[el.id] = childPathCollector; + buf.push(el); } - el.classList.add('darker-processed'); + // TODO: Do we need to join childPathCollector to pathCollector if there are not eq? } var obs = null; var canvas = null; var backdrop = null; var overlay = null; var shaderCtx = { - a_pos: null, u_screen_loc: null, u_mouse_loc: null, u_offset_loc: null, - triangleCnt: 0, gl: null, + prog: null, }; function ensureCanvas() { var container = document.createElement('div'); @@ -474,15 +522,11 @@ function ensureCanvas() { console.log(gl.getShaderInfoLog(fragShader)); // TODO: check compile status var prog = gl.createProgram(); + shaderCtx.prog = prog; gl.attachShader(prog, vertShader); gl.attachShader(prog, fragShader); gl.linkProgram(prog); - var a_pos_loc = gl.getAttribLocation(prog, 'a_pos'); - shaderCtx.a_pos = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, shaderCtx.a_pos); gl.useProgram(prog); - gl.enableVertexAttribArray(a_pos_loc); - gl.vertexAttribPointer(a_pos_loc, 3, gl.FLOAT, false, 0, 0); shaderCtx.u_screen_loc = gl.getUniformLocation(prog, 'u_screen'); shaderCtx.u_mouse_loc = gl.getUniformLocation(prog, 'u_mouse'); shaderCtx.u_offset_loc = gl.getUniformLocation(prog, 'u_offset'); @@ -495,95 +539,95 @@ function ensureCanvas() { } function ensureObs() { if (obs === null) { - obs = new MutationObserver(rescan); + obs = new MutationObserver(onMutate); obs.observe(document.body, { childList: true, subtree: true, }); } } +// Assembly var textCache = new WeakMap(); var assembleCache = new WeakMap(); -function reassemble() { - var traced = document.getElementsByClassName('darker-traced'); - function assemblePath(paths, sx, sy, scale) { - var buf = []; - for (var _i = 0, paths_3 = paths; _i < paths_3.length; _i++) { - var path = paths_3[_i]; - var dpath = discretize(path); - if (dpath.length === 1) - continue; - for (var i = 0; i < dpath.length; ++i) { - var cx = dpath[i].x * scale + sx; - var cy = dpath[i].y * scale + sy; - var nx = dpath[(i + 1) % dpath.length].x * scale + sx; - var ny = dpath[(i + 1) % dpath.length].y * scale + sy; - // Expand a little bit - buf.push(cx, cy, -0.01, nx, ny, -0.01, cx, cy, 5, cx, cy, 5, nx, ny, 5, nx, ny, -0.01); - } - } - return buf; - } - var total = []; - for (var _i = 0, traced_1 = traced; _i < traced_1.length; _i++) { - var trace = traced_1[_i]; - if (assembleCache.has(trace)) { - var cached = assembleCache.get(trace); - total.push.apply(total, cached); +// TODO: segmentation +function assemblePath(paths, sx, sy, scale) { + var buf = []; + for (var _i = 0, paths_3 = paths; _i < paths_3.length; _i++) { + var path = paths_3[_i]; + var dpath = discretize(path); + if (dpath.length === 1) continue; + for (var i = 0; i < dpath.length; ++i) { + var cx = dpath[i].x * scale + sx; + var cy = dpath[i].y * scale + sy; + var nx = dpath[(i + 1) % dpath.length].x * scale + sx; + var ny = dpath[(i + 1) % dpath.length].y * scale + sy; + // Expand a little bit + buf.push(cx, cy, -0.01, nx, ny, -0.01, cx, cy, 5, cx, cy, 5, nx, ny, 5, nx, ny, -0.01); } - var _a = trace.getBoundingClientRect(), x = _a.x, y = _a.y, width = _a.width, height = _a.height; - var populated = []; - if (trace.tagName === 'use') { - var sym = document.getElementById(trace.getAttribute('xlink:href').substring(1)); - var vbox = sym.viewBox.baseVal; - // TODO: handle browsers without baseVal - // TODO: handle origins other than 0,0 - // Firefox fucks up its dimension calculation - var parentDims = trace.parentElement.getBoundingClientRect(); - var scale = parentDims.width / vbox.width; - var vscale = parentDims.height / vbox.height; - // if(scale > vscale * 1.01 || scale < vscale * 0.99) - // console.warn(`incompatible scales: ${scale}, ${vscale}`); - var paths = symbolCache[sym.id]; - if (paths === undefined) { - console.warn("Symbol not in cache: ".concat(sym.id)); - continue; - } - populated = assemblePath(paths, x, y + window.scrollY, scale); - } - else if (trace.tagName === 'svg') { - var scale = 1; - var vb = trace.getAttribute('viewBox'); - if (vb) { - var _b = vb.split(' ').map(function (e) { return parseFloat(e); }), _ = _b[0], __ = _b[1], vboxw = _b[2], vboxh = _b[3]; - scale = width / vboxw; - } - var paths = svgCache[trace.id]; - if (paths === undefined) { - console.warn("SVG not in cache: ".concat(trace.id)); - continue; - } - populated = assemblePath(paths, x, y + window.scrollY, scale); - } - else if (trace.classList.contains('darker-text')) { - var cached = textCache.get(trace); - if (!cached) { - var glyph = trace.getAttribute('data-glyph'); - var paths = splitPathSegs(glyph); - cached = paths.map(function (e) { return e.getAttribute('d'); }); - textCache.set(trace, cached); - } - populated = assemblePath(cached, x, y + window.scrollY, 1); - } - assembleCache.set(trace, populated); - total.push.apply(total, populated); } - // TODO: error on me - if (!shaderCtx.gl) + return buf; +} +function assembleOne(el, buffer) { + if (assembleCache.has(el)) { + var cached = assembleCache.get(el); + buffer.push.apply(buffer, cached); return; - shaderCtx.gl.bufferData(shaderCtx.gl.ARRAY_BUFFER, new Float32Array(total), shaderCtx.gl.STATIC_DRAW); - shaderCtx.triangleCnt = total.length / 3; + } + var _a = el.getBoundingClientRect(), x = _a.x, y = _a.y, width = _a.width, height = _a.height; + var populated = []; + if (el.tagName === 'use') { + var sym = document.getElementById(el.getAttribute('xlink:href').substring(1)); + var vbox = sym.viewBox.baseVal; + // TODO: handle browsers without baseVal + // TODO: handle origins other than 0,0 + // Firefox fucks up its dimension calculation + var parentDims = el.parentElement.getBoundingClientRect(); + var scale = parentDims.width / vbox.width; + var vscale = parentDims.height / vbox.height; + // if(scale > vscale * 1.01 || scale < vscale * 0.99) + // console.warn(`incompatible scales: ${scale}, ${vscale}`); + var paths = symbolCache[sym.id]; + if (paths === undefined) { + console.warn("Symbol not in cache: ".concat(sym.id)); + return; + } + populated = assemblePath(paths, x, y + window.scrollY, scale); + } + else if (el.tagName === 'svg') { + var scale = 1; + var vb = el.getAttribute('viewBox'); + if (vb) { + var _b = vb.split(' ').map(function (e) { return parseFloat(e); }), _ = _b[0], __ = _b[1], vboxw = _b[2], vboxh = _b[3]; + scale = width / vboxw; + } + var paths = svgCache[el.id]; + if (paths === undefined) { + console.warn("SVG not in cache: ".concat(el.id)); + return; + } + populated = assemblePath(paths, x, y + window.scrollY, scale); + } + else if (el.classList.contains('darker-text')) { + var cached = textCache.get(el); + if (!cached) { + var glyph = el.getAttribute('data-glyph'); + var paths = splitPathSegs(glyph); + cached = paths.map(function (e) { return e.getAttribute('d'); }); + textCache.set(el, cached); + } + populated = assemblePath(cached, x, y + window.scrollY, 1); + } + assembleCache.set(el, populated); + buffer.push.apply(buffer, populated); +} +function assembleAll(els) { + var buf = []; + for (var _i = 0, els_1 = els; _i < els_1.length; _i++) { + var el = els_1[_i]; + assembleOne(el, buf); + } + return new Float32Array(buf); } var renderStopped = false; function renderLoop() { @@ -615,7 +659,22 @@ function renderLoop() { gl.disable(gl.DEPTH_TEST); // gl.enable(gl.SAMPLE_COVERAGE); // gl.sampleCoverage(0.5, false); - gl.drawArrays(gl.TRIANGLES, 0, shaderCtx.triangleCnt); + var ext = gl.getExtension("OES_vertex_array_object"); + function drawPathSet(ps) { + ext.bindVertexArrayOES(ps[0]); + gl.drawArrays(gl.TRIANGLES, 0, ps[1]); + } + // drawPathSet(staticPathSet); + // for(const el of tmpPathSets.keys()) + // if(!document.contains(el)) tmpPathSets.delete(el); + window.wtf = __spreadArray([], tmpPathSets.keys(), true); + window.wtf2 = tmpPathSets; + console.log(tmpPathSets.size); + console.log(__spreadArray([], tmpPathSets.entries(), true)); + for (var _i = 0, _a = tmpPathSets.keys(); _i < _a.length; _i++) { + var k = _a[_i]; + console.log(k); + } { overlay.width = window.innerWidth; overlay.height = window.innerHeight; diff --git a/static/js/dark.ts b/static/js/dark.ts index db1f124..e0ad8fc 100644 --- a/static/js/dark.ts +++ b/static/js/dark.ts @@ -151,6 +151,42 @@ function discretize(path: string): DiscreteLoop { return loop; } +type PathSet = Float32Array; +type RatifiedPathSet = [WebGLVertexArrayObjectOES, number]; + +let staticPathSet: RatifiedPathSet; +const tmpPathSets: Map = new Map(); +window.tmpPathSets = tmpPathSets; + +function ratify(ps: PathSet): RatifiedPathSet { + if(shaderCtx.gl === null) throw new Error('WebGL not initialized!'); + const gl = shaderCtx.gl; + + const ext = gl.getExtension("OES_vertex_array_object")!; + const vao = ext.createVertexArrayOES()!; + ext.bindVertexArrayOES(vao); + + const buf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, buf); + gl.bufferData(gl.ARRAY_BUFFER, ps, gl.DYNAMIC_DRAW); + + const a_pos_loc = gl.getAttribLocation(shaderCtx.prog!, 'a_pos'); + gl.enableVertexAttribArray(a_pos_loc); + gl.vertexAttribPointer(a_pos_loc, 3, gl.FLOAT, false, 0, 0); + + ext.bindVertexArrayOES(null); + gl.deleteBuffer(buf); + + return [vao, ps.length / 3]; +} + +function free(ps: RatifiedPathSet) { + if(shaderCtx.gl === null) throw new Error('WebGL not initialized!'); + const gl = shaderCtx.gl; + const ext = gl.getExtension("OES_vertex_array_object")!; + ext.deleteVertexArrayOES(ps[0]); +} + async function applyMode(m: string) { (document.body.parentElement as HTMLElement).classList.remove('forced-light'); (document.body.parentElement as HTMLElement).classList.remove('forced-dark'); @@ -162,9 +198,9 @@ async function applyMode(m: string) { await allFonts; setTimeout(() => { ensureCanvas(); - rescanAt(document.body); + const els = rescan(document.body); // TODO: async reassemble - reassemble(); + staticPathSet = ratify(assembleAll(els)); ensureObs(); renderLoop(); document.body.classList.remove('darker-engaging'); @@ -196,28 +232,45 @@ function tracker(e: MouseEvent) { my = e.clientY; } -function rescan(mutations: MutationRecord[], obs: MutationObserver) { +function onMutate(mutations: MutationRecord[], obs: MutationObserver) { for(const m of mutations) { if(m.type === 'attributes') continue; if(m.type === 'characterData') { - // TODO: rescan node console.log('Don\'t know how to rescan characterData'); continue; } - for(const n of m.addedNodes) rescanAt(n as HTMLElement); + for(const n of m.addedNodes) { + // Skip elements added by ourselves + if(!(n as HTMLElement).classList.contains('popover')) continue; + + const els = rescan(n as HTMLElement); + // Based on n + const ps = assembleAll(els); + tmpPathSets.set(n as HTMLElement, ratify(ps)); + } + for(const n of m.removedNodes) { + const orig = tmpPathSets.get(n as HTMLElement); + if(orig) free(orig); + tmpPathSets.delete(n as HTMLElement); + } } - setTimeout(() => reassemble()); +} + +function rescan(el: HTMLElement): HTMLElement[] { + const buf = []; + rescanAt(el, buf); + return buf; } // TODO: allow scaning arbitrary HTML-side nodes -function rescanAt(el: HTMLElement) { +function rescanAt(el: HTMLElement, buf: HTMLElement[]) { if(el.classList?.contains('sr-only')) return; if(el.classList?.contains('dark-switch-hint')) return; // Check if is svg if(el.tagName === 'svg') { - rescanSVG(el as unknown as SVGElement); + rescanSVG(el as unknown as SVGElement, buf, []); return; } @@ -235,20 +288,13 @@ function rescanAt(el: HTMLElement) { svg.setAttribute('viewbox', `0 0 ${width} ${height}`); svg.style.width = width + 'px'; svg.style.height = height + 'px'; - svg.classList.add('darker-rounded-debug'); + svg.classList.add('darker-rounded-surrogate'); const path = document.createElementNS("http://www.w3.org/2000/svg", 'path'); - path.setAttribute('d', d); - path.classList.add('darker-processed'); - path.classList.add('darker-surrogate'); svg.appendChild(path); el.appendChild(svg); - - svg.classList.add('darker-traced'); - svg.classList.add('darker-traced-misc'); - rescanAt(svg as unknown as HTMLElement); } for(const child of el.childNodes) { @@ -294,7 +340,8 @@ function rescanAt(el: HTMLElement) { const glyph = resolveGlyph(first, fsNum, isBold); if(!glyph) continue; - node.classList.add('darker-traced'); + // node.classList.add('darker-traced'); + buf.push(node); node.setAttribute('data-glyph', glyph); // console.log(first, glyph); @@ -326,7 +373,7 @@ function rescanAt(el: HTMLElement) { if(el.classList.contains('darker-text-group')) continue; if(el.classList.contains('darker-text-svg')) continue; - rescanAt(child as HTMLElement); + rescanAt(child as HTMLElement, buf); } } } @@ -335,19 +382,19 @@ const symbolCache: Record = {}; const svgCache: Record = {}; let svgIDGen = 0; +// FIXME: return string instead function splitPathSegs(path: string): SVGPathElement[] { - let d = path; + let d = path.trim(); const segs: string[] = []; while(true) { - // console.log(d); const nextMoveIdx = d.substring(1).toLowerCase().indexOf('m'); if(nextMoveIdx === -1) { segs.push(d); break; } segs.push(d.substring(0, nextMoveIdx + 1)); - d = d.substring(nextMoveIdx + 1); + d = d.substring(nextMoveIdx + 1).trim(); } let last = { x: 0, y: 0 }; @@ -389,39 +436,44 @@ function splitPathSegs(path: string): SVGPathElement[] { } // TODO: cache DOM -function rescanSVG(el: SVGElement) { - if(el.classList.contains('darker-processed')) return; - +function rescanSVG(el: SVGElement, buf: HTMLOrSVGElement[], pathCollector: string[]) { if(el.tagName === 'path') { - let d = el.getAttribute('d') ?? ''; - const segs = splitPathSegs(d); - for(const path of segs) el.parentNode!.appendChild(path); + let d = el.getAttribute('d')?.trim(); + try { + const segs = splitPathSegs(d!); + for(const path of segs) pathCollector.push(path.getAttribute('d')!); + } catch(e) { + console.error(e); + console.log(el); + } } else if(el.tagName === 'use') { - el.classList.add('darker-traced'); + buf.push(el); + // el.classList.add('darker-traced'); // const xlink = el.getAttribute('xlink:href'); } - // TODO: trace texts + + let childPathCollector = pathCollector; + if(el.tagName === 'symbol' && el.id !== '') { + childPathCollector = []; + } else if(el.tagName === 'svg' && el.getAttribute('display') !== 'none') { + childPathCollector = []; + } for(const child of el.children) { - rescanSVG(child as SVGElement); + rescanSVG(child as SVGElement, buf, childPathCollector); } if(el.tagName === 'symbol' && el.id !== '') { // Cache symbol content - const allPaths = el.querySelectorAll(".darker-surrogate"); - const ret: string[] = []; - for(const p of allPaths) ret.push(p.getAttribute('d')!); - symbolCache[el.id] = ret; + symbolCache[el.id] = childPathCollector; } else if(el.tagName === 'svg' && el.getAttribute('display') !== 'none') { const id = svgIDGen++; el.id = `darker-svg-${id}`; - const allPaths = el.querySelectorAll(".darker-surrogate"); - const ret: string[] = []; - for(const p of allPaths) ret.push(p.getAttribute('d')!); - svgCache[el.id] = ret; + // console.log(el.id, childPathCollector) + svgCache[el.id] = childPathCollector; + buf.push(el); } - - el.classList.add('darker-processed'); + // TODO: Do we need to join childPathCollector to pathCollector if there are not eq? } let obs: MutationObserver | null = null; @@ -429,14 +481,12 @@ let canvas: HTMLCanvasElement | null = null; let backdrop: HTMLCanvasElement | null = null; let overlay: HTMLCanvasElement | null = null; const shaderCtx = { - a_pos: null as WebGLBuffer | null, - u_screen_loc: null as WebGLUniformLocation | null, u_mouse_loc: null as WebGLUniformLocation | null, u_offset_loc: null as WebGLUniformLocation | null, - triangleCnt: 0, gl: null as WebGLRenderingContext | null, + prog: null as WebGLProgram | null, }; function ensureCanvas() { @@ -471,17 +521,12 @@ function ensureCanvas() { // TODO: check compile status const prog = gl.createProgram()!; + shaderCtx.prog = prog; gl.attachShader(prog, vertShader); gl.attachShader(prog, fragShader); gl.linkProgram(prog); - const a_pos_loc = gl.getAttribLocation(prog, 'a_pos'); - shaderCtx.a_pos = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, shaderCtx.a_pos); - gl.useProgram(prog); - gl.enableVertexAttribArray(a_pos_loc); - gl.vertexAttribPointer(a_pos_loc, 3, gl.FLOAT, false, 0, 0); shaderCtx.u_screen_loc = gl.getUniformLocation(prog, 'u_screen'); shaderCtx.u_mouse_loc = gl.getUniformLocation(prog, 'u_mouse'); @@ -497,7 +542,7 @@ function ensureCanvas() { function ensureObs() { if(obs === null) { - obs = new MutationObserver(rescan) + obs = new MutationObserver(onMutate) obs.observe(document.body, { childList: true, subtree: true, @@ -505,105 +550,104 @@ function ensureObs() { } } +// Assembly const textCache: WeakMap = new WeakMap(); const assembleCache: WeakMap = new WeakMap(); -function reassemble() { - const traced = document.getElementsByClassName('darker-traced'); +// TODO: segmentation +function assemblePath(paths: string[], sx: number, sy: number, scale: number): number[] { + const buf: number[] = []; + for(const path of paths) { + const dpath = discretize(path); + if(dpath.length === 1) continue; - function assemblePath(paths: string[], sx: number, sy: number, scale: number): number[] { - const buf: number[] = []; - for(const path of paths) { - const dpath = discretize(path); - if(dpath.length === 1) continue; + for(let i = 0; i < dpath.length; ++i) { + let cx = dpath[i].x * scale + sx; + let cy = dpath[i].y * scale + sy; - for(let i = 0; i < dpath.length; ++i) { - let cx = dpath[i].x * scale + sx; - let cy = dpath[i].y * scale + sy; + let nx = dpath[(i + 1) % dpath.length].x * scale + sx; + let ny = dpath[(i + 1) % dpath.length].y * scale + sy; - let nx = dpath[(i + 1) % dpath.length].x * scale + sx; - let ny = dpath[(i + 1) % dpath.length].y * scale + sy; + // Expand a little bit + buf.push( + cx, cy, -0.01, + nx, ny, -0.01, + cx, cy, 5, - // Expand a little bit - buf.push( - cx, cy, -0.01, - nx, ny, -0.01, - cx, cy, 5, - - cx, cy, 5, - nx, ny, 5, - nx, ny, -0.01, - ); - } + cx, cy, 5, + nx, ny, 5, + nx, ny, -0.01, + ); } - return buf; + } + return buf; +} + +function assembleOne(el: HTMLElement, buffer: number[]) { + if(assembleCache.has(el)) { + const cached = assembleCache.get(el)!; + buffer.push(...cached); + return; } - const total: number[] = []; - for(const trace of traced) { - if(assembleCache.has(trace)) { - const cached = assembleCache.get(trace)!; - total.push(...cached); - continue; + const { x, y, width, height } = el.getBoundingClientRect(); + + let populated: number[] = []; + + if(el.tagName === 'use') { + const sym = document.getElementById(el.getAttribute('xlink:href')!.substring(1)) as unknown as SVGSymbolElement; + const vbox = sym.viewBox.baseVal; + // TODO: handle browsers without baseVal + // TODO: handle origins other than 0,0 + + // Firefox fucks up its dimension calculation + const parentDims = el.parentElement!.getBoundingClientRect(); + + const scale = parentDims.width / vbox.width; + const vscale = parentDims.height / vbox.height; + // if(scale > vscale * 1.01 || scale < vscale * 0.99) + // console.warn(`incompatible scales: ${scale}, ${vscale}`); + const paths: string[] | undefined = symbolCache[sym.id]; + if(paths === undefined) { + console.warn(`Symbol not in cache: ${sym.id}`); + return; } - const { x, y, width, height } = trace.getBoundingClientRect(); - - let populated: number[] = []; - - if(trace.tagName === 'use') { - const sym = document.getElementById(trace.getAttribute('xlink:href')!.substring(1)) as unknown as SVGSymbolElement; - const vbox = sym.viewBox.baseVal; - // TODO: handle browsers without baseVal - // TODO: handle origins other than 0,0 - - // Firefox fucks up its dimension calculation - const parentDims = trace.parentElement!.getBoundingClientRect(); - - const scale = parentDims.width / vbox.width; - const vscale = parentDims.height / vbox.height; - // if(scale > vscale * 1.01 || scale < vscale * 0.99) - // console.warn(`incompatible scales: ${scale}, ${vscale}`); - const paths: string[] | undefined = symbolCache[sym.id]; - if(paths === undefined) { - console.warn(`Symbol not in cache: ${sym.id}`); - continue; - } - - populated = assemblePath(paths, x, y + window.scrollY, scale); - } else if(trace.tagName === 'svg') { - let scale = 1; - const vb = trace.getAttribute('viewBox'); - if(vb) { - const [_, __, vboxw, vboxh] = vb.split(' ').map(e => parseFloat(e))!; - scale = width / vboxw; - } - const paths: string[] | undefined = svgCache[trace.id]; - if(paths === undefined) { - console.warn(`SVG not in cache: ${trace.id}`); - continue; - } - populated = assemblePath(paths, x, y + window.scrollY, scale); - } else if(trace.classList.contains('darker-text')) { - let cached = textCache.get(trace); - if(!cached) { - const glyph = trace.getAttribute('data-glyph')!; - const paths = splitPathSegs(glyph); - cached = paths.map(e => e.getAttribute('d')!); - textCache.set(trace, cached); - } - populated = assemblePath(cached, x, y + window.scrollY, 1); + populated = assemblePath(paths, x, y + window.scrollY, scale); + } else if(el.tagName === 'svg') { + let scale = 1; + const vb = el.getAttribute('viewBox'); + if(vb) { + const [_, __, vboxw, vboxh] = vb.split(' ').map(e => parseFloat(e))!; + scale = width / vboxw; } - - assembleCache.set(trace, populated); - total.push(...populated); + const paths: string[] | undefined = svgCache[el.id]; + if(paths === undefined) { + console.warn(`SVG not in cache: ${el.id}`); + return; + } + populated = assemblePath(paths, x, y + window.scrollY, scale); + } else if(el.classList.contains('darker-text')) { + let cached = textCache.get(el); + if(!cached) { + const glyph = el.getAttribute('data-glyph')!; + const paths = splitPathSegs(glyph); + cached = paths.map(e => e.getAttribute('d')!); + textCache.set(el, cached); + } + populated = assemblePath(cached, x, y + window.scrollY, 1); } - // TODO: error on me - if(!shaderCtx.gl) return; + assembleCache.set(el, populated); + buffer.push(...populated); +} + +function assembleAll(els: HTMLElement[]): PathSet { + const buf = [] + for(const el of els) assembleOne(el, buf); + + return new Float32Array(buf); - shaderCtx.gl.bufferData(shaderCtx.gl.ARRAY_BUFFER, new Float32Array(total), shaderCtx.gl.STATIC_DRAW); - shaderCtx.triangleCnt = total.length / 3; } let renderStopped = false; @@ -639,7 +683,21 @@ function renderLoop() { // gl.enable(gl.SAMPLE_COVERAGE); // gl.sampleCoverage(0.5, false); - gl.drawArrays(gl.TRIANGLES, 0, shaderCtx.triangleCnt); + const ext = gl.getExtension("OES_vertex_array_object")!; + + function drawPathSet(ps: RatifiedPathSet) { + ext.bindVertexArrayOES(ps[0]); + gl.drawArrays(gl.TRIANGLES, 0, ps[1]); + } + + // drawPathSet(staticPathSet); + // for(const el of tmpPathSets.keys()) + // if(!document.contains(el)) tmpPathSets.delete(el); + window.wtf = [...tmpPathSets.keys()]; + window.wtf2 = tmpPathSets; + console.log(tmpPathSets.size); + console.log([...tmpPathSets.entries()]); + for(const k of tmpPathSets.keys()) console.log(k) { overlay.width = window.innerWidth;